공부할 챕터
| 챕터 | 주제 | 심화 개념 | 실전 구현/미니 프로젝트 |
| 1 | RAG & AI Agent 구조 | RAG/LLM의 한계와 아키텍처 분석, Retrieval 전략 비교 | 실무용 시스템 아키텍처 도식화 + Retrieval 전략 설계 |
| 2 | LangChain & LangGraph | Runnable 구조, AgentExecutor, LCEL, StateGraph 전이 원리 | ReAct 기반 Tool Agent 구현 + LangGraph 상태 기반 워크플로우 실습 |
| 3 | 문서 임베딩 & 벡터 DB | 텍스트 청크 전략, 임베딩 품질 비교, Index 튜닝 | FAISS vs Chroma 성능 실험 + 대용량 문서 검색기 구축 |
| 4 | RAG Pipeline 최적화 | Retriever 성능 튜닝, reranking, Chunk Linking, Hybrid Search | 문서 QA Agent 최적화 실험 + 모듈화된 RAG 파이프라인 구축 |
| 5 | Prompt/Context Engineering | Prompt 전략별 응답 품질 비교, Context Compression | Prompt 교차 실험 자동화 + Token 절감 Context Engine 구현 |
| 6 | LLM Fine-Tuning & LoRA | LoRA 구조, PEFT 기법, QLoRA, FT vs Prompting 비교 | 특정 도메인 응대 Agent용 LLM 미세조정 실습 |
| 7 | 평가 & 검증 | RAGAS, OpenAI Evals, 수치화된 평가 지표 설계 | 평가 자동화 파이프라인 구축 + EDA 대시보드 |
| 8 | 보안, 프라이버시, 권한 제어 | LLM 프롬프트 인젝션 대응, 사용자 인증 토큰 설계 | 사내 내부망 대응용 보안 정책 기반 Agent 구축 |
| 9 | 온프레미스 운영 및 배포 | LangServe, Docker, K8s 배포 자동화, API Gateways | LangChain Agent를 API 서비스로 배포하는 실전 |
| 10 | 종합 프로젝트 | 복합 Agent 통합 설계 + 기술서 작성 + 튜닝 | RAG 기반 기업형 AI 어시스턴트 시스템 완성 |

유사도 검색(Similarity Search)

