: solr search_engine

LTR (Learning To Rank) 이란?

주로 기존 검색엔진들은 score 즉 정렬에 필요한 Similarity 알고리즘을 주로 TF-IDF 혹은 BM25 알고리즘에 의지하였다.
일반적으로 업체들도 검색엔진을 사용할때 보통 이 알고리즘들에 기반하고 자신의 비즈니스 로직에 의한 가중치값을 더 추가하여 서비스에 사용하였을것이다.
LTR (Learning To Rank)란 머신러닝으로 계산된 스코어를 기반으로 정렬하는것을 의미한다. 말만하면 지루하니 위키피디아에 있는 이미지를 봐보자.

색인된 인덱스 파일에서 유저 쿼리가 들어와 Similarity를 계산할때 머신러닝 알고리즘으로 Training된 데이터를 기반으로 스코어를 계산하여 랭킹모델에 사용한다는 것이다.

한줄요약하면 스코어 만드는데 머신러닝 알고리즘 씀 이다.

Solr 적용

Solr의 최신버전인 6.5버전의 스펙에 이 LTR알고리즘이 추가되었다. 다만 아직 그냥 사용할수는 없고 solr를 기동할때 --Dsolr.ltr.enabled=true argument를 넘겨줘야한다.
아직 시험적인 적용이라 그러지 않을까 생각된다.
Solr Wiki의 예제를 따라가보자. 솔라 최신버전을 받고 압축을 해제한뒤 다음 명령어를 실행하자.
bin/solr start -e techproducts -Dsolr.ltr.enabled=true
기동이 성공적으로 되면 예제로 주어지는 techproducts볼륨도 함께 만들어지면서 기동된것을 확인할 수 있다. 다음 메시지가 뜨면 기동에 성공한것이다.

SimplePostTool version 5.0.0
Posting files to [base] url http://localhost:8983/solr/techproducts/update using content-type application/xml...
POSTing file gb18030-example.xml to [base]
POSTing file hd.xml to [base]
POSTing file ipod_other.xml to [base]
POSTing file ipod_video.xml to [base]
POSTing file manufacturers.xml to [base]
POSTing file mem.xml to [base]
POSTing file money.xml to [base]
POSTing file monitor.xml to [base]
POSTing file monitor2.xml to [base]
POSTing file mp500.xml to [base]
POSTing file sd500.xml to [base]
POSTing file solr.xml to [base]
POSTing file utf8-example.xml to [base]
POSTing file vidcard.xml to [base]
14 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/techproducts/update...
Time spent: 0:00:00.788

그 다음 json파일을 작성한다. 파일이름은 myFeatures.json이다

[{
  "name": "documentRecency",
  "class": "org.apache.solr.ltr.feature.SolrFeature",
  "params": {
    "q": "{!func}recip(ms(NOW,last_modified),3.16e-11,1,1)"
  }
}, {
  "name": "isBook",
  "class": "org.apache.solr.ltr.feature.SolrFeature",
  "params": {
    "fq": ["{!terms f=cat}book"]
  }
}, {
  "name": "originalScore",
  "class": "org.apache.solr.ltr.feature.OriginalScoreFeature",
  "params": {}
}]

작성한 파일을 solr에 feature로 넘겨주자
curl -XPUT 'http://localhost:8983/solr/techproducts/schema/feature-store' --data-binary '@/path/to/myFeatures.json' -H 'Content-type:application/json'
만일 json syntax오류가 발생한다면 이 블로그에서 markdown -> html을 만들때 이상한 코드가 들어가서 그런거일 수 있으니 천천히 타이핑 해서 잘 만들어보자(–;;)

http://localhost:8983/solr/techproducts/schema/feature-store/_DEFAULT_로 접속해서 다음과 같이 추가한 피쳐들이 조회되면 성공이다.

{
  "responseHeader":{
    "status":0,
    "QTime":23},
  "features":[{
      "name":"documentRecency",
      "class":"org.apache.solr.ltr.feature.SolrFeature",
      "params":{"q":"{!func}recip(ms(NOW,last_modified),3.16e-11,1,1)"},
      "store":"_DEFAULT_"},
    {
      "name":"isBook",
      "class":"org.apache.solr.ltr.feature.SolrFeature",
      "params":{"fq":["{!terms f=cat}book"]},
      "store":"_DEFAULT_"},
    {
      "name":"originalScore",
      "class":"org.apache.solr.ltr.feature.OriginalScoreFeature",
      "params":null,
      "store":"_DEFAULT_"}]}

적용시킬 스코어를 document의 쿼리 조건에 따라 적용할 수 있는것으로 판단된다.

