나는 메모를 좋아한다.

 

그냥 윈도우 메모장부터

스티커 메모

노션

다이널리스트

옵시디언 등 다양하게 사용 중이다.

 

옵시디언은 주로 정제된 지식들을 메모하는 데 사용하고 있었는데

최근에 너무 느려지고 폴더가 생성이 안 되는 등

별의별 이상한 이슈로 옵시디언을 접어두고 있었는데

 

퍼플렉시티에 서칭 한 결과 make.md 플러그인이 문제였고

이를 삭제 후 다시 설치하니 모든 문제가 해결되었다.

(구글 넌 나가있어 퍼플렉시티가 미래다.)

 

그래서 앞으로 옵시디언을 메인으로 사용하려고 한다.

 

본격적으로 메모를 하기 전에

항상 느끼던 메모의 권태를 깨기 위해 새로운 방법론을 도입했다.

PARA

라는 기법이다.

 

PARA Method는 Tiago Forte라는 아저씨가 만든 방법론인데.

 

Project

Area

Resource

Archive

의 앞글자를 따서 PARA다.

 

이건 내 노트

 

PARA 기법은 지금 당장 뭘 해야 하는지 직관적으로 다가와서 좋다.

 

기존 메모법은 시간이 지나면 까먹고 찾는 것도 어려운데

PARA는 매우 실용적이다.

 

그래서 어케 쓰는 건데

 

다음과 같이 분리해서 사용한다.

🎯 Projects (지금 하는 일)

📝 Areas (매일 관리)

📚 Resources (가끔 참고)

📦 Archives (끝난 것들)

 

 

 

PARA는 집 구조와 비슷하다:

 

1. Projects (프로젝트) = 지금 일하는 책상

  • 현재 진행 중인 일들만 올려두는 곳
  • "오늘/이번 주에 해야 하는 일" 전부

2. Areas (영역) = 매일 들여다보는 냉장고

  • 매일매일 관리하고 신경 써야 하는 것들
  • 자주 확인하고 업데이트가 필요한 것들

3. Resources (자료) = 주방 서랍

  • 필요할 때마다 꺼내 쓰는 것들
  • 지금 당장은 안 봐도 되지만 나중에 유용한 것들

4. Archives (보관함) = 창고

  • 다 끝나서 치워둔 것들
  • 혹시 몰라 버리긴 아깝지만 당장은 안 보는 것들
지금 하고 있는 일이면 → Projects (책상)
매일 봐야 하면 → Areas (냉장고)
나중에 쓸 거면 → Resources (서랍)
다 끝난 거면 → Archives (창고)

Projects: "지금 당장" 해야 하는 일
예) 이번 주 발표 자료, 이사 준비 체크리스트
Areas: "계속 신경 써야" 하는 일
예) 운동 기록, 가계부
Resources: "나중에 쓸 수 있는" 것들
예) 관심 있는 레시피들, 좋은 글귀 모음
Archives: "일단 보관" 해둘 것들
예) 끝난 프로젝트, 옛날 영수증

 

자세한 예시

🎯 Projects (지금 하는 일)
- 12월 회사 발표자료 만들기
- 이번 주말 제주도 여행 계획
- 이사 준비 체크리스트
- 연말 모임 장소 예약하기
- 1월 팀 예산안 작성

📝 Areas (매일 관리)
- 운동 기록 (매일 체크)
- 식단 관리 (매끼 기록)
- 업무 일지 (매일 작성)
- 가계부 (매일 지출 기록)
- 독서 일기 (읽은 책 기록)
- 건강 체크리스트 (수면, 약 복용)
- 주간 업무 계획표

📚 Resources (가끔 참고)
- 운동 방법 모음집
- 자주 가는 맛집 리스트
- 좋아하는 영화/도서 목록
- 업무 관련 참고자료
- 나중에 가볼 여행지 목록
- 집 인테리어 아이디어
- 유용한 회의 템플릿
- 요리 레시피 모음
- 쇼핑 위시리스트

📦 Archives (끝난 것들)
- 2023년 프로젝트 보고서
- 지난 여행 기록/사진
- 이전 회사 업무 자료
- 완료된 이사 준비 목록
- 끝난 계약서/영수증
- 옛날 일기/메모
- 지난 달력/스케줄
- 예전 집 사진

 

좀 감이 오나?

 

여기서 PARA의 진가는 메모의 위치들이 상황에 따라 계속 변경할 수 있다는 점이다.

 

우선순위에 따라서 메모의 저장 위치를 바꿔줄 수 있다.

 

초간단 분류 방법

  • 새로운 정보가 들어오면 딱 한 가지만 물어보면 됨.
    • 이거 지금 당장 필요해? → Projects
    • 아니면 꾸준히 봐야 해? → Areas
    • 나중에 쓸 수 있을까? → Resources
    • 다 끝난 거야? → Archives

가장 최우선순위이라면 Projects에 넣고

해결되면 다른 폴더에 집어넣으면 된다. 

 

CoolcoolCool

 

지금까지는 여러 가지 메모법을 찾아보고 적용해 봤지만

PARA 기법이 나에게 가장 잘 맞는 것 같다.

 

 

이쪽 선생님이 잘 정리해 뒀으니 본인 노트에 적용하고 싶으면 한번 보면 좋을 듯

 

암튼 하루에 1시간 이상은 옵시디언으로 아카이빙을 하는 시간에 투자해서

옵시디언과 더 친해질 예정이다.

 

수확자라는 소설을 읽고 있는데

