학습일지/AI

LangChain Meetup - R.A.G 우리가 절대 쉽게 결과물을 얻을 수 없는 이유

inspirit941 2024. 6. 17. 15:11
반응형

R.A.G 우리가 절대 쉽게 결과물을 얻을 수 없는 이유

https://youtu.be/NfQrRQmDrcc?si=kWmsM0cfv02ddpak

 

스크린샷 2024-06-16 오후 12 12 57

 

RAG을 위한 문서 전처리 방법...

  • Document Load
  • Split
  • Embedding
  • Vector Store
  • Retriever

각각의 과정마다 선택할 수 있는 종류가 너무 많음. 이것들을 조합하면서 경험했던 내용을 공유하는 발표.

Document Loader

스크린샷 2024-06-16 오후 12 15 41

 

다양한 종류의 데이터를 지원하지만 보통 csv, Excel, PDF.

  • Langchain은 load()를 인터페이스화해서, 어떤 document loader 객체라도 파일 로드할 때 load()함수 쓰면 되도록 했음

스크린샷 2024-06-16 오후 12 17 22

 

고려해야 했던 점들

  • 데이터 원형 그대로 잘 가져오는가?
    • 한글 인코딩 / 특수문자 같은 거
  • 어떤 metadata를 제공하는가? -> 본문 외적의 내용도 가져올 수 있는가.
    • page_content: 문서내용
    • page: 페이지 번호
    • 표, 차트, 문서의 coordinate (element의 좌표 - 위치 정보), 속성값 (title, table, image, text) 태깅해주는지
  • 문서 읽는 속도는?
    • 속도가 느리면, 많은 문서를 DB에 쌓는 데 오래 걸린다.
    • i.e. 실시간으로 읽어서 요약만 할 거라면, 읽는 속도가 중요함. batch로 넣어두고 나중에 출처표기하는 등의 기능이 필요하다면, 속도보다는 Metadata 지원 여부가 중요하다.

fitz

스크린샷 2024-06-16 오후 12 20 43

 

PDF loader. 단순하게 텍스트만 읽어서 합치기 위한 용도

  • 속도는 가장 빠름, 단 간혹 문장이 잘려서 읽히는 경우가 있음
  • metadata는 page 번호 말고 지원하지 않음.
  • 업로드해서 실시간으로 요약본 받아봐야 하는 형태의 작업에 유용함

PyPDFLoader

스크린샷 2024-06-16 오후 12 22 37

 

  • 평균적으로 우수한 편. 한글인코딩 문제 없고, 속도 적당하고, metadata도 제공해준다.
  • page단위로 데이터 로드.
  • metadata로는 source: 파일명, page: 페이지 번호. 두 가지 제공

UnstructuredPDFLoader

스크린샷 2024-06-16 오후 12 24 05

 

  • 가장 많은 종류의 metadata 제공
  • component별 load 기능을 제공한다.
    • title, 표, 이미지 등... 의 속성값.
    • coordinates 좌표값 / layout 정보 제공
  • 그래서 속도 느림

PDFPlumber

스크린샷 2024-06-16 오후 12 25 48

 

  • 한글인코딩 잘하고, 다양한 metadata 포함
    • total_page를 제공함. 안주는 경우도 있어서 이거 있으면 편함
    • ModDate: 수정 날짜도 제공함.
    • metadata 제공하는 측면에서는 훌륭함.
  • 단, 속도 느림.

Text Splitter

스크린샷 2024-06-16 오후 12 27 38스크린샷 2024-06-16 오후 12 28 12

 

예시 문장에서 'Retrieval Augmented Generation' 이라는 문장이 소제목이라고 한다면, 이 문장은 분할 없이 원문 그대로 가져오는 게 굉장히 중요하다.

  • 이 소제목과 관련된 내용이 하위에 계속 이어지기 때문.
  • 이걸 text Splitter가 어떻게 가져오는지 체크해보자.

CharacterTextSplitter

스크린샷 2024-06-16 오후 12 29 32

 

분할 가능한 최소 단위로 분할 시도한다. 공백 or '.' 단위.

  • 예시의 경우 문장 단위로 분할. 글자가 누락되는 경우는 없지만, 문장이 완성되지 못하고 잘리는 경우가 더러 있다.
  • 소제목을 원문 그대로 들고오지 못하고 잘라낸 것을 볼 수 있음.
    • chunk_overlap 옵션으로도 완벽히 해결할 수 있는 문제가 아니다.
  • 그래서 굳이 권장하지 않음.

