Today I Learned

[루씬 인 액션] 1장. 루씬과의 만남 본문

루씬 Lucene

[루씬 인 액션] 1장. 루씬과의 만남

하이라이터 2020. 11. 3. 02:07
728x90

1장에서 다루는 내용

 

- 루씬의 소개

- 일반적인 검색 에플리케이션의 구조

- 기본적인 색인  API

- 기본적인 검색  API


1.2 루씬이란?

고성능 정보검색 (IR, informataion retrieval) 라이브러리

전문(full-text) 색인과 검색 기능을 제공

핵심 기능 이외에 맞춤법 검사 기능이나 결과 하이라이팅 등의 추가기능을 담당하는 다양한 확장 모듈도 제공

 

 

1.2.1 루씬으로 처리하기 좋은 기능

루씬은 파일 검색이나 웹 문서 수집, 웹 검색 등에 바로 사용할 수 있는 애플리케이션? X

검색 기능을 갖고있는 애플리케이션을 개발할 때 사용할 수 있는 도구(소프트웨어 라이브러리) ? O

 

루씬은 색인과 검색 기능에만 집중하며, 색인과 검색을 강력하게 지원한다.

 

문서 파일, 웹페이지 파일, 단순 텍스트 파일, DOC, XML, HTML, PDF 파일 등 어떤 종류이든지 상관없이 검색 텍스트만 추출할 수 있으면 루씬을 통해 색인하고 검색할 수 있다.


1.3 루씬과 검색 애플리케이션의 구조

검색 애플리케이션의 색인 절차

  1. 원본 파일을 가져온다.

  2. 루씬 문서 형태로 변환한다.

  3. 변환된 텍스트를 색인에 추가한다.

  4. 사용자가 찾고자하는 검색어를 입력받는다.

  5. 검색어를 루씬 질의로 변환한다.

  6. 색인을 대상으로 준비한 루씬 질의를 실행해 결과를 받아온다.

  7. 화면에 출력한다.


1.3.1 색인 과정 구성 요소

색인이란?

원본 텍스트를 분석해 사용자가 원하는 정보를 빠르게 찾아낼 수 있게 재구성하는 절차

 

원본 문서를 색인하고 검색하기 좋은 형태로 변환해 색인으로 만들어두면 사용자가 검색어를 입력할 때마다 순차적으로 파일을 읽어가며 원하는 내용이 있는지 확인할 필요가 없다.

이러한 처리작업을 색인(indexing)이라고 하며, 색인 작업을 거친 결과물 역시 색인(index)이라고 부른다.

 

 

1. 검색 대상 텍스트 확보

문서 수집기를 사용해 색인할 대상 문서를 모아오는 단계

루씬은 검색 대상 확보와 관련한 기능은 포함하지 않으며, 검색 대상 텍스트를 확보하는 기능은 루씬에 연동하는 검색 애플리케이션에서 담당

 

 

2. 루씬 문서 생성

검색할 대상 문서의 원본을 확보한 후, 원본을 루씬에서 사용하는 개별 단위인 문서(document)로 변환하는 단계

루씬 문서는 이름이 붙은 여러 개의 필드(field)로 구성 (ex. 책에 대한 정보를 검색한다면 제목, 본문, 요약, 저자, 링크 등의 필드)

 

이 단계에서 포함되는 작업

- 텍스트 추출 및 필터링

- 텍스트 의미 분석(semantic analysis)을 통한 필드 추출 및 저장

- 문서나 필드 단위의 중요도(boost) 지정

 

루씬은 필드를 사용해 문서를 구성해주는 API를 제공하지만, 문서 객체를 생성하는 부분 또한 애플리케이션의 역할

 

 

3. 문서 텍스트 분석

텍스트 분석을 통한 토큰(token) 추출 단계

 

토큰이란?

문법적으로 더 이상 나눌 수 없는 언어요소, 상황에 따라 의미있는 단위로 토큰을 정의

연결된 단어, 맞춤법 검사, 동의어, 단수복수, 대소문자 등에 대한 처리 과정을 거쳐 토큰의 정규화를 수행한다.

 

루씬에는 다수의 텍스트 분석기가 내장돼있으며, 상세한 부분까지 조절 가능

 

토큰화 참고자료 : https://wikidocs.net/21698

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

 

4. 색인에 문서 추가

색인 과정을 모두 거치고 문서를 색인에 추가하는 단계

 

루씬은 문서를 색인에 추가하는데 필요한 모든 기능을 제공


1.3.2 검색 과정 구성 요소