이런 식으로 복잡한 인물관계를 옵시디언으로 직관적이게 메모해보고 있다.

(굳이? 싶지만 그냥 한번 해보는 거다.)

 

 

옵시디언 파이팅!

파라 파이팅!

AI 기초 시리즈다.

 

바로 간다 첫 번째

 

임베딩(Embeddings)



임베딩이 뭐임?

 

일단 영어로의 뜻

  1. 기본 뜻
  • embed (동사): 꽂아 넣다, 박다, 묻다, 깊이 새기다
  • 예: embed a nail in the wall (못을 벽에 박다)
  1. 파생된 형태
  • embedding (동명사/명사): 박아 넣기, 끼워 넣기
  • embedded (형용사): 박혀있는, 내장된

보다시피 그냥 박아 넣는다는 뜻이다.

 

뭐를요?

 

벡터요

 

벡터는 뭐임?

 

벡터를 모르면 임베딩 이해 불가능

 

숫자들을 모아둔 게 벡터임.

그리고 그 숫자들이 결국 하나의 정보를 표현함.

 

간단하게 


 

2차원 지도. 좌표 [ 1, 1 ]에 피자집이 있다고 하면

 

[ 1, 1 ]이라는 숫자가 표현하는 

하나의 정보 = 피자집 이 되는 거임

 

그리고 많이 들어봤을 벡터 DB

여기서 2차원 지도임.

 

 


수많은 좌표가 들어있는 게 2차원 지도이듯

수많은 벡터가 들어있는 게 벡터 DB임.

 

지금은 2차원을 예시로 들어서 [1,1]이지만

사실 벡터기본으로 몇백 차원이 넘음

그러면 숫자가 [1,1,1,1,1,1,.....] 이렇게 몇백 개가 생기는 거임.
(실제로는 1이 아니라 여러 가지의 실수임) 

 

차원의 수와 숫자의 개수가 정확히 1대 1 대응함.

사실 너무나 당연한 말임.

 

3차원에서 숫자 한 두 개로 어떻게 정확한 위치를 표시하겠음.

3개 초과되는 숫자도 마찬가지.

 

다시 임베딩으로 돌아와서.

 

임베딩

 

고차원 데이터(텍스트, 이미지 등) -> 변환 -> 저차원 벡터

이 과정이 임베딩 끝임.

 

텍스트(이미지, 오디오 등)를 숫자로 표현하는 거임.

 

그래서 이거 왜 하는 건데요.

 

왜냐면 컴퓨터는 지능이 없음. 빡통임.

컴퓨터는 텍스트의 "의미"를 이해 못 함.

 

임마는 숫자만 잘 다룸.

 

강아지비슷한 뜻인걸 컴퓨터는 모름.

 

그래서 이걸 숫자로 바꿔서 컴터한테 알려주는 거임.

 

어떤 식으로 알려줌?

 

대충 가까운 값이면 비슷한 의미임.

강아지는 벡터값이 가까움.

정마담은 벡터값이 멈

 

그걸 어떻게 판별함?

 

 

1. 코사인 유사도 (Cosine Similarity) 기법

 

아래 예시를 보자.

"개" [0.2, 0.5]
"강아지"  [0.21, 0.48]  -> 유사도 0.99 (매우 비슷!)
"정마담" [-0.4, 0.1]   -> 유사도 0.1 (많이 다름)

 

이는 코사인 유사도 (Cosine Similarity) 기법으로 유사도를 측정한 거임.

 

벡터 간의 각도를 측정하는 기법임.

값이 1에 가까울수록 유사하다는 거임.

값이 -1에 가까우면 정반대라는 뜻.

 

0 이면 그냥 관계없다는 뜻임.

 

2. 유클리드 거리

 

아래 예시를 보자.

점 A: [1, 2]
점 B: [2, 2]  -> 거리 1 (가까움)
점 C: [5, 5]  -> 거리 5 (더 멈)

 

이건 유클리드 거리 

 

실제 물리적 거리처럼 벡터 간의 가깝고 멀고를 계산함

당연히 값이 작을수록 가까운 의미임.

 

피타고라스 정리를 고차원으로 확장한 개념이라고 함.

 


대충 이런 식으로 벡터 간의 멀고 가까움을 측정하고 판별함.

 

암튼 이런 식으로 유사도와 거리를 통해.

단어 간의 맥락을 컴퓨터가 찾을 수 있게 된 거임.


 

결론

 

임베딩 하는 이유 :

 

컴퓨터는 빡통이라 의미를 몬알아먹음.

그래서 알아들을 수 있게 숫자로 변환해줘야함.

 

+

차원의 수는 누가 결정하나요?

 

임베딩 모델이 결정함.

특정 임베딩 모델은 항상 같은 차원을 뱉음.

 

OpenAi text-embedding-ada-002 모델 : 1536차원

BERT 모델 : 768차원 

 

이런 식임.

ㅎㅇ 2번째 코드 분석 글이다.

 

이전 글을 읽고 오면 더 이해하기 쉽다.

 

전체적인 코드 흐름에 관한 글. 0번

https://min-c-max.tistory.com/entry/Chroma-LangChain-Tutorial-크로마-랭체인-튜토리얼-라그-RAG

 

Chroma LangChain Tutorial (0) 크로마 랭체인 튜토리얼 라그 RAG

벡터 스토리지를 사용해야 되는 일이 생겼다.그래서 이것저것 해보고 있다. Chroma Doc를 읽으며 여러 가지 코드들을 실행해 보며 이해해보고 있다.나처럼 허우적 되는 사람을 위해 내가 이해한

