## 목차 ##



## 1. 간단한 검색 기능 구현 ##

(1) Term 검색

  • 텀을 포함하는 문서를 검색하는 가장 단순한 방법
    • TermQuery객체를 IndexSearcher로 넘겨준다.
IndexSearcher searcher = new IndexSearcher(indexDir);

//필드 문자열이 "str"인 텀 객체  생성
Term term = new Term("fieldName", "str");

//Term 객체를 TermQuery 객체로 변환
Query query = new TermQuery(term);

//TermQuery 객체로 검색
TopDocs hits = searcher.search(query, 10);



(2) QueryParser로 사용자가 입력한 검색어 파싱

  • QueryParser 클래스
    • 사용자가 입력한 텍스트 질의를 동일한 내용의 Query 객체로 변환
    • IndexSearcherQuery객체를 통해 검색할 수 있다.

queryparser_translation


IndexSearcher searcher = new IndexSearcher(indexDir);

//QueryParser 객체 생성
QueryParser parser = new QueryParser(Version.LUCENE_30, "contents", new SimpleAnalyzer());

String q = "mock OR junit"); //텍스트 검색어
Query query = parser.parse(q); //파싱하여 Query객체 생성

//Query 객체로 검색
TopDocs hits = searcher.search(query, 10);
  • QueryParser 객체는 분석기를 지정해주어야한다.
    • 생성자에서 분석기 객체를 매개변수로 넘겨준다.
    • 색인에 추가된 텀과 동일한 텀으로 Query를 만들어야한다.
      • 따라서 색인 과정에서 사용한 분석기와 동일한 분석기를 사용하는것이 좋다.
    • 분석기는 사용자가 입력한 검색어를 Term으로 분리한다.


  • QueryParser가 다룰 수 있는 표현식의 종류
    queryparser_treated_expressions



## 2. IndexSearcher 활용 ##

(1) IndexSearcher 인스턴스 생성

  • 색인 Directory 필요
Directory indexDir = FSDirectory.open(new File(indexPath));
IndexReader reader = IndexReader.open(dir);

IndexSearcher searcher = new IndexSearcher(reader);
//IndexSearcher searcher = new IndexSearcher(indexDir);
  • IndexSearcher 객체 생성시 넘겨주어야 하는 매개변수

    • 인덱스 Directory 혹은 IndexReader
  • IndexReader

    • 색인 내용 불러오기
    • IndexSearcher가 검색할 수 있게 기본적인 색인 관련 API 제공
    • IndexSearcherIndexReader의 API를 활용해 검색 메소드를 제공하는 껍데기일 뿐이다.
    • IndexReader.reopen()은 필요 자원을 최소화하여 색인을 새로 열어 최신 내용을 반영한다.

search_related_classes

(2) TopDocs 결과 활용

  • IndexSearcher의 주요 search 메소드
    • TopDocs search(Query query, int n)
      • 가장 기본적인 검색 메소드로 n개의 문서만 가져온다.
    • TopDocs search(Query query, Filter filter, int n)
      • 필터에 해당하는 n개의 문서만 가져온다.
    • TopFieldDocs search(Query query, Filter filter, int n, Sort sort)
      • 필터에 해당하는 n개의 문서를 sort로 정렬한다.
    • void search(Query query, Collector results)
      • 다른 기준으로 첫 번째 페이지를 확보하고자 하는 경우에 사용
    • void search(Query query, Filter filter, Collector results)
      • 위 메소드와 동일하지만 필터에 해당하는 문서 집합만 대상으로 한다.


  • TopDocs의 멤버변수 혹은 메소드

    • totalHits
      • 검색 결과의 건수
    • scoreDocs
      • 결과를 담고있는 ScoreDoc 객체 배열
    • getMaxScore()
      • 가장 높은 점수 리턴
  • ScoreDoc

    • 실제 문서를 참조하는 객체
    • ScoreDoc.doc으로 문서 ID 참조
    • IndexSearcher.doc(ScoreDoc.doc)을 통해 실제 문서 반환