검색(searching) : 색인에 들어있는 토큰을 기준으로 해당하는 토큰이 포함된 문서를 찾아내는 과정

검색의 품질은 흔히 정확도(precision)과 재현율(recall)로 표현

 

 

1. 검색 사용자 인터페이스

사용자가 웹브라우저나 데스크탑 애플리케이션, 휴대용 기기 등에서 직접 보면서 검색 에플리케이션을 사용하는 화면

 

고려되어야할 항목

- 간결한 인터페이스

- 결과를 표현하는 방법

- 명확한 정렬 조건과 적절한 기본값(일반적으로 검색어와의 연관도 기준)

- 고급 기능에 대한 안내(동의어 확장, 중요도 기준 정렬, 맞춤법 검사 등)

 

 

2. 검색 질의 생성

서버에서 전달받은 검색어를 검색 엔진에서 인식하는 Query 객체로 변환하는 과정

 

루씬의 QueryParser를 통해 검색 문자열을 Query 객체로 변환 가능

boolean 질의, 구문 질의, 와일드 카드 질의 등을 사용할 수 있고 가중치 및 필터를 검색결과에 적용할 수 있다.

 

 

3. 질의로 검색

실제 색인틀 통해 검색 질의에서 생성한 Query 객체에 해당하는 결과를 적당한 정렬 순서에 맞게 받아오는 단계

검색 엔진이 동작하는 복잡한 내부 구조가 포함되며,  결과 수집, 필터 적용, 정렬 방법들도 조정 가능

 

정보 검색 분야의 이론적인 검색 모델

  • 순수 불리언 모델 : 지정된 질의에 문서가 해당하는지 아닌지를 판단. 색인 안에서 질의에 해당하는 문서의 부분집합을 추출
  • 벡터 공간 모델 : 질의와 문서 모두 고차원 공간의 벡터로 표현. 벡터 간의 거리를 계산하여 문서와 질의 간의 연관도, 유사도를 산출
  • 확률 모델 : 확률적인 방법을 통해 개별 문서가 질의와 일치하는 확률을 계산

루씬에서는 벡터 공간 모델과 순수 불리언 모델을 함께 사용

 

 

4. 결과 출력


1.3.3 검색 에플리케이션의 나머지 요소

 

1. 관리 인터페이스

검색 엔진의 설정 항목들을 관리

문서 수집 시작 URL, 페이지 이동 속도, 서버 상태 관리, 백업 및 복원 기능, 메모리 버퍼 용량, 최적화 작업 시점 등등

 

 

2. 분석 인터페이스

사용자의 검색 질의 패턴 분석을 통해 검색 시스템을 최적화 할 수 있다.

 

루씬에서 제공하는 분석 정보

- 실행된 질의의 종류별 빈도수

- 연관도가 낮은 결과를 뽑아낸 질의

- 사용자가 결과에 대해 아무 항목도 클릭하지 않은 질의

- 연관도 대신 별도의 필드 기준으로 정렬하는 빈도수

- 루씬에서 검색하는 데 걸린 시간 내역 분석

 

 

3. 시스템 확장

검색 대상 문서의 개수와 검색 질의 속도를 고려한 확장이 필요

검색 대상 문서의 양이 많을 경우 - 색인을 여러 개의 shard로 분리하여, 여러 대의 장비에서 검색결과를 취합하여 구성

사용자의 검색 요청 양이 많을 경우 - 동일한 색인을 여러 장비에 복제

 

분산 환경에서는, 확장성을 지원하는 분산 처리 기능이 필요(루씬에는 없음)

 


1.4 루씬 인 액션 : 예제 애플리케이션

 

1. Indexer