min-c-max.tistory.com

 

 

wikipedia.py에 관한 글. 1번

https://min-c-max.tistory.com/entry/Chroma-LangChain-Tutorial-크로마-랭체인-튜토리얼-라그-RAG-코드-분석-1

 

Chroma LangChain Tutorial (1) 크로마 랭체인 튜토리얼 라그 RAG 코드 분석

https://min-c-max.tistory.com/entry/Chroma-LangChain-Tutorial-%ED%81%AC%EB%A1%9C%EB%A7%88-%EB%9E%AD%EC%B2%B4%EC%9D%B8-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EB%9D%BC%EA%B7%B8-RAG Chroma LangChain Tutorial 크로마 랭체인 튜토리얼 라그 RAG벡터

min-c-max.tistory.com

 

 


이번 코드 분석은 ask_wikipedia.py 다.

Chroma와 LangChain이 사용된 코드다.

 

바로 분석해 보자.

코해분


from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.llms import OpenAI
from langchain.chains import RetrievalQA
from typing import List
from langchain.schema import Document
import os

 

여기는 임포트부터 뭐 처음 보는 것들이 많다.

 

일단 랭체인이 뭔지부터 알아보자.

 

LangChain(랭체인)

 

랭체인은 사실 그냥 LLM을 가지고

어플리케이션을 개발하는 프레임 워크임
(안드로이드 , ios 앱 말고 더 큰 의미의 어플리케이션)

 

대표적인 기능은 이런 게 있음

 

  • 프롬프트 관리: 효과적인 프롬프트를 작성, 최적화, 재사용하는 도구를 제공.
    • langchain.prompts
  • 체인: 여러 LLM 또는 다른 구성 요소를 연결하여 복잡한 작업을 수행.
    • langchain.chains
  • 에이전트: LLM을 의사 결정 엔진으로 사용하여 작업을 자동으로 수행.
    • langchain.agents
  • 메모리: 대화나 작업 간의 정보를 유지하고 관리.
    • langchain.memory
  • 인덱서와 벡터 저장소: 대규모 데이터셋에서 관련 정보를 효율적으로 검색.
    • langchain.vectorstores, langchain.embeddings

이렇게 5개가 대표적인 기능임

그리고 노란색으로 칠해둔 애들은 이번 코드에 사용되는 애들임

 

근데 import문을 자세히 본 사람은 눈치챘겠지만

점마들 말고도 훨씬 많은 모듈을 씀.

 

+α

 

  • 문서 로더 : 다양한 형식의 문서를 로드하고 처리함.
    • langchain.document_loaders
  • 텍스트 분할기 : 긴 문서를 더 작은 청크로 분할함.
    • langchain.text_splitter
  • 언어 모델 : 다양한 왕짱큰 언어 모델(LLMs)을 사용할 수 있게 함.
    • langchain.llms
  • 스키마 : LangChain의 기본 구조를 정의함.
    • langchain.schema  
    • 이건 좀 다른 녀석들에 비해 설명을 들었을 때 이해가 직관적이지 못함.(내가 그랬음)
    • 쉽게 말하면  LangChain의 "설계도"라고 생각하면 됨.
    • '문서'가 뭔지, '언어 모델'이 어떤 기능을 해야 할지 정의함.
    • 새로운 기능을 추가할 때 기존 기능들과 잘 호환되도록 규칙을 제공하는 역할도 함.

지금은 이 정도로만 가볍게 알고 자세한 건 추후 설명

 

근데 렝체인 뒤에 _community가 붙어 있는데 이건 머임?

 

LangChain Community 

 

요거는 걍 랭체인 커뮤니티에서 관리하는 통합 패키지임

주로 서드파티 도구, API, DB 등과의 연동을 담당함.

우리가 쓰는 Chroma같은게 서드파티 도구임.

요런거 쓸라믄 커뮤를 써야한다는것.

 

실험적이고 새로운 기능들이 먼저 이곳에서 많이 시도됨.

 (LangChain의 카카오톡 실험실 ㄷㄷ)

커뮤니티에서 검증된 기능들이 나중에 핵심 Langchain 패키지로 이동되기도 함.

 

암튼 이런 특징 때문에 더 많은 외부 서비스, 도구, 데이터베이스 등과의 통합을 제공하게 됨.

 

그래서 이 코드에서는 더 최신 기능 더 다양한 기능을 위해 커뮤니티 모듈을 사용함.

 


클래스 알아보기

from langchain_community.document_loaders import TextLoader

 

 

위에 언급한 문서 로더 기능을 사용하기 위한 준비 단계다.

  • langchain_community.document_loaders

랭체인 뒤에 커뮤니티가 붙었다는 점만 다름.

 

TextLoader

document_loaders 모듈에서

TextLoader라는 특정 클래스를 '좀 쓸게요' 하고 가져옴.

 

인마는 텍스트 파일을 읽어 들이는 기능을 함.

 

예시 코드

from langchain_community.document_loaders import TextLoader

# 텍스트 파일 경로
file_path = "example.txt"

# TextLoader를 사용하여 파일 로드
loader = TextLoader(file_path)

# 문서 내용 읽기
documents = loader.load()

# 문서 내용 출력
print(documents)

 

loader = TextLoader(file_path)

 

이 단계는 loader안에 여러 가지 정보를 담음.

파일 경로나 뭐 인코딩 방식 같은 거 

 

아마 객체의 내용을 시각화하면 이렇게 생겼을 거임