RecursiveCharacterTextSplitter

스크린샷 2024-06-16 오후 12 31 34

 

  • chunk가 충분히 작아질 때까지 재귀적으로 분할 시도.
    • 분할 시도 순서: 단락 -> 문장 -> 단어.
    • 그래서 소제목이 끊기지 않고 가져오는 효과가 있음. (단락으로 인식해서 살려둔 것)
  • 텍스트에서 context가 유지되는 순서대로 분할을 시도하므로 (단락 -> 문장 -> 단어), 범용적으로 쓸만함.

TokenTextSplitter

스크린샷 2024-06-16 오후 12 33 31

 

한글은 글자 단위로 토큰이 되지 않는 문자이므로, 한글깨짐 현상 발생

  • KonlpyTextSplitter를 쓰면 한글깨짐 문제는 해결됨. (Kkma 형태소 분석기 기반)
    • text splitting 시간이 오래 걸리는 편.
    • 정보 정확성이 우선시되고, 언어분석이 필요한 애플리케이션의 경우 쓸만함.

기타 오픈소스 (HuggingFace)

스크린샷 2024-06-16 오후 12 35 21

 

다양한 tokenizer 사용 가능. 신조어 or 특정 도메인 용어를 fine tuning하거나 add_vocab할 수 있다.

  • BPE가 요즘 많이 회자되는 편.

SemanticChunker (experimental)

스크린샷 2024-06-16 오후 12 36 26

 

langchain Experimental에 새로 추가된 chunker.

  • 텍스트를 의미 유사도에 따라 분할했음.
  • chunk_size, chunk_overlap 같은 파라미터를 받지 않는다.
  • Embedding을 지정해줘야 한다.

의미 파악해서, 비슷한 의미끼리 묶어서 분할하는 방식... 분할 결과물의 text size를 통제할 수가 없다. 길이가 제각각임

Embedding

스크린샷 2024-06-16 오후 12 43 48

 

Vector Space에서 '가장 유사한 텍스트를 찾아내는' Semantic Search 작업을 수행할 수 있음.

  • 문서에 적합한 embedding model + 한글처리까지 잘 되는 걸 고민해야 함.

Embedding의 실행 시점은 두 곳. 둘 다 같은 Embedding model을 써야 semantic search에 문제가 없다.

  • Document: PDF 같은 문서를 사전에 embedding + DB에 저장
  • Query: 사용자의 입력값을 변환

OpenAIEmbedding 함수를 별다른 옵션 없이 default로 생성할 경우, default로 설정된 모델이 버전업되면서 바뀔 수 있다.

  • 이 경우, DB에 저장할 때 사용한 모델과 사용자의 입력값을 변환할 때 사용한 모델이 달라질 수 있다.

OpenAIEmbedding

스크린샷 2024-06-16 오후 12 47 54

 

많이들 쓰는 기본모델이자 유료 Embedding. api로 embedding하는 거라 인프라 부담이 없으며, 한글성능도 괜찮음.

  • 가격이 비싸다고는 볼 수 없지만, 호출할 때마다 돈 나가는 건 생각해야 함.

CacheBackedEmbeddings

스크린샷 2024-06-16 오후 12 51 07

 

호출하는 족족 돈이 나가는 OpenAIEmbedding 방식과 달리, embedding을 임시로 캐싱해서 다시 계산할 필요가 없도록 한 것.

  • 한 번이라도 임베딩했다면, 동일한 문서에 한해서는 embedding을 수행하지 않는다.
    • 로컬에 캐시파일 생성하고, 캐시 hit할 경우 캐시결과를 사용하는 식.
    • 로컬캐시 결과물이 많을 경우 조회할 때도 3~4초까지 소요되는 경우가 있음 (0.00초 수렴한다고 보장할 수는 없다)

스크린샷 2024-06-16 오후 12 54 13

 

  • OpenAIEmbedding + CacheBackedEmbedding을 쓸 경우, 동일한 문서를 여러 사람이 조회한다 해도 추가비용이 나가지 않는다
    • namespace 지정하면, 모델별로 cache 관리하는 것도 가능하다.

MTEB (Massive Text Embedding Benchmark)

스크린샷 2024-06-16 오후 12 54 51

 