public class Indexer {
    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            throw new IllegalArgumentException("Usage: java " + Indexer.class.getName() + " <index dir> <data dir>");
        }
        
        String indexDir = args[0];  //색인 디렉토리
        String dataDir = args[1];   //txt 파일
        
        long start = System.currentTimeMillis();
        Indexer indexer = new Indexer(indexDir);
        
        int numIndexed;
        
        try {
            numIndexed = indexer.index(dataDir, new TextFilesFilter());
        } finally {
            indexer.close();
        }
        long end = System.currentTimeMillis();

        System.out.println("Indexing " + numIndexed + " files took "  + (end - start) + " milliseconds");
    }
    
    private IndexWriter writer;

    public Indexer(String indexDir) throws IOException {
        //루씬의 IndexWriter 생성
        Directory dir = FSDirectory.open(new File(indexDir));
        writer = new IndexWriter(dir,
                    new StandardAnalyzer(
                        Version.LUCENE_30),
                    true,                       
                    IndexWriter.MaxFieldLength.UNLIMITED);
    }
        
    public void close() throws IOException {
        writer.close(); //IndexWriter 닫음
    }

    public int index(String dataDir, FileFilter filter) throws Exception {
        File[] files = new File(dataDir).listFiles();
        
        for (File f: files) {
            if (!f.isDirectory() &&
                    !f.isHidden() &&
                    f.exists() &&
                    f.canRead() &&
                    (filter == null || filter.accept(f))) {
                indexFile(f);
            }
        }
        
        return writer.numDocs(); // 색인된 문서 건수 return
    }
        
    private static class TextFilesFilter implements FileFilter {
        public boolean accept(File path) {
            return path.getName().toLowerCase().endsWith(".txt"); //색인할 txt파일
        }
    }
    
    protected Document getDocument(File f) throws Exception {
        Document doc = new Document();
        doc.add(new Field("contents", new FileReader(f))); // 파일 내용
        doc.add(new Field("filename", f.getName(), Field.Store.YES, Field.Index.NOT_ANALYZED)); //파일 이름
        doc.add(new Field("fullpath", f.getCanonicalPath(), Field.Store.YES, Field.Index.NOT_ANALYZED)); //파일 경로
        return doc;
    }
    
    private void indexFile(File f) throws Exception {
        System.out.println("Indexing " + f.getCanonicalPath());
        Document doc = getDocument(f);
        writer.addDocument(doc); // 루씬 색인에 문서 추가
    }
}

2. Searcher

public class Searcher {
    public static void main(String[] args) throws IllegalArgumentException, IOException, ParseException {
        if (args.length != 2) {
            throw new IllegalArgumentException("Usage: java " + Searcher.class.getName() + " <index dir> <query>");
        }
        
        String indexDir = args[0]; //색인 디렉토리
        String q = args[1]; //검색어
        search(indexDir, q);
    }
    
    public static void search(String indexDir, String q) throws IOException, ParseException {
        Directory dir = FSDirectory.open(new File(indexDir)); 
        IndexSearcher is = new IndexSearcher(dir);   // 색인 열기
        
        //질의 분석
        QueryParser parser = new QueryParser(Version.LUCENE_30,
                                    "contents",
                                    new StandardAnalyzer(
                                    Version.LUCENE_30));
        Query query = parser.parse(q); 
        long start = System.currentTimeMillis();
        TopDocs hits = is.search(query, 10); // 색인의 내용 검색
        long end = System.currentTimeMillis();
        
        System.err.println("Found " + hits.totalHits + " document(s) (in " + (end - start) + " milliseconds) that matched query '" + q + "':");                                   // 6. 검색 결과 관련 정보
        
        for(ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc); //결과문서
            System.out.println(doc.get("fullpath"));
        }
        
        is.close();
    }
}

1.5 색인 관련 핵심 클래스

1.5.1 IndexWriter

색인을 새로 생성 또는 기존 색인의 문서를 추가/삭제/변경하는 기능을 담당

 

1.5.2 Directory

색인을 저장하는 공간

추상클래스이며, 색인을 저장할 공간에 따라 상속받아 메소드 구현 필요

 

1.5.3 Analyzer

지정된 텍스트를 색인할 단위 언어로 분리하고 필요없는 단어를 제거하는 기능을 담당

 

1.5.4 Document

개별 필드의 집합

웹 페이지, 이메일 메시지, 텍스트 파일, 단순 텍스트 등 검색 결과로 받아보려는 결과 단위

 

1.5.5 Field

색인의 각 문서는 두개 이상의 개별 필드로 구성

루씬은 필드마다 지정된 설정에 따라 해당 필드의 값을 색인


1.6 검색 관련 핵심 클래스

1.6.1 IndexSearcher

검색을 담당하는 클래스이며, 여러 종류의 검색 메소드를 지원

 

1.6.2 Term

검색 과정을 구성하는 가장 기본적인 단위

Field 클래스처럼 필드 이름과 해당 필드에 속한 특정 단어의 쌍으로 구성

 

1.6.3 Query

가장 최소한의 공통부분만 갖고 있는 최상위 질의 클래스

 

1.6.4 TermQuery

루씬에서 지원하는 질의 중 가장 기본적인 기능

특정 필드에 원하는 단어가 들어 있는 문서를 찾음

 

1.6.5 TopDocs

검색 결과 중 최상위 N개의 문서에 대한 링크를 담고 있는 결과 클래스


1.7 정리

루씬은 모든 기능을 제공하는 검색 애플리케이션이 아니라 단순한 검색 라이브러리이다.

 

 

 

728x90
Comments