loader = {
    'file_path': '2024-10-16_17-20-28.txt',
    'encoding': 'utf-8',
    # 블라블라...
}

documents = loader.load()

여기가 진짜 텍스트를 읽는 단계임

이 작업을 하면 documents에 다음과 같은 documents 객체가 들어있을 거임.(출력 시)

documents = [
    Document(
        page_content="파일의 전체 내용...(매우 긴 문자열)",
        metadata={
            "source": "2024-10-16_17-20-28.txt"
        }
    )
]

 

documents(변수)는 리스트인데 그 안에

단 하나의 Document 객체가 포함되어 있을 거임. (적어도 우리가 분석하는 코드에서는)

 

Document 객체가 먼디용?

 

Document는 LangChain 라이브러리에 정의된 클래스임.

from langchain.schema import Document

 

우리가 여기서 임포트 해옴.

근데 안해도 작동 잘할거임 (내 코드 아님)

 

from langchain.schema import Document

class Document:
    def __init__(self, page_content: str, metadata: dict = None):
        self.page_content = page_content
        self.metadata = metadata or {}

 

이렇게 생김.

document.page_content 이런 식으로 쓸 수 있는 거임.

 

정리

 

TextLoader

얘는 그냥 파일 읽을 준비만 하는 클래스

경로랑 인코딩 정보만 저장하고 실제로 파일 안 읽음

 

load() 인마가 진짜로 파일을 읽는 메소드

(TextLoader의 인스턴스 메소드.)

촤라라락 읽고 읽은 내용을 Document 객체로 변환

 


from langchain_community.vectorstores import Chroma

 

위에 설명해서 이제 이해가 쉬울 텐데

langchain_community.vectorstores 라는 모듈에서

Chroma를 쓰겠다는 거임

 

그렇다는 건 당연히 LangChain에서 지원하는

다른 벡터 DB도 쓸 수 있다는 뜻! (벡터 DB 개념은 바로 밑에 설명)

 

짧게 살펴보고 가자면 이런 게 있다

 

  • Chroma: 간단하고 빠르게 설정할 수 있어 프로토타이핑에 좋음.
  • FAISS: 대규모 데이터셋에서 빠른 검색이 필요할 때 유용함. (페북 AI에서 만듦)
  • Pinecone: 클라우드 기반 설루션이 필요하고 확장성이 중요할 씀.
  • Qdrant: 고성능이 필요하고 복잡한 필터링 기능이 필요할 때 씀.
  • Weaviate: 그래프 데이터 구조와 벡터 검색을 결합하고 싶을 때 유용함.
  • Milvus: 대규모 분산 시스템에서 벡터 검색이 필요할 때 씀.

우리도 간단한 설정과 빠른 프로토타이핑을 위해

Chroma를 공부하는 거임.

 

이 코드 만든 아저씨도 그 목적으로 쓴 거일 테고

 

FAISS랑 Chorma는 인터페이스가 유사해서

모델만 바꿔 끼우면 쉽게 교체 가능 할지도?

(하고 싶은 사람 라이브러리 설치하고 실행 후 후기점.)

 

결론 : Chroma는 빠르다

 

잠만 근데 벡터 DB가 머임?

 

벡터 DB

벡터는 숫자들의 리스트임.

[1,2,3] 뭐 막 일케 생겼음.

 

인공지능에서는 텍스트나 이미지 같은 복잡한 데이터를

저 위에 예시처럼 숫자 리스트로 표현함.

 

그래서 저런 애들이 모여있어서 벡터. 데이터베이스. 임

 

그냥 텍스트 파일에서 정보 찾으면 안 됨? 왜 저렇게 변환함? 

 

1. 일단 속도부터 차이가남.

 

예를 들어 :

1GB 크기의 텍스트 파일에서 정보를 찾는 데 몇 분이 걸릴 수 있지만,

벡터 DB를 사용하면 같은 양의 데이터에서 몇 초 만에 찾을 수 있는겨.

 

근데 그냥 속도만 빠르다고 쓰는 게 아님.

 

2. 의미 기반 검색

벡터 DB는 검색을 할 때 crtl + f 로 키워드 찾듯이 정보를 찾는 게 아님.

훨씬 지능적이게 검색을 함.

 

예를 들어 :

"지구 온난화의 영향" 을 검색한다면

"기후 변화로 인한 해수면 상승" 같은 관련 정보도 찾아옴.

 

키워드가 정확히 매칭되지 않아도 문맥을 고려해서 찾아옴.

 

3. 유사성 측정

벡터 DB는 정보 간의 유사성을 수치화함.

 

뭔 말이고?

 

"사과의 영양성분" 이라는 질문에 대해

 

가장 관련 높은 정보부터 순서대로 

  1. 사과의 비타민 함량
  2. 사과의 식이섬유
  3. 과일의 일반적인 영양가

요로코로미 찾아온다는 거임.

 

4. 다차원 데이터 처리 

텍스트가 아닌 이미지나 소리를 검색해와야 하면 어칼 거임.

 

벡터 DB는 이미지, 소리도 컴터가 이해하는 방식으로 저장을 할 수가 있음.

 

결론:

벡터 DB를 쓰면 더 지능적이고 효율적인 정보 검색 처리가 가능함.

특히 데이터가 킹왕짱 커지거나 복잡한 질문을 답할 때 매우 유용해짐.

 


from langchain_community.embeddings import OpenAIEmbeddings

 

from은 이제 다 알 테니 생략하고

 

먼저 embeddings가 뭔지 알아보자

 

임베딩(embedding)

 

위에 언급했듯이

 