이제는 model을 만들어야 한다. 적용하는 feature의 가중치(weight)를 설정하는것으로 판단된다. myModel.json으로 저장한다.

{
  "class" : "org.apache.solr.ltr.model.LinearModel",
  "name" : "myModel",
  "features" : [
    { "name" : "documentRecency" },
    { "name" : "isBook" },
    { "name" : "originalScore" }
  ],
  "params" : {
    "weights" : {
      "documentRecency" : 1.0,
      "isBook" : 0.1,
      "originalScore" : 0.5
    }
  }
}

저장한 json파일을 solr에 다시 넣어준다.
curl -XPUT 'http://localhost:8983/solr/techproducts/schema/model-store' --data-binary "@/path/myModel.json" -H 'Content-type:application/json'

http://localhost:8983/solr/techproducts/schema/model-store로 접속되서 잘 학습되었는지 확인한다. 다음과 같은 response가 나오면 성공이다.

{
  "responseHeader":{
    "status":0,
    "QTime":35},
  "models":[{
      "name":"myModel",
      "class":"org.apache.solr.ltr.model.LinearModel",
      "store":"_DEFAULT_",
      "features":[{
          "name":"documentRecency",
          "norm":{"class":"org.apache.solr.ltr.norm.IdentityNormalizer"}},
        {
          "name":"isBook",
          "norm":{"class":"org.apache.solr.ltr.norm.IdentityNormalizer"}},
        {
          "name":"originalScore",
          "norm":{"class":"org.apache.solr.ltr.norm.IdentityNormalizer"}}],
      "params":{"weights":{
          "documentRecency":1.0,
          "isBook":0.1,
          "originalScore":0.5}}}]}

여기서 설정한 “name”:”myModel” 값과 weights를 조절하여 여러벌의 모델을 만들어서 A/B 테스트처럼 진행도 가능하다.

학습시킨 피쳐는 쿼리의 fl= 파라미터에 [features]를 추가해서 확인할 수 있고 학습시킨 feature는 solr에 기존에 있었던 re-rank쿼리를 이용해서 사용한다.
스코어를 한 번 유심히 봐보자
http://localhost:8983/solr/techproducts/query?q=test&fl=id,score,[features]

{
  "responseHeader":{
    "status":0,
    "QTime":9,
    "params":{
      "q":"test",
      "fl":"id,score,[features]"}},
  "response":{"numFound":2,"start":0,"maxScore":1.959392,"docs":[
      {
        "id":"GB18030TEST",
        "score":1.959392,
        "[features]":"documentRecency=0.020832444,isBook=0.0,originalScore=1.959392"},
      {
        "id":"UTF8TEST",
        "score":1.5513437,
        "[features]":"documentRecency=0.020832444,isBook=0.0,originalScore=1.5513437"}]
  }}

다음은 re-rank를 적용한 쿼리이다. model json파일에서 사용한 이름이 여기서 model=을 지정할때 사용된다.
http://localhost:8983/solr/techproducts/query?q=test&rq={!ltr model=myModel reRankDocs=100}&fl=id,score,[features]

{
  "responseHeader":{
    "status":0,
    "QTime":12,
    "params":{
      "q":"test",
      "fl":"id,score,[features]",
      "rq":"{!ltr model=myModel reRankDocs=100}"}},
  "response":{"numFound":2,"start":0,"maxScore":1.0005285,"docs":[
      {
        "id":"GB18030TEST",
        "score":1.0005285,
        "[features]":"documentRecency=0.020832442,isBook=0.0,originalScore=1.959392"},
      {
        "id":"UTF8TEST",
        "score":0.7965043,
        "[features]":"documentRecency=0.020832442,isBook=0.0,originalScore=1.5513437"}]
  }}

feature가 적용되어 스코어에 변동이 생긴것을 확인할 수 있다.
예제에서 검색된 두개의 문서는 카테고리필드(cat)에 book에 해당하는 것은 없어 isBook feature에는 스코어가 적용되지 않고 documentRecency필드로만 가중치가 적용된것으로 보여진다.

계산을 해보자면 myModel.json에 originalScore의 weight는 0.5 즉 절반만 사용하고 신상 가중치(recency)는 1.0 즉 전체를 사용한다고 설정했으니
(1.959392 * 0.5) + (0.020832442 * 1.0) = 1.0005285가 되는 것이다.

사실 글을 작성하는 지금 시점에 아직 적절한 예제가 없어 기능만 간단히 테스트 해볼 수 있는 정도이다. 자세한 사항은 Solr Wiki Learning To Rank Page를 참조 바라며 해당글은 기능 소개 정도로 정리하고 조만간 더 테스트를 한뒤 다시 글을 정리 해보는 시간을 가져야 할 것 같다.