(3) 준실시간 검색

  • IndexReader를 다시 생성하지 않고도 새로운 추가 내용 확인 가능

  • IndexWriter.getReader()

    • 버퍼에 쌓여있는 변경 사항을 Directory에 반영하고 이를 포함하는 IndexReader를 반환
    • 준실시간 검색의 핵심 메소드
  • reader.reopen()

    • 내부적으로는 getReader()를 다시 호출할 뿐이다.
    • 변경사항이 있으면 이를 포함하는 새로운 IndexReader 객체 생성
    • 변경사항이 없는 색인 파일은 그대로 물려받아 효율적이다.



## 3. 연관도 점수 ##

(1) 점수 계산

tf × idf^2 × boost × lengthNorm × coord × queryNorm

  • tf

    • term frequency
    • 문서에 term이 나타나는 빈도수
  • idf

    • inverse document frequency
    • term이 얼마나 독보적인지 나타내는 지표
    • term을 포함하는 전체 문서 수가 적을수록 높은 점수를 가진다.
  • lengthNorm

    • 필드에 속한 텀의 개수를 정규화한 값
    • 필드 내용의 길이가 짧을수록 높은 점수를 가진다.
  • coord

    • 질의에 지정된 텀을 더 많이 포함하는지를 나타내는 지표
    • ex) query가 'apple banana'이면 'apple'만 포함한 문서보다 'apple'과 'banana' 모두 포함한 문서가 더 높은 점수를 갖는다.
  • queryNorm

    • 질의에 있는 각 텀의 norm의 제곱 합을 정규화한 값


  • Similarity 클래스의 하위 클래스 구현으로 유사도 점수 계산 공식 변경 가능
    • 루씬은 기본적으로 DefaultSimilarity 클래스 사용


  • Explanation 객체를 활용해 유사도 점수 산출 내역 확인
    • IndexSearcher.explain() 메소드가 리턴
Explanation explanation = searcher.explain(Query, ScoreDoc.doc);
System.out.println(explanation.toString());



## 4. 다양한 종류의 질의 ##

(1) TermQuery

  • Term 검색
//field 내용에 text를 포함하는 문서 검색을 위한 Term 객체 생성
Term term = new Term("field", "text");

Query query = new TermQuery(term);



(2) TermRangeQuery

  • Term 범위 검색
//title의 첫 글자가 a~c로 시작하는 문서 검색을 위한 TermRangeQuery 생성
Query query = new TermRangeQuery("title", "a", "c", "true", "true");

매개변수 마지막 두 개의 boolean 값은 각각 시작점과 종료점의 범위를 포함할 것인지를 설정한다. 즉, "a"와 "c"를 사용할 것인지 아닌지 결정하는 것이다.



(3) NumericRangeQuery

  • 숫자 범위 검색
    • NumericField를 검색할 때 사용
NumericRangeQuery query = NumericRangeQuery.newIntRange("pubmonth", 200605, 200609, true, true);



(4) PrefixQuery

  • 접두어 검색
    • 지정한 문자열로 시작하는 텀을 갖는 문서 검색
// /home/computers 하위 디렉토리의 모든 디렉토리 검색
Term term = new Term("path", "/home/computers");
PrefixQuery query = new PrefixQuery(term);



(5) BooleanQuery

  • 논리 구조를 사용해 다양한 질의를 묶어 복잡한 검색이 가능하다.
    • AND, OR, NOT
//제목이 computer인 문서
Query searchingTitleQuery = new TermQuery("title", "computer");

//출간일이 2006년 5월  ~ 2006년 9월인 문서
Query pubmonthQuery = NumericRangeQuery.newIntRange("pubmonth", 200605, 200609, true, true);