데이터들은 벡터 DB에 들어갈 때

숫자로 변환되어 벡터가 되어 들어감.

 

데이터 -> 벡터(숫자) -> 벡터 DB

 

근데 여기서 생각해봐야 할게

누가 데이터를 벡터로 바꿔주냐 이거임.

 

임베딩 모델 : 저요

 

ㅇㅇ 임베딩 모델이 그걸 하는 놈임.

 

얘가 대량의 데이터로 학습되어서 데이터의 의미와 관계를 포착함.

그리고 그걸 숫자로 표현하는 거임.

 

여기서 하나 더 알고 가면 좋은 점 있는데

 

이 벡터들은 의미나 관계를 다차원 공간상의 위치로 나타냄.

 

ㄷㄷ 뭐소리고 

 

대충 후려쳐서 말하면

단어끼리 의미가 가까우면 서로 가까운데 위치함.

 

쉬운 예시로 ㄱ

 

3차원 공간이 있다고 생각해 보자

 

[2,3,1] 이런 좌표가 있음.

이게 벡터임 

 

계란말이라는 단어가 임베딩되어 벡터 [2,3,1] 된 거임

 

그럼 이 벡터 [2,3,1](계란 말이)

3차원 공간에서 좌표

x=2

y=3

z=1 

위치에 있는 거임 

 

근데 갑자기 계란찜이 임베딩 되어서 들어옴.

그럼 임마는 벡터 [2,3,1](계란 말이)와 가까운

x=3

y=3

z=1

계란찜 [3,3,1]쯤에 위치하게 되는 거임

 

둘 다 계란으로 만들었고 음식이니깐 유사성이 높아서 가까운데 위치하게 됨.

 

결국 벡터에 들어가 있는 숫자들은

데이터의 특성을 고차원 공간에서 표현하는 수치들이라고 보면 됨

 

이 차원은 천차만별임 GPT-3는 무슨 12,000 이상 차원까지 사용하고

몇백 따리차원도 있고 그럼.

 

근데 재밌는 건 인간이 각 차원에 대해서 정확한 의미는 해석할 수 없음

 

일단 12,000차원 이상의 공간을 직관적으로 이해하기 어렵고

 

임베딩 모델인간이 명시적으로 정의한 규칙이 아니라

 

블랙박스 안에서 데이터 지지고 볶으면서  꼬운 대로

학습한 패턴을 기반으로 작동해서 그럼

 

이게 인터레스팅 하다면

AI의 '설명 가능성(explainability)' 문제를 찾아보도록


 

다시 코드로 돌아와서 

from langchain_community.embeddings import OpenAIEmbeddings

 

임베딩을 아니깐 이제 이 코드는 그냥 귀요미 중에 귀요미 코드.

embeddings 패키지에서  OpenAIEmbeddings 모델 꺼내 쓰겠다는 거임.

그게 끝임.

 

참고로 이런 애들도 쓸 수 있

  • HuggingFaceEmbeddings (킹깅갓이스)
  • VertexAIEmbeddings (구글이 만듦)
  • CohereEmbeddings (다국어 처리 잘함)
  • TensorflowHubEmbeddings

참고로 OpenAIEmbeddings 얘는 유료임.

API 키 필요함

 

참고로 OpenAI는 비영리 단체임.