문서 검색(RAG)의 핵심은 입력 쿼리와 가장 유사한 텍스트 조각(chunk)을 찾는 것 이다! 앞서 사용한 faiss는 벡터(숫자)로 표현된 문장들 간의 거리를 계산해서 가장 가까운 것들을 찾아주는 라이브러리이다.
용어 정리
- 백터화(Embedding): 문장을 다차원 벡터(숫자 배열)로 변환)
- 유사도(Similarity): 두 벡터 간의 가까움 정도
- 거리(Distance): 유사도의 반대 개념 (멀수록 덜 관련됨)
- 코사인 유사도: 방향이 얼마나 유사한지 (가장 자주 사용)
- L2 거리: 유클리드 거리 (좌표 간 거리)
좋아 이제 실습해보자!
- query 벡터 vs. 각 chunk 벡터 간의 유사도 점수 계산
- 가장 유사한 상의 k개의 chunk 반환
- 결과를 표와 그래프로 시각화
from sentence_transformers import SentenceTransformer
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
import matplotlib.pyplot as plt
# 임베딩 모델 로그
model = SentenceTransformer('all-MiniLM-L6-v2')
# 문서 청크 (텍스트 조각들)
chunks = [
"신입사원의 연차는 입사 후 1개월부터 발생합니다.",
"연차 신청은 최소 2주 전에 해야 합니다.",
"회사 보안 정책은 클라우드 시스템을 통해 데이터가 관리됩니다.",
"원격 근무는 주 2회 가능합니다.",
]
# 각 chunk를 벡터로 변환
chunk_embeddings = model.encode(chunks)
# 쿼리 입력 및 벡터화
query = "연차는 언제부터 생기나요?"
query_embedding = model.encode([query])
# 코사인 유사도 계산
similarities = cosine_similarity(query_embedding, chunk_embeddings)[0]
# 유사도 정렬 및 Top-K 결과 출력
top_k = 3
top_indices = np.argsort(similarities)[::-1][:top_k] # 유사도가 높은 순
print(f"Query: {query}\n")
for rank, idx in enumerate(top_indices):
print(f"Top-{rank+1} (유사도: {similarities[idx]:.4f}): {chunks[idx]}")
# 시각화
plt.figure(figsize=(8, 4))
plt.bar(range(len(similarities)), similarities, tick_label=[f"Chunk {i}" for i in range(len(chunks))])
plt.xlabel("Chunk Index")
plt.ylabel("Cosine Similarity")
plt.title("쿼리 vs 청크 유사도")
plt.ylim(0, 1)
plt.show()
Query: 연차는 언제부터 생기나요?
Top-1 (유사도: 0.7901): 연차 신청은 최소 2주 전에 해야 합니다.
Top-2 (유사도: 0.7301): 원격 근무는 주 2회 가능합니다.
Top-3 (유사도: 0.7292): 신입사원의 연차는 입사 후 1개월부터 발생합니다.
- SentenceTransformer('all-MiniLM-L6-v2'): 이 모델은 사람이 이해할 수 있는 문장을 숫자로 된 벡터로 바꿔준다.
- sklearn.metrics.pairwise.cosine_similarity(): 두 벡터 간의 방향이 얼마나 비슷한지를 계산해주는 함수이다.
- 두 벡터가 같은 방향 이면 1에 가까운 값 (유사도 높음)
- 직각이면 0 (상관없음)
- 반대 방향이면 -1 (완전 다름)
문장이 벡터로 바뀌는 방식(내부를 조금 들여다보자!)
내가 model.encode("연차는 언제부터 생기나요?") 라고 호출하면 이 문장은 SentenceTransformer 모델을 통해 고차원 벡터로 변환된다!
1. 토크나이징 (Tokenizing): 문장이 "단어" 또는 "서브워드 조각" 단위로 분해된다.
예)
"연차를 언제부터 쓸 수 있나요?"
→ ["연", "차", "를", "언", "제", "부", "터", "쓸", "수", "있", "나", "요", "?"]
2. 임베딩(Embedding): 각 토큰을 숫자로 바꾸고 그것들을 딥러닝 모델 (BERT 기반 등)이 통과하면서 문맥을 반영한 고차원 벡터로 변환된다.
3. 문장 단위 벡터로 평균 또는 풀링 (Mean pooling 등): 토큰별 벡터들을 하나의 문장 벡터로 요약해준다!
벡터간 거리 또는 유사도 계산
변환된 문장 벡터들끼리는 이제 단순히 수학적인 벡터니까 비교가 가능해진다. 대표적인 방법은 CosineSimilarity(코사인 유사도) 또는 L2 거리이다.
Cosine Similarity: 두 벡터가 이루는 각도의 유사도를 계산한다. 각도가 작을수록 (방향이 비슷할수록) 더 유사한 문장으로 간주된다. 결과 값은 -1 ~ 1 사이이고 1 이면 완전히 같은 방향 (매우 유사), 0이면 서로 무관, -1이면 반대 방향 (완전 반대의미) 이 있다.
FAISS에서는 L2거리 또는 Cosine 유사도 기반으로 검색
위에 작성된 코드에서 벡터를 faiss.normalize_L2()로 정규화한 후에 검색했는데 이렇게 정규화하면 L2거리로 계산해도 Cosine 유사도와 동일한 정렬 순서가 나온다.
normalize_L2()는 벡터를 단위 벡터로 만들고 FAISS는 L2거리로 비교하지만 결과적으로 코사인 유사도와 같은 효과를 준다!
한글이나 영어나 의미 단위를 유지해야 유사도가 정확한데 "연", "차" 처럼 글자 단위(subword)로 나누면 오히려 다른 단어랑 섞이지 않을까? 라는 생각을 해보았다.
모델이 한글을 subword 또는 whole-word로 처리한다!
위에 사용한 SentenceTransformer('all-MiniLM-L6-v2') 같은 모델은 영어 기반이라 한글은 내부적으로 유니코드 글자 단위로 처리 되거나 BPE(Byte Pair Encoding) 등의 서브워드 방식으로 쪼개질 수 있다!
예를 들어보면
| 입력 문장 | 토큰화 결과 예시 |
| 연차를 | ['연', '차', '를'] 또는 ['연차', '를'] |
| 연극을 | ['연', '극', '을'] 또는 ['연극', '을'] |
이처럼 한글은 모델에 따라 어설프게 쪼개질 수도 있고 의미 단위로 유지될 수도 있다. 그래서 한글은 영어보다 더 주의 깊게 전처리하고 모델을 선택해야 정확한 임베딩이 나온다.
그래도 문장 단위 임벧딩이 중요한 이유는!?
Sentence-BERT 모델은 단어 하나하나의 임베딩이 아니라 전체 문장의 의미를 통합한 벡터를 만들어준다!
즉 "연차를 언제부터 쓸 수 있나욤??" 라는 문장을 벡터로 만들 땐 단순히 "연"이라는 토큰 하나만 보고 판단하지 않는다.
모델은 "연차" 라는 단어의 문맥적 의미를 이해하고 벡터를 구성하려고 한다.!
결론: 단어 단위가 아니라 "문맥" 전체를 반영한 임베딩이기 때문에 일부 서브워드 단위 쪼개짐이 있어도 생각보다 성능이 잘 나온다!
이제 그만 알아보자!
https://zilliz.com/blog/similarity-metrics-for-vector-search
Similarity Metrics for Vector Search - Zilliz blog
Exploring five similarity metrics for vector search: L2 or Euclidean distance, cosine distance, inner product, and hamming distance.
zilliz.com
** 그냥 하루하루 개인 공부한 것을 끄적 거리는 공간입니다.
이곳 저곳에서 구글링한 것과 강의 들은 내용이 정리가 되었습니다.
그림들은 그림밑에 출처표시를 해놓았습니다.
문제가 될시 말씀해주시면 해당 부분은 삭제 하도록하겠습니다. **
'public void static main > AI' 카테고리의 다른 글
| [AI] 챕터01 - RAG & Agent 구조 개념-1 (6) | 2025.07.29 |
|---|---|
| [AI] 챕터08 - 과적합과 일반 (2) | 2025.07.24 |
| [AI] 챕터07 - 대표 알고리즘 (5) | 2025.07.22 |
| [AI] 챕터06 - 성능 평가 (7) | 2025.07.21 |
| [AI] 챕터05 - 학습과 예측 (3) | 2025.07.17 |
댓글