LLM

Opensearch의 PreProcessing ? - Opensearch Analyzer

aiden0729

 

Analyzer라는 이름만 들으면 numeric data와 자연스럽게 연결되는 나의 편견 때문에 아직도 왜 검색엔진의 Analyzer인지 명확하게 와닿지는 않지만 ( 아마 '분석' 후 가공 정도의 의미일 것으로 추측한다. ) , Opensearch에서의 Analyzer는 Tokenizer와 불용어 등의 filter 를 하는 즉 python 내 nltk ( Natural Language Tookit ) 과 비슷한 Pre-Processing 역할을 해준다. 

 

정확히는 Character Filter -> Tokenizer -> Token Filters의 일련의 과정들을 한 번에 처리해주는 Piple-line역할을 한다. 

**Charactor Filter : 토크나이징 전에 텍스트를 전처리 (예: HTML 태그 제거, 특정 문자 치환 등)

** Tokenizer : 텍스트를 토큰(단어 등)으로 나누는 역할

** Token Filters : Tokenizer생성한 토큰들을 추가로 변형 (소문자화, 불용어 제거 등 ) 

 

 

단, 모든 analyzer는 Mapping시, text 타입에만 적용이 가능하다. 애초에 keyword 부터 integer, boolean 등등은 text analyze가 필요없기 때문이다.

 

Analyzer 처리 방식 토큰 분리 예시
standard - 단어 경계에서 토큰화- 대부분의 구두점 제거- 소문자 변환 [it’s, fun, to, contribute, a, brand, new, pr, or, 2, to, opensearch]
simple - 비문자(숫자·문자 외) 기준 토큰화- 비문자 제거- 소문자 변환 [it, s, fun, to, contribute, a, brand, new, pr, or, to, opensearch]
whitespace - 공백(스페이스, 탭 등) 기준 토큰화 [It’s, fun, to, contribute, a, brand-new, PR, or, 2, to, OpenSearch!]
stop - simple 분석 + 불용어(예: “to”, “or”, “a”) 제거- 소문자 변환 [s, fun, contribute, brand, new, pr, opensearch]
keyword - 전체 문자열을 단일 토큰으로 취급 (no-op) [It’s fun to contribute a brand-new PR or 2 to OpenSearch!]
pattern - 정규표현식 기반 토큰화- 소문자 변환- (옵션) 불용어 제거 [it, s, fun, to, contribute, a, brand, new, pr, or, 2, to, opensearch]
language  - 언어별 형태소 분석·스테밍·불용어 제거 (영어 기준), 한국어는 nori 플러그인 사용 [fun, contribut, brand, new, pr, 2, opensearch]
fingerprint - 비문자 기준 토큰화- ASCII 정규화(asciifolding)- 소문자 변환- 토큰 중복 제거·정렬·단일 토큰화 [2 a brand contribute fun it's new opensearch or pr to]

 

 

 

 

 

 

다만 위는 전통적인 bm25를 위한 기법이며, 문맥이 중요할 수도 있는 벡터검색에서는 오히려 임베딩 및 검색 품질이 저해될 수 있다. 

예시 ) 

"How do I go to the airport?"  
→ "go airport" 로 줄이면 의미 흐름 망가짐

 

 

따라서 아래와 같이 다른 데이터셋을 사용하여 hybrid search를 하는 방법도 고려해봐야한다. 각각 데이터셋이나 목적이 다르기 때문에 결과물에 따라 휴리스틱하게 적용하면 될 것 같다. 

 

custom_bm25_analyzer 정의

PUT my-index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "custom_bm25_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "stop", "stemmer"]
        }
      }
    }
  },

 

데이터셋 분리 및 정의 ( bm25 analyzer / raw / 임베딩 vector )

  "mappings": {
    "properties": {
      "content_bm25": {
        "type": "text",
        "analyzer": "custom_bm25_analyzer"
      },
      "content_raw": {
        "type": "text",
        "index": false
      },
      "content_vector": {
        "type": "dense_vector",
        "dims": 768
      }
    }
  }
}

 

데이터 Ingest 시 순서

POST my-index/_doc
{
  "content_bm25": "OpenSearch is fast and flexible for text search.",
  "content_raw": "OpenSearch is fast and flexible for text search.",
  "content_vector": [0.12, -0.34, ..., 0.56]  // 길이 768짜리 벡터
}

 

검색 시, Hybrid search 적용

POST my-index/_search
{
  "query": {
    "hybrid": {
      "queries": [
        {
          "lexical": {
            "field": "content_bm25",
            "query": "flexible text engine"
          }
        },
        {
          "semantic": {
            "field": "content_vector",
            "query": "flexible text engine",
            "model_id": "my-huggingface-model"
          }
        }
      ]
    }
  }
}