(비영리 단체도 수익산업 가능함. 임마들이 증거임 (실제로 가능함 ㅋ)


from langchain.text_splitter import RecursiveCharacterTextSplitter

 

text_splitter 에서  RecursiveCharacterTextSplitter 를 꺼내온다.

 

text splitting

걍 말 그대로 텍스트 분할임.

 

RecursiveCharacterTextSplitter

ㄹㅇ 길다 이름

번역하자면 재귀적 문자 텍스트 분할기인데.

 

임마가 어떤 식으로 동작하냐면 ( split_text 메소드 기준)

 

1. 먼저 전체 텍스트를 구분자(줄바꿈, 마침표 같은 거)를 기준으로 크게 나눔.

2. 그리고 이게 설정한 최대 길이(chunk_size)를 초과하면 또다시 더 작은 구분자로 나눔.

    이 과정이 재귀적이라 Recursive 붙은 거임

3. 모든 부분이 지정된 길이 이하가 될 때까지 계속함.

 

쉬운 예시를 인공지능에게 짜달라고 해봤음

 

청크 사이즈가 100일 때의 출력

청크 1 (56자): 1. 인공지능은 컴퓨터가 인간의 지능을 모방하는 기술입니다.
--------------------------------------------------
청크 2 (75자): 2. 머신러닝은 데이터로부터 학습하여 성능을 향상시키는 AI의 한 분야입니다.
--------------------------------------------------
청크 3 (62자): 3. 딥러닝은 인간 뇌의 신경망을 모방한 알고리즘을 사용합니다.
--------------------------------------------------
청크 4 (67자): 4. 자연어 처리는 컴퓨터가 인간의 언어를 이해하고 생성하는 기술입니다.
--------------------------------------------------

 

청크 사이즈 50일 때의 출력

청크 1 (48자): 1. 인공지능은 컴퓨터가 인간의 지능을 모방하는
--------------------------------------------------
청크 2 (8자): 기술입니다.
--------------------------------------------------
청크 3 (49자): 2. 머신러닝은 데이터로부터 학습하여 성능을 향상시키는
--------------------------------------------------
청크 3 (26자): AI의 한 분야입니다.
--------------------------------------------------
청크 4 (50자): 3. 딥러닝은 인간 뇌의 신경망을 모방한 알고리즘을
--------------------------------------------------
청크 5 (12자): 사용합니다.
--------------------------------------------------
청크 6 (49자): 4. 자연어 처리는 컴퓨터가 인간의 언어를 이해하고
--------------------------------------------------
청크 7 (18자): 생성하는 기술입니다.
--------------------------------------------------

 

직관적이다 그죠잉.

 

암튼 이런 식으로 텍스트 나누는 재귀뭐시기 클래스 가져다 쓴다는 코드임.

 

근데 데이터를 왜 나누는 거임? 청크는 또 머임;;;

 

청크(chunk)

말 그대로 덩어리임.

근데 데이터 분야에서 청크 '관리 가능한 부분', '논리적 단위'를 나타냄.

 

"예림이 그 패 봐봐 혹시 장이야?"

 

이거를

예림이 / 그 패 봐봐 / 혹시 / 장이야?

이렇게 나누면 청크 나눈 건데

 

예/ 림이그 / 패봐 /봐혹 /시 / 장이 /야? 

이렇게 나누면 청크로 나눴다고 보기 어렵다는 거임.


 

데이터를 나누는 이유

 

1. 모델이 입력 제한이 있는 경우가 있음.

 

토큰 제한이 있어서 긴 문서를 못 넣는 경우임.

그걸 그냥 나눠서 박아버리는 거임.

 

2. 메모리 효율성

 

청크로 나눠서 넣으면 메모리 사용이 최적화됨.

 

3. 병렬 처리

텍스트를 청크로 나눠서 동시에 처리시키는 거임.

 

그럼 시간 단축 ㄱㅇㄷ

 

30분 카레를 3분 카레 10개로 나눠서 전자레인지 10개에 돌리는 거임.

 

4. 정보 검색 개선

문서를 작은 청크로 나누면 특정 정보를 더 정확하고 빠르게 검색함

 

계란말이라는 단어를 찾아야 할 때

백과사전 다 뒤져서 단어 찾는 거보다.

백과사전을 ㄱ / ㄴ / ㄷ 이런 식으로 찢어놓는다면

그냥 ㄴ 청크 들고 가서 거서 찾으면 더 빠름.

 

5. 컨텍스트 유지

 

RecursiveCharacterTextSplitter와 같은 고급 분할기를 사용하면

의미 있는 단위(예: 문단, 문장)로 텍스트를 나눌 수 있어

각 청크 내에서 컨텍스트를 유지할 수 있음.

 

이러면 예림이가 패를 볼 수가 있음

아귀가 말리겠지만.

 


from langchain_community.llms import OpenAI

 

걍 OpenAI 모델 쓴다는 거

 

이것도 뭐 당연히 여러 가지 모델 쓸 수 있음.

 

Claude도 쓸 수 있고 뭐 라마도 쓸 수 있음.

 

llm = OpenAI(model_name="킹왕짱좋은모델")

 

참고로 이런 식으로 모델 선택 가능함.

gpt-4o 나 mini 같은 거 아마

아무것도 안 적으면 gpt-3.5-turbo-instruct 이 모델을 쓰는 걸로 알고 있음.

알아서 찾아보도록

 


 

from langchain.chains import RetrievalQA

 

chains라는 모듈에서 RetrievalQA를 가져옴.

 

여기서는 chains 말고 RetrievalQA를 먼저 설명함

 

RetrievalQA(Retrieval Question Answering)

임마는 문서에서 정보를 검색하고 질문에 답변하는 AI 모델을 만드는 도구임.

 

Retrieval 말 그대로 검색한다.라는 

 

RetrievalQA는 여러 단계를 연결함.

  1. 문서 검색 (Retrieval)
  2. 관련 정보 추출
  3. 질문 이해
  4. 답변 생성

이게 단계 하나하나를 체인처럼 엮어서 동작시켜서 chain임.

 

더 자세히 

 

  1. 문서 인덱싱 (Document Indexing):
    1. 먼저, 사용 가능한 모든 문서를 작은 조각(chunk)으로 나눔.
    2. 각 조각을 벡터(숫자 배열)로 변환. ㅇㅇ맞음 '임베딩(embedding)'
    3. 이 벡터들을 효율적으로 검색할 수 있는 데이터베이스에 저장. ㅇㅇ 맞음 벡터 DB 
      • 사용된 친구들
        • 문서 조각화: RecursiveCharacterTextSplitter
        • 임베딩: OpenAIEmbeddings
        • 벡터 데이터베이스: Chroma
  2. 질문 처리 (Query Processing):
    1. 사용자의 질문도 같은 방식으로 벡터로 변환.
      • 임베딩: OpenAIEmbeddings (문서와 동일한 임베딩 모델 사용)
  3. 유사도 검색 (Similarity Search):
    1. 질문 벡터와 가장 유사한 문서 조각들을 찾음.
    2. 이 과정은 벡터 간의 '거리'를 계산함. 계란찜 / 계란말이 
      • Chroma 벡터 데이터베이스의 내장 검색 기능
  4. 관련 정보 추출 (Relevant Information Extraction):
    1. 가장 유사한 몇 개의 문서 조각을 선택.
      • Chroma의 검색 결과를 바탕으로 RetrievalQA 내부에서 처리
  5. 답변 생성 (Answer Generation):
    1. 선택된 문서 조각들과 원래 질문을 LLM에 입력으로 제공.
    2. LLM은 이 정보를 바탕으로 답변을 생성.
      • LLM: OpenAI (코드에서 OpenAI() 사용)

각 단계에서 사용된 친구들을 주목해야 됨.

RetrievalQA 혼자 하드캐리하는 게 아님.

 

전체 과정은 RetrievalQA 클래스가 조율하지만

각 컴포넌트들을 연결하여 각 단계의 입력이 다음 단계의 출력으로

사용되는 전체 파이프라인을 구성함.

 

그 연결된 모습이 마치 체인 같다는 거임.

 

그래서 Chain

 

사실 이게 끝임.

 

RetrievalQA  결과

이게 이 코드의 목적임.

 

정리 :

내가 갑자기 어디 멀리 가야겠다 싶어서 

오토바이를 만들어야겠다 생각함.

그래서 막 바퀴 안장이랑 엔진이랑 다 들고 옴

 

근데 생각해 보니깐 나는 오토바이 만들 줄 모르는 거임 (바보)

 

근데 갑자기 뒤에서 어떤 오토바이 장인이 툭툭 치면서 한 마디 함.

 

장인 : 필수 부품은 다 있구마 바퀴는 여기 두고 엔진은 저기두고 어쩌구 저쩌구

나: 네? 네.

 

장인: (뚝딱뚝딱) 오토바이 완성

나: 야호 

 

그냥 장인이 물건만 두라는 데로 뒀더니 오토바이가 만들어짐.

 

  • 멀리 가야겠다 = 사용자에게 질문을 받고 알맞은 답을 하자
  • 필수 부품은 다 있구마 바퀴, 엔진, 안장 등 = LLM, retriever, chain_type 등의 컴포넌트
  • 바퀴는 여기 두고 엔진은 저기 두고 = RetrievalQA 구문
  • 이것들을 가져온 사람 = 코드를 작성하는 개발자(나)
  • 조립하는 방법을 모르는 상황 = RetrievalQA의 복잡한 설정을 직접 하기 어려운 상황
  • 장인 = RetrievalQA.from_chain_type 메서드
  • 오토바이 = 완성된 RetrievalQA 객체

이제 나는 바퀴랑 엔진 같은 필수 부품만 구해서

장인이 넣으란데 넣으면 오토바이가 만들어지는 것을 알게 됨.

 

이제 나는 LLM, retiever 같은 컴포넌트를

RetrievalQA.from_chain_type 구문에 맞게 넣으면 사용자 질문에 알맞은 답변이 나오는 것을 알게 됨.

 

참고로 이게 RetrievalQA 구문(syntax) 임 필수 파라미터 없으면 에러남.

적어도 엔진은 니가 니손으로 들고 와야 오토바이가 만들어진다는 뜻. llm 같은 거

self.genie = RetrievalQA.from_chain_type(
    llm=OpenAI(), 
    chain_type="stuff", 
    retriever=self.vectordb.as_retriever()
)

from typing import List

 

typing 타입 힌팅기능을 제공함

List 를 나타내는 타입 힌트를 가져옴.

 

타입 힌팅

 

말 그대로 그냥 타입이 뭔지 힌트 준다는 거임.

 

1. 코드의 가독성 향상 때문에 씀.

메소드가 어떤 타입의 입력을 받는지 명확히 표시하는 거임.

그럼 코드 읽는 사람이 "아하" 하고 이해하는 거

 

def text_split(documents: TextLoader):

def embeddings(texts: List[Document]):

 

우리 코드에 이렇게 2개의 타입 힌팅이 쓰였는데

 

그냥 말 그대로

documents 매개변수는  TextLoader의 인스턴스여야 함;;;

texts 매개변수는 Document 객체들의 리스트여야 함;;

 

하고 알려주는 거임 

 


from langchain.schema import Document

 

맨 위 TextLoader 참조

 

import os

 

이건 코드 뒷부분 환경 변수를 구동하기 위해 씀.

 

os.environ['OPENAI_API_KEY'] = "니 API 키"

 

원래 이런 API는 매우 민감한 사항이라

환경 변수에 따로 저장해서 관리하는데

지금은 그냥 귀찮기도 하고 귀찮기도 하고 귀찮기도 해서 그냥

여기에 같이 쓰는 거임


 

class Genie:

    def __init__(self, file_path: str):
        self.file_path = file_path
        self.loader = TextLoader(self.file_path, encoding='utf-8')  # 인코딩을 명시적으로 지정
        self.documents = self.loader.load()
        self.texts = self.text_split(self.documents)
        self.vectordb = self.embeddings(self.texts)
        self.genie = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=self.vectordb.as_retriever())

 