//위에서 지정한 두 개의 쿼리를 모두 만족하는 문서 검색
BooleanQuery query = new BooleanQuery();
query.add(searchingTitleQuery, BooleanClause.Occur.MUST);
query.add(pubmonthQuery, BooleanClause.Occur.MUST);

searcher.search(query, 5);


  • BooleanClause
    • Occur.MUST
      • Query에 해당하는 문서만 반환
    • Occur.SHOULD
      • Query에 해당하는 문서를 반환
      • 해당하지 않더라도 다른 조건에 해당하면 반환
      • SHOULD를 사용하면 OR 조건 검색을 할 수 있다.
    • Occur.MUST_NOT
      • Query에 해당하지 않는 문서만 반환



(6) PhraseQuery

  • 구문을 포함하는 문서 검색

    • slop값에 따라 정확히 일치하지 않아도 된다.
  • slop

    • 문서의 구문과 일치하기 위해 구문의 텀을 이동해야하는 횟수
private void index() {

    ...

    Document doc = new Document();
    Field field = new Field("contents", "the quick brown fox jumped over the lazy dog",
                            Field.Store.YES,
                            Field.Index.ANALYZED);

    doc.add(field);
    writer.addDocument(doc);
    writer.close();
}


private void search() {

    ...

    PhraseQuery query = new PhraseQuery();
    query.setSlop(2);

    //텀 2개를 순서대로 구문에 추가
    query.add(new Term("contents", "fox"));
    query.add(new Term("contents", "quick"));

    searcher.search(query, 10);
}

"fox quick"은 fox를 오른쪽으로 세 번 옮겨야 "quick (brown) fox"가 되기때문에 slop 2로는 검색이 되지 않는다.


  • PhraseQuery 점수 계산
    • 구문 질의 결과 문서 점수는 (1/이동거리)에 따라 계산한다.



(7) WildcardQuery

  • 단어를 완벽하게 모르는 상태에서도 검색 가능
    • ? : 글자가 없거나 한 글자
      • ex) ?ather : ather 앞에 글자가 없거나 한 글자인 단어
    • * : 글자가 없거나 한 글자 혹은 여러 글자
      • ex) ?ather : ather 앞에 글자가 없거나 있거나. 즉, ather로 끝나는 단어
// ild앞에 글자가 없거나 한 글자, 그리고 뒤에 글자가 없거나 하나 이상의 글자인 단어로 쿼리 생성
Term term = new Term("contents", "?ild*");
Query query = new WildcardQuery(term);



(8) FuzzyQuery

  • 비슷한 단어 검색
    • Levenshtein 편집거리 기반
private void index() {
    ...
    doc.add(new Field("contents", "fuzzy", Field.Store.YES, Field.Index.ANALYZED));
    writer.addDocument(doc);

    ...
    doc.add(new Field("contents", "wuzzy", Field.Store.YES, Field.Index.ANALYZED));
    writer.add(Document(doc);
}

private void search() {
    ...
    Query query = new FuzzyQuery(new Term("contents", "wuzza"));

    searcher.search(query, 10);
}

wuzzy가 편집거리 1이므로 먼저 나올 것이다.

  • 유사도 임계값 보다 작은 문서만 리턴

    • threshold = 편집거리 / 문자열 길이
  • FuzzyQuery는 색인된 모든 텀을 대상으로 편집거리를 계산

    • 느리다.



(9) MatchAllDocsQuery

  • 모든 문서 반환

  • Query query = new MatchAllDocsQuery(Field);

    • 해당 필드에 지정했던 중요도 값을 유사도 점수로 활용하여 정렬



## QueryParser로 질의 표현식 파싱 ##

  • QueryParser를 사용하면 간편하게 Query를 생성할 수 있다.

(1) Query.toString()

  • QueryParser로 파싱한 질의식 확인



(2) TermQuery

  • QueryParser는 검색어 표현식의 단일 단어를 TermQuery로 변환한다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
Query query = parser.parse("computers");
System.out.println(query);



출력결과

subject:computers



(3) 텀 범위 검색

  • 문자열, 날짜 등의 범위 검색
    • 괄호 사용
    • 시작 텀과 종료 텀 사이 TO 사용
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);