다양한 오픈소스 모델의 benchmark 점수를 비교할 수 있는 지표.

  • 단, 한국어 쓸 거면 이 벤치마크 점수를 곧이곧대로 믿을 수는 없다. semantic search 해보면서 결과 비교해봐야 함.

스크린샷 2024-06-16 오후 12 56 11

 

  • 오픈소스 Embedding 모델 써서, 인프라 직접 구축하거나.
    • HuggingFace Inference API 가 있긴 한데 속도가 느림
  • OpenAIEmbedding 사용하거나.
    • 인프라 구축할 여력이 없고
    • 한글 정확도를 오픈소스 모델 테스트할 여력이 없을 때
    • 단, cache 반드시 적용해서 불필요한 과금 피해야 함

Vector Store

스크린샷 2024-06-17 오전 9 56 43스크린샷 2024-06-17 오전 9 58 35

 

langchain에서 지원하는 종류만 80여 개. 선택 기준도 상황에 따라 다름.

  • 로컬 vs 클라우드?
  • 데이터 조회 / 업데이트 빈도?
    등등..

FAISS

스크린샷 2024-06-17 오전 9 59 32

 

Meta에서 주도적으로 개발하는 오픈소스. 로컬 DB 세팅한다면 안정성 면에서 가장 나은 선택지

전처리

풀기 아까운 노하우지만, 공유하면 커뮤니티가 발전한다는 생각에 공유함.

페이지 단위 분할

스크린샷 2024-06-17 오전 10 01 23

 

전체 문서를 페이지 단위로 분할할 때, 필요한 metadata 정보는 태깅한다.

  • 페이지 번호, 파일명: LLM응답의 신뢰도 확보
  • MOD: 최신 정보인지 체크할 용도. metadata로 필터링할 때 유용함
  • Author: 누가 썼는지
  • 각 문서에서 키워드 추출해서 tagging할 수 있다면 더 좋다.

스크린샷 2024-06-17 오전 10 19 12

 

필요한 영역만 사용한다.

  • PDF의 header나 footer의 정보가 vector 검색 시 포함되는 경우가 있음.
    • 예컨대 PDF footer의 Licensed to: <사람 이름> ... (London) 항목의 경우, 문서의 'London 시장 이슈' 검색했을 때 포함되는 경우가 있다. London 문자열에 포함되어 있기 때문.

스크린샷 2024-06-17 오전 10 28 42스크린샷 2024-06-17 오전 10 28 50

 

PDF 문서 구성방식에 따른 영향

  • 일반적인 pdf 파싱 방식은 1 row. 문서가 1:1 분할 또는 1:2 분할 구성으로 되어 있다면, 분리된 열을 건너뛰기 때문에 문장 순서가 전부 꼬인다.

스크린샷 2024-06-17 오전 10 30 51스크린샷 2024-06-17 오전 10 30 56

 

PDFPlumber Bounding Box 기능을 활용한다.

  • page에 crop 수행
  • 왼쪽 먼저 다 읽고, 오른쪽 문서를 읽은 뒤 합친다.

스크린샷 2024-06-17 오전 10 43 09스크린샷 2024-06-17 오전 10 43 45

 

PDFminer: 자동으로 인식해서 해준다

  • 단, 차트나 표 안의 텍스트를 '표 / 차트' 구성항목이 아니라 그냥 텍스트로 인식할 수 있음.
    • 사전에 표 / 그래프를 제거하거나
    • 한 줄에 글자수 N개 이하일 경우 무시하는 식으로 작업

표 추출

스크린샷 2024-06-17 오전 10 44 58스크린샷 2024-06-17 오전 10 46 01

 

  • 오픈소스: camelot, PaddleOCR. 관련 표를 추출해주는 기능
  • PDFPlumber의 표 추출 기능 활용.
    • table_setting의 여러 옵션값을 활용하면, 적절한 표를 추출할 수 있음.

Chunk Overlap?

스크린샷 2024-06-17 오전 10 47 13

 

문서마다 다양해서 정답은 없지만, 기본적으로는 "여유있게 설정한다"

  • 이전 page의 마지막 부분 / 다음 page의 앞부분은 병합 처리한 뒤 Semantic Chunker (의미단위 분할)를 사용할 수도 있음.
    • 단, 이 경우 page번호 출처를 어디로 할 것인지가 애매함
  • 한 문서에 chunk size나 overlap을 여러 개 적용헤서 retriever 여러 개 만든 뒤 ensemble. 여러 retrieval 결과를 Union 처리한다.