위에서 작동방식을 미리 다 설명해서 

그냥 큰 흐름만 보고 넘어갈 거임.

 

def __init__(self, file_path: str)

 

메소드 정의하는 거임.

`file_path: str`는 이 메소드가 문자열 타입의 `file_path`라는 매개변수 받는다는 의미임.

 

self.file_path = file_path


입력받은 `file_path`를 클래스의 속성으로 저장하는 거임.
이렇게 하면 클래스의 다른 메서드에서도 이 파일 경로 사용할 수 있음.

self.loader = TextLoader(self.file_path, encoding='utf-8')


`TextLoader`라는 클래스의 인스턴스 생성하는 거임.
걍 파일 읽기 위한 도구임.
`encoding='utf-8'`은 파일의 문자 인코딩 지정하는 거임. 

UTF-8은 거의 모든 문자 표현할 수 있는 인코딩 방식임. (한글 쓸라고 넣었음)

 

self.documents = self.loader.load()


`loader` 사용해서 파일 내용 읽어오는 거임.
읽어온 내용은 `documents`라는 속성에 저장됨.

self.texts = self.text_split(self.documents)


`text_split` 메서드 호출해서 `documents`를 더 작은 텍스트 조각으로 나누는 거.
이거 긴 문서 처리하기 쉽게 만들기 위한 거임.

