본문 바로가기
ELK

Elasticsearch를 이용한 문서 유사도 검색과 Springboot를 통한 구현 (1)

by AndoneKwon 2020. 9. 5.

문서 유사도?

기본적으로 문서가 얼마나 유사하는가에 대한 계산은 Cosine 유사도로 판단하게 된다.

그 이전에 사용하는 개념이 tf-idf인데 그 설명은 위키피디아에 다음과 같이 설명되어 있다.

 

TF-IDF(Term Frequency - Inverse Document Frequency)는 정보 검색과 텍스트 마이닝에서 이용하는 가중치로, 여러 문서로 이루어진 문서군이 있을 때 어떤 단어가 특정 문서 내에서 얼마나 중요한 것인지를 나타내는 통계적 수치이다. 문서의 핵심어를 추출하거나, 검색 엔진에서 검색 결과의 순위를 결정하거나, 문서들 사이의 비슷한 정도를 구하는 등의 용도로 사용할 수 있다.

TF(단어 빈도, term frequency)는 특정한 단어가 문서 내에 얼마나 자주 등장하는지를 나타내는 값으로, 이 값이 높을수록 문서에서 중요하다고 생각할 수 있다. 하지만 단어 자체가 문서군 내에서 자주 사용 되는 경우, 이것은 그 단어가 흔하게 등장한다는 것을 의미한다. 이것을 DF(문서 빈도, document frequency)라고 하며, 이 값의 역수를 IDF(역문서 빈도, inverse document frequency)라고 한다. TF-IDF는 TF와 IDF를 곱한 값이다.

IDF 값은 문서군의 성격에 따라 결정된다. 예를 들어 '원자'라는 낱말은 일반적인 문서들 사이에서는 잘 나오지 않기 때문에 IDF 값이 높아지고 문서의 핵심어가 될 수 있지만, 원자에 대한 문서를 모아놓은 문서군의 경우 이 낱말은 상투어가 되어 각 문서들을 세분화하여 구분할 수 있는 다른 낱말들이 높은 가중치를 얻게 된다.

 

말그대로 특정 단어가 문서내에서 얼마나 자주 등장하는지와 너무 자주 등장하는 단어의 경우 그 문서의 핵심 단어라고 말할 수 없는(예를들어 조사같은 것들은 모든 문서에 공통적인 것이기 떄문에 중요도가 높다고 할 수 없다.

이 두가지를 동시에 반영(곱한다)한 것이 tf-idf인데 자세한 수학적 공식은 구글링을 통해 스스로 알아보도록 하자.(어차피 밑에 코드에서 나오기는 하지만..)

아무튼 이러한 것들을 elasticsearch에서는 script를 직접 작성할 수 있게 지원하기 때문에 similarity를 평가하는데에 기준을 tf-idf로 둔다. 그 코드는 다음과 같다.(elasticsearch 공식 홈페이지에 있는 내용을 가지고 왔다.)

 

유사도 검색을 위한 Elasticsearch index 설정

 

"scripted_tfidf": {
        "type": "scripted",
        "script": {
          "source": "double tf = Math.sqrt(doc.freq); double idf = Math.log((field.docCount+1.0)/(term.docFreq+1.0)) + 1.0; double norm = 1/Math.sqrt(doc.length); return query.boost * tf * idf * norm;"
        }
      }

 

scipted_tfidf라고 짜놓은 스크립트 이름을 지정해주고 source에 그 식을 적어준다.

double tf = Math.sqrt(doc.freq); //tf계산식이다.
double idf = Math.log((field.docCount+1.0)//(term.docFreq+1.0)) + 1.0; //idf계산식이다.
double norm = 1/Math.sqrt(doc.length); //정규화를 위한 식이다.
return query.boost * tf * idf * norm; //이렇게 모든 값을 source에 반영한다.

아무튼.. 이렇게 tf-idf로 기준값을 줬으니 다음에는 유사도를 계산할 차례이다.

cosine유사도를 우리가 직접 구현할 필요는 없다. 왜냐면 이미 elasticsearch에 similarity가 이미 구현되어있기 때문이다.

또한 elasticsearch에 입력되는 문서들을 넣을때 단순히 text또는 keyword로 넣어버리면 그 개수를 알 수 없기때문에

 

"analysis": {
        "analyzer": {
          "nori_analyzer": {
            "tokenizer": "nori_my_dict_tokenizer"
          }
        },
        "tokenizer": {
          "nori_my_dict_tokenizer": {
            "type": "nori_tokenizer",
            "decompound_mode": "mixed"
          }
        }
      }

 

tokenizer를 지정해준다.

기본적으로 elasticsearch에서 지원해주는 nori_tokenizer를 사용한다.

그 후 rdbms에서 table과 같은 index를 지정해준다.

여기선 테스트이기 때문에 단순히 하나의 필드만 지정한다.

 

"mappings": {
    "properties": {
      "field": {
        "type": "text",
        "similarity": "scripted_tfidf",
        "analyzer":"nori_analyzer"
      }
    }
  }

 

추가로 similarity도 어떠한 기준을 이용할것인지 정해줘야한다.

이것으로 기본적인 유사도를 위한 elasticsearch의 설정은 완료되었다.

쿼리스트링을 이용해서 검색하면 매우 간단하지만 서버를 구축하기 위해 Spring boot(Java)를 이용한 구현을 다음장에 마져 포스팅하겠다.