이미지 추출

스크린샷 2024-06-17 오전 10 50 13

 

fitz 사용. 이미지 추출하거나 태깅이 필수인 경우를 생각해본다면...

  • 커머스 관련 상품 (상품이미지)
  • 그래프 / 차트가 이미지로 삽입된 형태

Retriever

Vector Store에 query -> 결과물 받아오는 것. 이것도 종류가 여러 가지 있음.

Multi-Query Retriever

스크린샷 2024-06-17 오전 10 51 58

 

Query 문구가 미묘하게 다른 경우라거나, Embedding이 텍스트의 의미를 제대로 파악하지 못하는 경우

  • 이건 사실 사용자의 검색 패턴과 관련이 있음. 키워드 기반 검색에 익숙해져 있는데, 이건 semantic search 방식과 부합하지 않는다.
  • LLM으로 사용자의 Input을 paraphrase -> 비슷한 여러 쿼리를 생성해서, 결과의 Union 값을 반환한다.

Ensemble Retriever

스크린샷 2024-06-17 오후 2 41 03

 

특정 단어가 정확히 포함된 데이터만 검색해야 할 경우에도 Semantic Search는 성능이 좋지 않다.

  • 예컨대 '비타민A 영양제 추천해줘' -> '비타민' 이라는 단어와 유사도 높은 비타민C, 비타민D 등도 같이 검색됨.

스크린샷 2024-06-17 오후 2 45 11

 

일반적으로

  • 키워드 기반 검색이 필요하면... Sparse Retriever. 코드상으로는 BM25Retriever
  • 의미 검색이 필요하면... Dense Retriever. 코드상으로는 faiss_retriever

두 가지를 Ensemble해서 쓰는 게 Ensemble Retriever. 어느 정도 절충된 결과를 받을 수 있다.

  • 가중치로 weight 설정 가능.

Long Context Retriever

스크린샷 2024-06-17 오후 2 46 41스크린샷 2024-06-17 오후 2 48 51

 

보통 RAG로 정보를 가져온 다음, LLM에 유사도 높은 순서대로 정렬해서 포함한다.

  • 그런데, LLM은 중간 부분의 context를 무시하는 경향이 있다. context 앞쪽, 뒤쪽 문서를 응답할 때 주요 문서로 확인하는 식으로 동작함.
  • 그러면 LLM에 데이터 넣는 서순을 바꿔보자. 매우 중요한 것은 최상단, 그 다음으로 중요한 건 끝부분에 배치하는 식.

이걸 구현한 Langchain 객체가 LongContextReorder().

스크린샷 2024-06-17 오후 2 50 27

 

그 외에도

  • Ensemble에 필요한 Multi Vector Retriever
  • ContextualCompressos
  • LLMChainFilter

등과 같은 Retriever 구현체가 있음.

Prompt Engineering

스크린샷 2024-06-17 오후 2 51 33

 

Few-shot 방식을 권장. 명령하는 것보다 예시를 제공하는 게 효과적임.

  • 문자열로 입력하면 지저분하므로, yaml 등 별도의 파일로 빼서 관리하는 걸 추천한다.
  • langsmith hub에 prompt 업로드 + pull해서 사용할 수 있다. 이 경우 버전관리도 가능.

langchainHub에 있는 완성형 prompt 사용해도 좋다.

스크린샷 2024-06-17 오후 2 53 17스크린샷 2024-06-17 오후 2 55 49스크린샷 2024-06-17 오후 2 57 46

 

요약의 경우... 워낙 초반부터 니즈가 많았던 task라서 많은 방법론이 공개돼 있다.

  • Chain Of Density: 5번의 퇴고를 거쳐서 요약본을 수정해가는 것.
    • 1.Sparse summary 작성 (간략한 형태의 요약)
    • 2.Missing Entity (누락된 키워드 찾기)
    • 3.Missing Entity 포함해서 다시 요악
    • 위 step을 5번 반복한다.
  • 중요한데 누락되어 있던 키워드를 추가해가며 Denser Summary로 만드는 것.

왜 굳이 5번?

  • 논문에서 '5회 수행하면 인간의 summary보다 낫더라'고 밝혔음.

스크린샷 2024-06-17 오후 2 58 15

반응형