self.vectordb = self.embeddings(self.texts)


`embeddings` 메서드 호출해서 텍스트를 벡터로 변환하는 거임.
이 벡터들은 `vectordb`라는 데이터베이스에 저장됨.

self.genie = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=self.vectordb.as_retriever())


`RetrievalQA` 시스템 설정하는 거임. 이거 질문에 답변하는 AI 시스템임.
`OpenAI()`는 OpenAI의 언어 모델 사용한다는 의미임.
`chain_type="stuff"`는 사용할 검색 방법 지정하는 거임.
`retriever=self.vectordb.as_retriever()`는 앞서 만든 벡터 데이터베이스를 검색기로 사용한다는 의미임.

 


@staticmethod
    def text_split(documents: TextLoader):
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
        texts = text_splitter.split_documents(documents)
        return texts

 

  def text_split(documents: TextLoader):

 

documents 라는 매개변수에는 TextLoader 타입만 와야 된다는 

 

  text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)

 

위 RecursiveCharacterTextSplitter 설명 참조.

여기서 RecursiveCharacterTextSplitter 는 text_splitter

어떤 식으로 텍스트를 나눌정보규칙을 넣어둠 

 

 texts = text_splitter.split_documents(documents)

 

split_documents()

이 녀석이 실질적으로 문서를 분할을 하는 녀석임.

 

문자를 분할해서 texts에 집어넣음

 

return texts

 

texts 발사 (뽕)


 

    @staticmethod
    def embeddings(texts: List[Document]):
        embeddings = OpenAIEmbeddings()
        vectordb = Chroma.from_documents(texts, embeddings)
        return vectordb

 

이 부분이 문서를 임베딩하고 벡터 DB를 만드는 부분임.

우리가 공부하고 싶은 것들이 왕창 모아져있음.

근데 이미 공부는 위에서 다함.

 

그래서 흐름만 보자.

 

메서드 정의는 건너뜀 이제 알거임.(그냥 알아줘)

 

 embeddings = OpenAIEmbeddings()

 

OpenAIEmbeddings 클래스의 인스턴스를 생성.

임마가 바로 OpenAI 임베딩 모델을 사용해서

텍스트를 벡터로 변환하는 놈을 만드는거임.

 

vectordb = Chroma.from_documents(texts, embeddings)

 

Chroma다 

 

Chroma 클래스는 벡터 DB를 생성하는 클레스임.

거기서 from_documents라는 메서드가 

문서들이랑 임베딩 모델 받아서 벡터 DB를 만드는거

 

위의 코드에서 만든 embeddings 모델을 여기서 사용함

이제 임베딩 모델이 문서를 벡터로 변환해서 벡터 DB에 저장.

 

return vectordb

 

벡터 DB 발사

 

    def ask(self, query: str):
        return self.genie.invoke(query)

 

ask 메소드 정의하는거

여기서 invoke는 RetrievalQA의 메소드임 너무 당연해서 왜 말했나 싶은데

그냥 썼으니깐 안지움

   self.genie = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=self.vectordb.as_retriever())

 

혹시나 모르겠다면 이 코드를 다시 보자 

 

invoke 근데 얘는 좀 알아야됨

 

invoke( RetrievalQA의 메소드)

 

  1. 쿼리 전처리:
    • 입력된 쿼리를 정규화하거나 필요한 경우 확장함.
  2. 문서 검색:
    • 벡터 데이터베이스에서 쿼리와 가장 유사한 문서 조각들을 검색.
    • 유사도 점수를 기반으로 상위 N개의 문서를 선택.
  3. 프롬프트 구성:
    • 검색된 관련 문서들과 원래 쿼리를 결합하여 언어 모델에 전달할 프롬프트를 만드삐.
    • 이 프롬프트는 보통 "다음 정보를 바탕으로 질문에 답하세요" 형태일거임.
  4. 언어 모델 호출:
    • 구성된 프롬프트를 사용하여 언어 모델(예: GPT-4o)을 호출. (잠만 일로와보소)
    • 모델은 주어진 컨텍스트(검색된 문서)와 쿼리를 바탕으로 응답을 생성.
  5. 후처리:
    • 생성된 응답을 필요에 따라 정제하거나 포맷팅.
    • 메타데이터(예: 사용된 문서 출처)를 추가할 수도 있다고 함.
  6. 결과 반환:
    • 최종 응답을 사용자에게 반환.

우리가 지금 위에서한 모든 난리란 난리를 이 친구가 조율하여 수행하는거임.

고맙다 invoke야.

 

결론 :

검색을 트리거하고 결과를 처리하는 메소드.

 

if __name__ == "__main__":
    genie = Genie("2024-10-16_17-20-28.txt")
    print(genie.ask("한강 작가님이 누구야?"))

 

와 진짜 끝이다.

 

내가 wikipedia.py에서 만든 txt 파일을 가지고

문서 쪼개고 나누고 지지고 볶기 -> 임베딩 -> 벡터 DB를 만들어서

이를 바탕으로 질문을 하는거임.

 

여긴 설명할게 없음 위에 설명을 차근 차근 따라오면 이해 가능.

그냥 직관적으로 이해 가능 하기도 하고

 

암튼 코드 분석 끝까지 완.

 

다들 수고했다. (특히 나)

 

 

 

 

+ Recent posts