//title필드 내용이 a~c로 시작하는 문서를 검색하기 위한 쿼리 
Query query = parser.parse("title:[a TO c]");



(4) 숫자, 날짜 범위 검색

  • QueryParserNumericRangeQuery를 지원하지 않는다.
    • 루씬은 필드별로 NumericField로 색인했는지 여부를 저장하지 않기 때문



(5) 접두어, 와일드카드 질의

  • 텀에 별표(*)나 물음표(?)가 포함돼 있으면 QueryParserWildcardQuery로 변환된다.

  • 별표 기호가 단어 뒤에만 있으면 PrefixQuery로 변환된다.

QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);

//QueryParser가 PrefixQuery로 변환
Query query = parser.parse("head*");

//QueryParser가 WildcardQuery로 변환
Query query = parser.parse("?ello*");



(6) BooleanQuery

  • Term이 여러개인 경우 BooleanQuery로 변환

    • Boolean 연산자의 기본값은 OR 연산이다.
  • AND, OR, NOT 연산자를 사용하면 QueryParserBooleanQuery로 변환

QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);

//기본 연산자를 AND 연산으로 지정
parser.setDefaultOperator(QueryParser.AND_OPERATOR);

//"apple"과 "banana" 모두 들어있는 문서 검색을 위한 쿼리
Query query = parser.parse("apple banana");
//Query query = parser.parse("+apple +banana");

//"apple"은 포함하고 "banana"는 포함하지 않는 문서 검색을 위한 쿼리
Query query2 = parser.parse("apple AND NOT banana");
//Query query2 = parser.parse("+apple -banana");



(7) Phrase Query

  • 여러 Term이 큰따옴표 안에 있으면 PhraseQuery로 변환한다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);

//slop값을 5로 지정
parser.setPhraseSlop(5);

//"linux kafka apache" 구문의 slop 5 이내 구문이 포함된 문서 검색을 위한 쿼리
Query query = parser.parse("\"linux kafka apache\"");

구문 검색으로 넘기기 위해 큰따옴표(")를 escape 했다.



(8) Fuzzy Query

  • 물결기호(~)를 붙이면 QueryParserFuzzyQuery로 변환한다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);

Query query = parser.parse("rinux~");
//Query query = parser.parse("rinux~0.77"); //FuzzyQuery의 유사도 임계값 0.7로 설정



(9) MatchAllDocsQuery

  • 질의 표현식으로 *:*을 입력하면 MatchAllDocsQuery 생성



(10) 필드 선택

  • QueryParser에서 지정한 필드는 기본값일 뿐 쿼리 필드 이름은 제한이 없다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);

//title 필드값이 lucene이고 author 필드값이 kelly인 문서 검색을 위한 쿼리
Query query = parser.parse("(title:lucene AND author:kelly");



(11) Term 중요도 지정

  • ^ 연산자 사용
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);

//"apple"은 중요도를 2.0으로 지정, "banana"는 중요도 기본 값 사용
Query query = parser.parse("apple^2.0 banana");



(12) QueryParser를 사용해야 하는가?

  • 의도한 Query가 나오지 않아 원하는 검색결과를 얻지 못하는 경우를 조심
    • 대부분이 분석기를 통과하면서 검색어가 변경되는 현상과 관련있다.

'프레임워크 > 루씬(Lucene)' 카테고리의 다른 글

(루씬) 4 - 고급 검색 기법  (0) 2020.05.16
(루씬) 2 - 색인(Indexing)  (0) 2020.05.16
(루씬) 1 - 루씬과의 만남  (0) 2020.05.16

+ Recent posts