임베딩 및 벡터 검색: BERT 대 문장 변환기
이 시리즈의 첫 번째 기사에서는 RAG 아키텍처와 그 역할을 살펴보았습니다. LLM의 환각을 해결합니다. 모든 RAG 시스템의 심장은 바로 검색: 잠재적인 지식 기반에서 찾을 수 있는 능력 거대하고 신청서와 가장 관련성이 높은 문서입니다. 이 능력은 기반 전적으로 임베딩 그리고 계속 벡터 검색.
임베딩은 텍스트의 의미를 숫자로 표현한 것입니다. 단어, 문장, 문서 간의 의미론적 관계를 포착하는 숫자(벡터)입니다. 임베딩의 품질은 검색 품질을 직접적으로 결정하므로 전체 RAG 시스템의 품질. 잘못된 임베딩 모델을 선택한다는 것은 모래 위에 집의 기초를 쌓으라.
이 시리즈의 두 번째 기사에서는 AI 엔지니어링 및 고급 RAG, 우리는 Word2Vec을 통한 임베딩의 기원부터 BERT 혁명부터 현대 Sentence Transformers까지. 생성하는 방법을 살펴보겠습니다. 임베딩, 벡터 공간에서 텍스트를 비교하는 방법, 엔진을 구축하는 방법 FAISS를 사용한 의미 검색 및 사용 사례에 적합한 모델을 선택하는 방법
시리즈 개요
| # | Articolo | 집중하다 |
|---|---|---|
| 1 | RAG 설명 | 기본 및 전체 아키텍처 |
| 2 | 현재 위치 - 임베딩 및 의미 검색 | 텍스트가 벡터가 되는 방법 |
| 3 | 심층적인 벡터 데이터베이스 | 저장, 인덱싱, 유사성 검색 |
| 4 | LangChain과 Python을 사용한 RAG | 실용적인 엔드투엔드 구현 |
| 5 | 하이브리드 검색 및 순위 재지정 | 하이브리드 키워드 + 의미 검색 |
| 6 | 컨텍스트 창 및 프롬프트 엔지니어링 | LLM에 대한 컨텍스트 최적화 |
| 7 | 생산 중인 RAG | 확장, 모니터링, 평가 |
| 8 | 지식 그래프 및 RAG | 지식 그래프 + 검색 |
| 9 | 다중 에이전트 시스템 | 협업 AI 에이전트 |
| 10 | RAG의 미래 | 동향, 연구 및 다음 단계 |
무엇을 배울 것인가
- 임베딩이란 무엇이며 숫자 형식으로 의미를 어떻게 표현합니까?
- Word2Vec에서 BERT, 문장 변환기로의 진화
- 바닐라 BERT가 유사성을 위해 작동하지 않는 이유와 SBERT가 문제를 해결하는 방법
- 수십 가지 옵션 중에서 올바른 임베딩 모델을 선택하는 방법
- Python에서 문장 변환기와 FAISS를 사용하여 의미 검색 구현
- 벡터 유사성 측정 항목 및 각 사용 시기
- 주요 벡터 검색 엔진 간의 아키텍처 비교
- 특정 도메인에 대한 임베딩을 미세 조정하는 방법
1. 임베딩이란 무엇입니까?
Un 삽입 이는 개별 객체를 변환하는 수학적 함수입니다. (단어, 문장, 문서, 이미지)를 실수 벡터로 변환 고정된 차원을 갖는 연속 공간. 기본적으로 사람이 읽을 수 있는 텍스트를 변환합니다. 의미론적 관계를 유지하면서 기계가 이해할 수 있는 숫자 목록으로 변환 원문 중에서.
기본적인 아이디어는 비슷한 의미를 가진 텍스트는 텍스트에 가까운 벡터를 가져야 한다는 것입니다. 공간이 있는 반면, 의미가 다른 텍스트는 먼 벡터를 가져야 합니다. 이 부동산 그것은 호출된다 의미론적 동형성: 의미론적 관계의 구조 단어 사이의 정보는 벡터 공간의 기하학에 보존됩니다.
1.1 원-핫 인코딩에서 고밀도 벡터로
임베딩이 필요한 이유를 이해하기 위해 가장 간단한 대안을 고려해 보겠습니다. 는 원-핫 인코딩. 50,000 단어의 어휘로 모든 단어를 이는 하나만 1이고 나머지는 모두 0인 50,000차원의 벡터로 표현됩니다.
ONE-HOT ENCODING (vocabolario di 50.000 parole):
"gatto" = [0, 0, ..., 1, ..., 0, 0] (50.000 dimensioni, un solo 1)
"cane" = [0, 0, ..., 0, ..., 1, 0] (50.000 dimensioni, un solo 1)
Distanza tra "gatto" e "cane" = stessa di "gatto" e "frigorifero"
Nessuna informazione semantica!
DENSE EMBEDDING (es. 384 dimensioni):
"gatto" = [0.23, -0.45, 0.89, ..., 0.12] (384 dimensioni, tutti numeri reali)
"cane" = [0.21, -0.42, 0.91, ..., 0.15] (384 dimensioni, tutti numeri reali)
Distanza tra "gatto" e "cane" = PICCOLA (animali domestici)
Distanza tra "gatto" e "frigorifero" = GRANDE (concetti diversi)
Il significato è catturato nella geometria!
원-핫 인코딩의 문제는 명백합니다. 벡터가 거대합니다(차원성). 어휘와 동일), 흩어져 있고(거의 모두 0), 무엇보다도 모두 서로 직교합니다. 두 단어는 의미에 관계없이 동일한 거리를 갖습니다. 없다 "고양이"와 "고양이"를 구별하는 방법과 "고양이"와 "경제"를 구별하는 방법입니다.
I 조밀한 임베딩 세 가지 문제를 모두 해결합니다. 벡터는 다음과 같습니다. 컴팩트(수백 차원), 밀도(모든 값이 중요함) 및 그들은 기하학에서 의미론적 관계를 포착합니다. 비슷한 단어에는 가까운 벡터가 있습니다. 공간의 방향은 언어적 개념에 해당합니다.
1.2 의미 공간
임베딩의 흥미로운 속성은 다음과 같습니다. 의미론적 관계 예 기하학적 관계로 변환. 고전적인 예는 산술입니다. 벡터: 벡터 "왕"에서 "남자"를 더하고 "여자"를 빼면 매우 가까운 벡터가 생성됩니다. "여왕"에게. 이것은 마술이 아닙니다. 공간이 개념을 포착했다는 뜻입니다. 한 방향으로 '젠더'가 있고 다른 방향으로 '왕권'이라는 개념이 있습니다.
Relazioni semantiche come operazioni vettoriali:
vec("re") - vec("uomo") + vec("donna") ~ vec("regina")
vec("Parigi") - vec("Francia") + vec("Italia") ~ vec("Roma")
vec("buono") - vec("migliore") + vec("grande") ~ vec("più grande")
Cluster nello spazio:
[gatto, cane, cavallo, pesce] --> vicini (animali)
[Python, Java, C++, Rust] --> vicini (linguaggi di programmazione)
[felice, contento, gioioso] --> vicinissimi (sinonimi)
근본적인 직관
임베딩은 본질적으로 압축기를 의미. 소요 모든 뉘앙스와 함께 텍스트의 의미를 파악하고 한 지점에서 이해합니다. 다차원 공간에서. 다른 모든 지점과 관련된 해당 지점의 위치 points는 모델이 학습한 모든 의미론적 관계를 포착합니다. 이것은 전체 의미론적 연구의 기초가 되는 기초.
2. 클래식 단어 임베딩: Word2Vec, GloVe, FastText
임베딩의 현대 역사는 2013년부터 시작됩니다. Word2Old, Google의 Tomas Mikolov와 동료가 게시했습니다. 혁명적인 아이디어는 간단했습니다. 단어가 나타나는 문맥에서 단어의 의미를 배울 수 있습니다. 그가 말했듯이 1957년 언어학자 존 퍼스(John Firth)는 "단어와 함께 지내는 사람에 따라 단어를 알게 될 것입니다."라고 말했습니다.
2.1 Word2Vec: CBOW 및 스킵그램
Word2Vec은 임베딩 학습을 위한 두 가지 신경 아키텍처를 제안합니다.
- CBOW(연속 단어 모음): 문맥 단어 창이 주어지면 중심 단어를 예측합니다. 예: "___ 야옹 소리가 크게 난다"라고 하면 "고양이"를 예측하세요.
- 스킵그램: 중심 단어가 주어지면 문맥 단어를 예측합니다. 예: "cat"이 주어지면 "the", "meow", "loud"를 예측합니다.
CBOW (Continuous Bag of Words):
Input: parole di contesto ["il", "___", "miagola", "forte"]
Output: parola target "gatto"
Contesto ──> [Embedding Layer] ──> Media vettori ──> [Softmax] ──> "gatto"
Veloce, buono per parole frequenti
SKIP-GRAM:
Input: parola target "gatto"
Output: parole di contesto ["il", "miagola", "forte"]
"gatto" ──> [Embedding Layer] ──> [Softmax] ──> parole contesto
Più lento, migliore per parole rare
Parametri tipici:
- Dimensioni embedding: 100-300
- Finestra di contesto: 5-10 parole
- Vocabolario: 100k-1M parole
- Training: miliardi di parole (Wikipedia, Common Crawl)
2.2 GloVe와 FastText
장갑 (단어 표현을 위한 전역 벡터, Stanford 2014)는 다음을 채택합니다. 다른 접근 방식: 전역 동시 발생 행렬을 구축하고 이를 인수분해합니다. 임베딩을 가져옵니다. Word2Vec의 창을 통해 글로벌 관계를 포착하세요. 지역, 잃을 수 있습니다.
FastText (Facebook 2016)은 다음 수준에서 작업하여 Word2Vec을 확장합니다. 하위 단어 (n-그램 문자). 'embedding'이라는 단어가 표시됩니다. 또한 "emb", "mbe", "bed", "edd" 등의 구성 요소를 사용하여 생성할 수 있습니다. 훈련 중에 본 적이 없는 단어에 대한 임베딩(단어 어휘력이 부족하다), 이탈리아어와 같이 형태가 풍부한 언어에는 매우 중요한 이점입니다.
고전적인 단어 임베딩 비교
| 모델 | 년도 | 접근하다 | Forza | 한계 |
|---|---|---|---|---|
| Word2Old | 2013년 | 지역 상황 예측 | 빠르고 효과적입니다. | OOV 없음, 컨텍스트 없음 |
| 장갑 | 2014년 | 글로벌 동시 발생 | 글로벌 관계 | OOV 없음, 컨텍스트 없음 |
| FastText | 2016년 | N-그램 문자 | OOV를 관리합니다 | 단어당 하나의 벡터 |
2.3 기본 제한: 단어당 하나의 벡터
모든 고전적인 단어 임베딩은 구조적 한계를 공유합니다. 각 단어에 대한 단일 벡터, 맥락에 관계없이. "책상"이라는 단어는 "학교 책상"과 "책상"에 모두 동일하게 포함되어 있습니다. 이탈리아의". 이는 단어의 의미가 달라지기 때문에 심각한 문제입니다. 거의 항상 그것이 나타나는 맥락에 따라.
또한, 이러한 모델은 다음 수준에서 작동합니다. 한 단어: 아니다 문장이나 단락에 대한 임베딩을 생성할 수 있습니다. 대표하기 위해 문장의 경우 벡터 평균화와 같은 기초적인 전략에 의존해야 합니다. 그것을 구성하는 단어는 순서와 구문 구조에 대한 정보를 잃습니다.
RAG에 Word2Vec을 사용하지 않는 이유
고전적인 단어 임베딩은 다음과 같은 이유로 현대 의미 검색에 적합하지 않습니다. (1) 맥락을 포착하지 못합니다. (2) 문장 수준 임베딩을 생성하지 않습니다. (3) 벡터 평균은 중요한 정보를 잃습니다. "개가 사람을 물다"라는 문구 "man이 개를 물다"도 동일한 임베딩을 갖습니다. RAG의 경우 모델이 필요합니다. 문맥에서 전체 문장의 의미를 이해하는 사람.
3. 상황별 임베딩: BERT 혁명
2018년에 구글은 다음과 같이 발표합니다. 버트 (양방향 인코더 표현 Transformers에서) 풍경을 근본적으로 변화시킵니다. BERT가 생산 상황별 임베딩: 각 단어의 표현은 다음과 같습니다. 그것이 나타나는 문장의 전체 문맥에서. "학교 책상"에 "책상"이라는 단어가 있습니다. "il banca d'Italia"와는 다른 임베딩을 갖습니다.
3.1 BERT의 작동 방식
BERT는 아키텍처 기반입니다. 변압기 인코더. 입력을 받습니다 일련의 토큰과 각 토큰에 대해 768의 상황화된 벡터를 생성합니다. 차원(BERT 기반) 또는 1024 차원(BERT-대형). BERT의 힘은 메커니즘 자기 관심: 각 토큰은 해당 토큰의 다른 모든 토큰을 "봅니다". 왼쪽 및 오른쪽 순서(양방향)를 사용하여 직접 계산 표현.
Input: [CLS] il banco di scuola [SEP]
Tokenizzazione:
[CLS] il ban ##co di scuo ##la [SEP]
12 layer di Transformer Encoder:
Layer 1: Self-attention + Feed-forward
Layer 2: Self-attention + Feed-forward
...
Layer 12: Self-attention + Feed-forward
Output: un vettore 768-dim per OGNI token
[CLS] --> [0.23, -0.45, ..., 0.12] (vettore "riassunto")
il --> [0.11, -0.32, ..., 0.08]
ban --> [0.56, 0.12, ..., 0.34] (contestualizzato!)
##co --> [0.45, 0.09, ..., 0.29]
di --> [0.03, -0.21, ..., 0.05]
scuo --> [0.67, 0.33, ..., 0.41]
##la --> [0.59, 0.28, ..., 0.38]
[SEP] --> [0.02, -0.05, ..., 0.01]
3.2 풀링 전략: 토큰에서 문구까지
BERT는 다음에 대한 벡터를 생성합니다. 각 토큰, 그러나 의미론적 검색을 위해서는 필요합니다. 에 전체 문장에 대한 단일 벡터. 그것을 얻는 방법? 여러 가지가 있습니다 전략 풀링:
BERT의 풀링 전략
| 전략 | 설명 | 품질 |
|---|---|---|
| [CLS] 토큰 | 특수 토큰 캐리어 [CLS] 사용 | 유사성 보통 |
| 평균 풀링 | 모든 토큰 벡터의 평균 | 일반적으로 좋음 |
| 최대 풀링 | 각 차원의 최대값 | 주요 특징 포착 |
| 가중 평균 | 주의 가중치를 적용한 가중 평균 | 좋아요, 더 복잡해요 |
3.3 BERT 바닐라가 유사성을 위해 작동하지 않는 이유
BERT의 강력한 성능에도 불구하고 BERT를 직접 사용하여 문장은 매우 비효율적이며 결과가 좋지 않음. 그 이유는 두 가지입니다:
- 계산상의 비효율성: N개의 문장을 서로 비교하려면, BERT를 통해 모든 문장 쌍을 전달해야 합니다. 10,000개의 문장이 필요합니다. 10,000 x 10,000 / 2 = 5천만 회 전달 패스. 당 약 65ms의 시간 커플이면 65시간 정도 걸릴 것 같아요
- 임베딩 공간 퇴화: 풀링을 통해 생성된 임베딩 BERT에서는 코사인 유사성에 최적화되지 않았습니다. 모든 문장은 경향이 있다 공간이 좁아서 문장을 구별하기가 어렵습니다. 다른 문장과 비슷합니다. 이 현상을 이방성
크로스 인코딩 문제
BERT는 다음과 같이 설계되었습니다. 크로스 엔코더: 두 문장을 입력으로 사용합니다. 유사성 점수를 생성합니다. 각 문장 쌍에 대해 다시 계산해야 합니다. 처음부터 모든 것. 10,000개의 문서로 구성된 코퍼스와 쿼리를 사용하려면 10,000개가 필요합니다. 포워드 패스. 가능한 모든 비교의 색인을 구성하려면 비용이 필요합니다. 이차적으로 성장합니다. 이로 인해 BERT는 실시간 검색에 실용적이지 않습니다.
4. 문장 변환기: 솔루션
2019년에는 Nils Reimers와 Iryna Gurevych가 출판합니다. 문장-BERT(SBERT), 두 가지 문제를 우아하게 해결합니다. 핵심 아이디어는 BERT를 크로스 엔코더 a 바이 인코더: 문장 쌍을 전달하는 대신, 각 문장은 독립적으로 밀집된 벡터로 인코딩됩니다. 유사성 예 그런 다음 간단한 벡터 연산(코사인 유사성)으로 계산합니다.
4.1 샴 아키텍처와 트리플렛 네트워크
SBERT는 다음과 같은 아키텍처로 훈련되었습니다. 샴 네트워크: 2개 동일한 BERT 모델(공유 가중치 사용)은 두 문장을 별도로 처리합니다. 운송업체 결과는 벡터에 "접근"하는 손실 함수와 비교됩니다. 유사한 문장을 제거하고 다른 문장의 벡터를 "제거"합니다.
CROSS-ENCODER (BERT vanilla):
["frase A", "frase B"] ──> [BERT] ──> punteggio similarità
Deve processare OGNI coppia. O(n^2) per n frasi.
BI-ENCODER (SBERT):
"frase A" ──> [BERT + Pooling] ──> vettore_A (384-dim)
"frase B" ──> [BERT + Pooling] ──> vettore_B (384-dim)
similarità = cosine(vettore_A, vettore_B)
Ogni frase viene codificata UNA VOLTA. O(n) per n frasi.
I vettori possono essere pre-calcolati e indicizzati!
TRAINING con Siamese Network:
Frase 1 ──> [BERT] ──> pool ──> u
├──> loss(u, v, label)
Frase 2 ──> [BERT] ──> pool ──> v
Label = 1 se frasi simili, 0 se diverse
Loss: Contrastive Loss o Cosine Similarity Loss
Le 삼중 네트워크 앵커, 세 가지 입력으로 접근 방식을 확장합니다. 긍정적인(유사한) 예와 부정적인(다른) 예입니다. 손실(삼중 마진 손실) 거리보다 작은 앵커 포지티브 거리를 갖도록 모델을 푸시합니다. 최소한 미리 정의된 여백만큼 앵커 네거티브입니다.
4.2 BERT와 비교한 SBERT의 장점
성능 비교
| 미터법 | BERT 크로스 인코더 | SBERT 바이 인코더 |
|---|---|---|
| 유사성 10,000 문장 | ~65시간 | ~5초 |
| 벡터 사전 계산 | 불가능 | 네, 한 번만요 |
| 인덱싱 | 해당 없음 | FAISS, HNSW 등 |
| STS 벤치마크 품질 | ~87 (스피어맨) | ~85 (스피어맨) |
| 생산용 | 순위만 재지정 | 검색 + 순위 |
SBERT는 약간의 정확도를 희생합니다(STS 벤치마크에서 약 2포인트). 하지만 하나 벌어 10,000배 빠른 속도. 실제로 선택은 의무사항: 크로스 인코더는 SBERT의 상위 k 결과에 대한 재순위 지정자로 사용될 수 있습니다. 두 접근 방식의 장점을 결합합니다(패턴이라고 함). 검색 및 순위 재지정).
5. 임베딩 모델 비교
임베딩 모델 생태계는 방대하고 빠르게 발전하고 있습니다. 선택 올바른 모델은 의미 검색, 클러스터링, 분류 등 사용 사례에 따라 다릅니다. 다국어 RAG, 예산 및 대기 시간. 다음은 가장 관련성이 높은 모델에 대한 자세한 비교입니다.
임베딩 모델 비교(2024~2025)
| 모델 | 공급자 | 치수 | 최대 토큰 | MTEB 평균 | 메모 |
|---|---|---|---|---|---|
| 모든-MiniLM-L6-v2 | SERT | 384 | 256 | 56.26 | 매우 빠르며 프로토타입 제작에 이상적 |
| 모든 mpnet-base-v2 | SERT | 768 | 384 | 57.78 | 최고의 오픈 소스 품질/속도 비율 |
| e5-대형-v2 | 마이크로소프트 | 1024 | 512 | 62.20 | 고품질, 접두사 "query:" / "passage:"가 필요합니다. |
| BGE-M3 | BAAI | 1024 | 8192 | 63.55 | 다국어, 조밀+희소+콜버트 |
| 텍스트 삽입-3-작은 | 오픈AI | 1536년 | 8191 | 62.26 | Cloud API, 저렴함($0.02/1M 토큰) |
| 텍스트 삽입-3-대형 | 오픈AI | 3072 | 8191 | 64.59 | Cloud API, 최고 품질($0.13/1M 토큰) |
| embed-v3(영어) | 코히어 | 1024 | 512 | 64.47 | 연구에 적합한 Cloud API |
| nomic-embed-text-v1.5 | 노믹 | 768 | 8192 | 62.28 | 오픈 소스, 긴 컨텍스트, Matryoshka |
MTEB 벤치마크를 읽는 방법
MTEB (Massive Text Embedding Benchmark)은 사실상의 표준입니다. 임베딩 모델을 평가합니다. 8개 범주(분류, 클러스터링, 쌍 분류, 순위 재지정, 검색, STS, 요약). 점수 중간은 모든 범주의 평균이지만 RAG의 경우 하위 점수입니다. 검색 가장 관련성이 높습니다. 항상 사용 사례에 대한 특정 점수를 확인하세요.
5.1 선택 기준
- 신속한 프로토타이핑: all-MiniLM-L6-v2 - 빠르고, 가볍고, 무료
- 오픈 소스 제작: 모든-mpnet-base-v2 또는 e5-large-v2
- 최고의 클라우드 품질: text-embedding-3-large(OpenAI) 또는 embed-v3(Cohere)
- 다국어/이탈리아어: BGE-M3 또는 다국어-e5-대형
- 긴 컨텍스트(긴 문서): BGE-M3(8192개 토큰) 또는 nomic-embed-text-v1.5
- 제한된 예산: text-embedding-3-small(OpenAI) 또는 자체 호스팅 오픈 소스 템플릿
6. 벡터 유사성 메트릭
벡터가 있으면 두 벡터가 얼마나 많은지 측정하는 기능이 필요합니다. "비슷하다". 측정항목 선택은 결과와 성능 모두에 영향을 미칩니다.
6.1 코사인 유사성
La 약간의 유사성 두 벡터 사이의 각도의 코사인을 측정합니다. 범위는 -1(반대 벡터)부터 +1(동일한 벡터)까지이며 직교 벡터의 경우 0입니다. 벡터의 크기는 무시하고 방향에만 초점을 맞추세요. 텍스트 임베딩에 가장 많이 사용됩니다.
COSINE SIMILARITY:
A . B sum(a_i * b_i)
cos(A,B) = ─────── = ──────────────────────────────
|A| |B| sqrt(sum(a_i^2)) * sqrt(sum(b_i^2))
Range: [-1, +1]
+1 = identici, 0 = ortogonali, -1 = opposti
Invariante alla scala (normalizzata)
DOT PRODUCT (Prodotto Scalare):
dot(A,B) = sum(a_i * b_i)
Range: (-inf, +inf)
Sensibile alla magnitudine dei vettori
Più veloce (nessuna normalizzazione)
Equivalente a cosine se i vettori sono già normalizzati (norma L2 = 1)
EUCLIDEAN DISTANCE (L2):
d(A,B) = sqrt(sum((a_i - b_i)^2))
Range: [0, +inf)
0 = identici, valori alti = molto diversi
Sensibile alla magnitudine
Nota: DISTANZA, non similarità (valori bassi = più simili)
언제 어떤 측정항목을 사용해야 할까요?
| 미터법 | 언제 사용하는가 | 성능 |
|---|---|---|
| 코사인 유사성 | 텍스트 임베딩의 기본값, 정규화되지 않은 템플릿 | 평균(정규화가 필요함) |
| 내적 | 속도가 필요한 경우 이미 정규화된 임베딩(문장 변환기) | 높음(조작 용이) |
| 유클리드(L2) | 크기가 클 때 클러스터링 | 평균 |
유용한 팁: Sentence Transformers를 사용하면 벡터를 정규화할 수 있습니다. 단일 표준에 따라. 이 경우 코사인 유사성과 내적은 동일하며 내적이 더 빠릅니다. 대부분의 벡터 데이터베이스는 세 가지를 모두 지원합니다. 지표를 생성하고 인덱스를 생성할 때 선택할 수 있습니다.
7. Python을 이용한 실제 구현
코드로 넘어 갑시다. 우리는 다음을 사용하여 완전한 의미 검색 시스템을 구현할 것입니다.
sentence-transformers 임베딩 e의 경우 FAISS 에 대한
인덱싱 및 벡터 검색.
7.1 설정 및 설치
# Crea ambiente virtuale
python -m venv embedding-env
source embedding-env/bin/activate
# Installa le dipendenze principali
pip install sentence-transformers==3.3.1
pip install faiss-cpu==1.9.0 # Per CPU (usa faiss-gpu per GPU)
pip install numpy==2.1.3
pip install pandas==2.2.3
pip install tqdm==4.67.1
# Opzionale: per visualizzazione
pip install matplotlib==3.9.3
pip install scikit-learn==1.6.0
7.2 문장 변환기를 사용하여 임베딩 생성
from sentence_transformers import SentenceTransformer
import numpy as np
# Carica il modello (download automatico al primo utilizzo)
model = SentenceTransformer("all-MiniLM-L6-v2")
# Frasi di esempio
sentences = [
"Il gatto dorme sul divano",
"Il felino riposa sul sofa",
"Python è un linguaggio di programmazione",
"Java è usato per lo sviluppo enterprise",
"La ricerca semantica usa gli embeddings",
]
# Genera embeddings
embeddings = model.encode(sentences, show_progress_bar=True)
# Ogni embedding è un vettore numpy
print(f"Tipo: {type(embeddings)}")
print(f"Shape: {embeddings.shape}") # (5, 384) - 5 frasi, 384 dimensioni
print(f"Norma L2 del primo vettore: {np.linalg.norm(embeddings[0]):.4f}")
# Visualizza i primi 10 valori del primo embedding
print(f"\nPrimi 10 valori: {embeddings[0][:10]}")
7.3 구문 간의 유사성 계산
from sentence_transformers import SentenceTransformer, util
import numpy as np
model = SentenceTransformer("all-MiniLM-L6-v2")
# Frasi da confrontare
sentences = [
"Il gatto dorme sul divano",
"Il felino riposa sul sofa",
"Python è un linguaggio di programmazione",
"La pioggia cade sulla citta",
]
embeddings = model.encode(sentences, convert_to_tensor=True)
# Matrice di similarità completa
cosine_scores = util.cos_sim(embeddings, embeddings)
print("Matrice di Similarità Coseno:")
print("-" * 60)
for i in range(len(sentences)):
for j in range(len(sentences)):
score = cosine_scores[i][j].item()
if i != j:
print(f" {sentences[i][:30]:<30} vs {sentences[j][:30]:<30}")
print(f" Similarità: {score:.4f}")
# Calcolo manuale della cosine similarity per verifica
def cosine_similarity_manual(vec_a, vec_b):
"""Calcola cosine similarity manualmente."""
dot_product = np.dot(vec_a, vec_b)
norm_a = np.linalg.norm(vec_a)
norm_b = np.linalg.norm(vec_b)
return dot_product / (norm_a * norm_b)
emb_np = model.encode(sentences)
manual_score = cosine_similarity_manual(emb_np[0], emb_np[1])
print(f"\nVerifica manuale (frase 0 vs 1): {manual_score:.4f}")
예상 출력
"고양이는 소파에서 잠을 잔다"와 "고양이는 소파에서 잠을 잔다"라는 문장은 하나의 문장을 갖게 됩니다. 동일한 개념을 다른 단어로 표현하기 때문에 유사도가 높습니다(~0.85). "Python은 프로그래밍 언어이다"와의 유사성은 낮을 것입니다(~0.10). 개념이 완전히 다르기 때문이죠. 이것이 바로 연구의 힘이다 의미론 대 키워드 검색.
7.4 FAISS를 이용한 의미 검색
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
class SemanticSearchEngine:
"""Motore di ricerca semantica basato su FAISS."""
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
self.model = SentenceTransformer(model_name)
self.dimension = self.model.get_sentence_embedding_dimension()
self.index = None
self.documents = []
def build_index(self, documents: list[str], use_gpu: bool = False):
"""Costruisce l'indice FAISS dai documenti."""
self.documents = documents
# Genera embeddings per tutti i documenti
embeddings = self.model.encode(
documents,
show_progress_bar=True,
normalize_embeddings=True, # Normalizza per cosine similarity
batch_size=64,
)
embeddings = embeddings.astype("float32")
# Crea indice FAISS (Inner Product = Cosine per vettori normalizzati)
self.index = faiss.IndexFlatIP(self.dimension)
if use_gpu:
# Trasferisci indice su GPU per ricerca più veloce
res = faiss.StandardGpuResources()
self.index = faiss.index_cpu_to_gpu(res, 0, self.index)
self.index.add(embeddings)
print(f"Indice costruito: {self.index.ntotal} documenti indicizzati")
def search(self, query: str, top_k: int = 5) -> list[dict]:
"""Cerca i documenti più simili alla query."""
# Genera embedding della query
query_embedding = self.model.encode(
[query],
normalize_embeddings=True,
).astype("float32")
# Ricerca nell'indice FAISS
scores, indices = self.index.search(query_embedding, top_k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx != -1: # -1 indica nessun risultato
results.append({
"document": self.documents[idx],
"score": float(score),
"index": int(idx),
})
return results
def save_index(self, path: str):
"""Salva l'indice su disco."""
faiss.write_index(self.index, path)
def load_index(self, path: str):
"""Carica l'indice da disco."""
self.index = faiss.read_index(path)
# Utilizzo
if __name__ == "__main__":
# Corpus di documenti
documents = [
"Python è un linguaggio di programmazione ad alto livello, interpretato e multiparadigma",
"Java è un linguaggio orientato agli oggetti progettato per essere portabile",
"Il machine learning è un sottoinsieme dell'intelligenza artificiale",
"I database relazionali usano SQL per le query sui dati strutturati",
"Docker permette di containerizzare le applicazioni per il deployment",
"Kubernetes orchestra i container in ambienti di produzione distribuiti",
"React è una libreria JavaScript per costruire interfacce utente",
"Angular è un framework TypeScript per applicazioni web enterprise",
"I transformer sono l'architettura alla base dei modelli linguistici moderni",
"FAISS è una libreria per la ricerca efficiente di similarità tra vettori",
"La sicurezza informatica protegge i sistemi da accessi non autorizzati",
"Il cloud computing fornisce risorse computazionali on-demand via internet",
]
engine = SemanticSearchEngine()
engine.build_index(documents)
# Ricerca semantica
queries = [
"Come funziona l'intelligenza artificiale?",
"Framework per sviluppo web frontend",
"Come distribuire applicazioni in modo scalabile?",
]
for query in queries:
print(f"\nQuery: '{query}'")
print("-" * 50)
results = engine.search(query, top_k=3)
for i, result in enumerate(results, 1):
print(f" {i}. [{result['score']:.4f}] {result['document']}")
7.5 대규모 데이터 세트의 일괄 처리
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer
from tqdm import tqdm
import json
from pathlib import Path
class LargeScaleEmbedder:
"""Gestisce embedding e indicizzazione per grandi dataset."""
def __init__(self, model_name: str = "all-mpnet-base-v2"):
self.model = SentenceTransformer(model_name)
self.dimension = self.model.get_sentence_embedding_dimension()
def encode_in_batches(
self,
texts: list[str],
batch_size: int = 256,
output_path: str | None = None,
) -> np.ndarray:
"""Genera embeddings in batch con salvataggio incrementale."""
all_embeddings = []
for start_idx in tqdm(range(0, len(texts), batch_size)):
batch = texts[start_idx : start_idx + batch_size]
batch_embeddings = self.model.encode(
batch,
normalize_embeddings=True,
batch_size=batch_size,
show_progress_bar=False,
)
all_embeddings.append(batch_embeddings)
# Salvataggio incrementale (checkpoint)
if output_path and (start_idx % (batch_size * 10) == 0):
partial = np.vstack(all_embeddings)
np.save(f"{output_path}_partial.npy", partial)
result = np.vstack(all_embeddings).astype("float32")
if output_path:
np.save(f"{output_path}.npy", result)
print(f"Embeddings salvati: {result.shape}")
return result
def build_ivf_index(
self,
embeddings: np.ndarray,
n_clusters: int = 100,
n_probe: int = 10,
) -> faiss.Index:
"""Costruisce un indice IVF per ricerca approssimata veloce."""
# Quantizzatore per assegnare vettori ai cluster
quantizer = faiss.IndexFlatIP(self.dimension)
# Indice IVF con Inner Product
index = faiss.IndexIVFFlat(
quantizer, self.dimension, n_clusters, faiss.METRIC_INNER_PRODUCT
)
# Training: impara la struttura dei cluster
print(f"Training indice IVF con {n_clusters} cluster...")
index.train(embeddings)
# Aggiungi i vettori
index.add(embeddings)
index.nprobe = n_probe # Numero di cluster da esplorare in ricerca
print(f"Indice IVF costruito: {index.ntotal} vettori, {n_clusters} cluster")
return index
# Esempio di utilizzo con un grande corpus
if __name__ == "__main__":
embedder = LargeScaleEmbedder(model_name="all-MiniLM-L6-v2")
# Simula un grande corpus (in produzione: carica da file/database)
corpus = [f"Documento di esempio numero {i} sul tema tecnologia" for i in range(100_000)]
# Genera embeddings in batch
embeddings = embedder.encode_in_batches(
corpus,
batch_size=512,
output_path="corpus_embeddings",
)
# Costruisci indice IVF (più veloce di Flat per grandi corpus)
index = embedder.build_ivf_index(embeddings, n_clusters=256, n_probe=16)
# Ricerca
query_embedding = embedder.model.encode(
["intelligenza artificiale e machine learning"],
normalize_embeddings=True,
).astype("float32")
scores, indices = index.search(query_embedding, 5)
for score, idx in zip(scores[0], indices[0]):
print(f" [{score:.4f}] {corpus[idx]}")
7.6 E5 템플릿 포함(접두사 기반)
from sentence_transformers import SentenceTransformer
import numpy as np
# I modelli E5 richiedono prefix specifici
model = SentenceTransformer("intfloat/e5-large-v2")
# Per i documenti nel corpus: prefix "passage: "
documents = [
"passage: Python è un linguaggio di programmazione versatile e potente",
"passage: I database NoSQL sono progettati per dati non strutturati",
"passage: Kubernetes gestisce il deployment di applicazioni containerizzate",
"passage: Il deep learning usa reti neurali con molti layer",
]
# Per le query di ricerca: prefix "query: "
queries = [
"query: Come funziona l'apprendimento automatico?",
"query: Strumenti per gestire i container",
]
# Genera embeddings
doc_embeddings = model.encode(documents, normalize_embeddings=True)
query_embeddings = model.encode(queries, normalize_embeddings=True)
# Calcola similarità
for i, query in enumerate(queries):
scores = np.dot(query_embeddings[i], doc_embeddings.T)
best_idx = np.argmax(scores)
print(f"Query: {query}")
print(f" Miglior match: {documents[best_idx]}")
print(f" Score: {scores[best_idx]:.4f}\n")
접두사에 주의하세요
E5 및 BGE 모델에는 쿼리를 문서와 구별하기 위해 특정 접두사가 필요합니다.
E5의 경우: query: e passage: . BGE의 경우:
Represent this sentence: 쿼리용. 접두사를 잊어버리면 품질이 저하됩니다.
성능이 크게 향상되었습니다. 항상 모델 문서를 확인하세요.
8. 벡터 검색 엔진: 아키텍처 비교
FAISS는 저수준 구현에는 탁월하지만 프로덕션 환경에서는 필요합니다. 지속성, 메타데이터 필터링, 수평 확장성을 관리하는 솔루션 그리고 실시간 업데이트. 주요 벡터 데이터베이스와 그 특성은 다음과 같습니다.
벡터 데이터베이스 비교
| 데이터베이스 | 유형 | 언어 | 인덱스 | 이상적인 사용 사례 |
|---|---|---|---|---|
| FAISS | 책장 | C++/파이썬 | 플랫, IVF, HNSW, PQ | 순수 연구, 임베딩, 프로토타이핑 |
| Qdrant | 데이터베이스 | Rust | HNSW | 생산, 복잡한 필터, 고성능 |
| 솔방울 | 클라우드 관리 | 소유자 | 소유자 | 제로 운영(Zero-ops), 시작, 자동 확장 |
| 밀부스 | 데이터베이스 | Go/C++ | 플랫, IVF, HNSW, DiskANN | 엔터프라이즈 규모, 수십억 개의 벡터 |
| pg벡터 | 확대 | C | IVFF플랫, HNSW | 이미 PostgreSQL, 적당한 데이터 세트를 사용하고 있습니다. |
| 크로마DB | 데이터베이스 | 파이썬/러스트 | HNSW | 프로토타이핑, LangChain 통합 |
| 위비에이트 | 데이터베이스 | Go | HNSW | 다중 모드, GraphQL API |
8.1 Qdrant의 예
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance,
VectorParams,
PointStruct,
Filter,
FieldCondition,
MatchValue,
)
from sentence_transformers import SentenceTransformer
# Inizializza client (in-memory per test, o URL per produzione)
client = QdrantClient(":memory:") # oppure QdrantClient("http://localhost:6333")
model = SentenceTransformer("all-MiniLM-L6-v2")
# Crea collection
client.create_collection(
collection_name="articles",
vectors_config=VectorParams(
size=384, # Dimensione vettore all-MiniLM-L6-v2
distance=Distance.COSINE,
),
)
# Documenti con metadati
documents = [
{"text": "Python è ottimo per il data science", "category": "programming", "year": 2024},
{"text": "React è il framework frontend più popolare", "category": "web", "year": 2024},
{"text": "Kubernetes gestisce i container in produzione", "category": "devops", "year": 2023},
{"text": "TensorFlow è usato per il deep learning", "category": "ai", "year": 2024},
{"text": "PostgreSQL è un database relazionale avanzato", "category": "database", "year": 2023},
]
# Indicizzazione
points = []
for i, doc in enumerate(documents):
embedding = model.encode(doc["text"]).tolist()
points.append(
PointStruct(
id=i,
vector=embedding,
payload={"text": doc["text"], "category": doc["category"], "year": doc["year"]},
)
)
client.upsert(collection_name="articles", points=points)
# Ricerca semantica semplice
query = "machine learning e intelligenza artificiale"
query_vector = model.encode(query).tolist()
results = client.search(
collection_name="articles",
query_vector=query_vector,
limit=3,
)
print(f"Query: '{query}'")
for result in results:
print(f" [{result.score:.4f}] {result.payload['text']} ({result.payload['category']})")
# Ricerca con filtro per metadati
filtered_results = client.search(
collection_name="articles",
query_vector=query_vector,
query_filter=Filter(
must=[FieldCondition(key="year", match=MatchValue(value=2024))]
),
limit=3,
)
print(f"\nRicerca filtrata (solo 2024):")
for result in filtered_results:
print(f" [{result.score:.4f}] {result.payload['text']}")
9. 인덱싱 알고리즘: 속도/재현율 절충
수백만 개의 벡터에 대한 정확한(무차별 대입) 검색은 너무 느립니다. 알고리즘 의 ANN(근사 인접 이웃) 그들은 작은 비율을 희생한다 속도를 몇 배나 높일 수 있는 정밀도. 이러한 알고리즘을 이해하세요 벡터 데이터베이스를 올바르게 구성하는 것이 중요합니다.
9.1 Flat Index(정확검색)
인덱스 평평한 쿼리를 데이터베이스의 모든 단일 벡터와 비교합니다. 100% 리콜이 보장되지만 비용은 O(n*d)입니다. 여기서 n은 캐리어 수이고 d는 캐리어 수입니다. 차원. 소규모 데이터 세트(최대 100,000개 벡터)에만 실용적입니다.
9.2 IVF(반전된 파일 인덱스)
IVF k-평균을 사용하여 벡터 공간을 클러스터로 분할합니다. 현재
검색에서는 쿼리에 가장 가까운 클러스터만 탐색됩니다. 매개변수
nprobe 탐색할 클러스터 수를 제어합니다. 값이 높을수록 재현율이 높아집니다.
하지만 검색 속도가 느려집니다.
9.3 HNSW(계층적 탐색 가능 작은 세계)
HNSW 이는 현대 벡터 데이터베이스에서 가장 널리 사용되는 알고리즘입니다. 그것은 구축 각 노드가 이웃 노드에 연결된 벡터인 다중 레벨 탐색 가능 그래프 가장 가까운. 검색은 최상위 수준에서 시작됩니다(노드 수가 적고 연결이 길음). 기본 수준(모든 노드, 짧은 연결)으로 내려가서 범위가 좁아집니다. 연구분야를 점차적으로
FLAT (Brute Force):
Costruzione: O(n) | Ricerca: O(n*d) | Recall: 100%
Memoria: n*d*4 bytes | Aggiornamento: O(1)
Ideale per: <100k vettori, quando il recall perfetto e necessario
IVF (Inverted File):
Costruzione: O(n*k) | Ricerca: O(nprobe*n/k*d) | Recall: 90-99%
Memoria: n*d*4 bytes | Aggiornamento: richiede re-training
Parametri: nlist (cluster), nprobe (cluster esplorati)
Ideale per: 100k-10M vettori, quando serve controllo fine
HNSW (Hierarchical Navigable Small World):
Costruzione: O(n*log(n)) | Ricerca: O(log(n)*d) | Recall: 95-99.9%
Memoria: n*d*4 + grafo | Aggiornamento: O(log(n))
Parametri: M (connessioni), ef_construction, ef_search
Ideale per: qualsiasi scala, migliore latenza, più memoria
Product Quantization (PQ):
Comprime vettori 768-dim in ~64 bytes (12x compressione)
Recall inferiore ma enorme risparmio memoria
Combinabile con IVF: IVF-PQ per miliardi di vettori
선택에 대한 실용 가이드
| 규모 데이터세트 | 권장 알고리즘 | 전형적인 회상 |
|---|---|---|
| 10만 개 미만의 이동통신사 | 플랫(무차별 대입) | 100% |
| 10만~100만 | HNSW | 98-99% |
| 100만~100만 | HNSW 또는 IVF-HNSW | 95-99% |
| 100M - 1B | IVF-PQ 또는 DiskANN | 90-95% |
| >1B | 분산형 IVF-PQ(Milvus) | 85-95% |
10. 임베딩의 미세 조정
사전 학습된 모델은 일반적인 사용 사례에는 잘 작동하지만 도메인에는 적합합니다. 전문가(의학, 법률, 금융, 코드) 미세 조정 검색 품질을 크게 향상시킬 수 있습니다. 원리는 간단합니다. 도메인의 데이터와 어휘에 맞게 모델을 조정하세요.
10.1 미세 조정 시기
- 전문 용어: 도메인에서 일반 학습 데이터에 없는 기술 용어를 사용합니다.
- 다양한 의미론적 관계: 귀하의 도메인에서는 일반적으로 관련이 없는 단어가 밀접한 관계를 가지고 있습니다.
- 대표성이 낮은 언어: 기본 모델에서는 해당 언어로 된 텍스트가 충분하지 않습니다.
- 불량한 검색: 좋은 청크에도 불구하고 의미 검색 결과가 만족스럽지 않습니다.
10.2 대조 학습을 통한 구현
from sentence_transformers import (
SentenceTransformer,
InputExample,
losses,
evaluation,
)
from torch.utils.data import DataLoader
# Carica modello base
model = SentenceTransformer("all-MiniLM-L6-v2")
# Prepara i dati di training: coppie (frase_a, frase_b, similarità)
# similarità: 1.0 = semanticamente identiche, 0.0 = completamente diverse
train_examples = [
# Coppie positive (dominio medico)
InputExample(texts=["infarto del miocardio", "attacco cardiaco acuto"], label=0.95),
InputExample(texts=["ipertensione arteriosa", "pressione alta cronica"], label=0.90),
InputExample(texts=["cefalea tensiva", "mal di testa da stress"], label=0.85),
InputExample(texts=["diabete mellito tipo 2", "diabete dell'adulto"], label=0.90),
# Coppie negative
InputExample(texts=["infarto del miocardio", "frattura del femore"], label=0.10),
InputExample(texts=["ipertensione arteriosa", "gastrite cronica"], label=0.05),
# Aggiungi migliaia di coppie per risultati ottimali...
]
# DataLoader
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
# Loss function: Cosine Similarity Loss
train_loss = losses.CosineSimilarityLoss(model)
# Valutazione (opzionale ma consigliata)
eval_examples = [
InputExample(texts=["tachicardia sinusale", "battito cardiaco accelerato"], label=0.85),
InputExample(texts=["polmonite batterica", "infezione polmonare"], label=0.80),
]
evaluator = evaluation.EmbeddingSimilarityEvaluator.from_input_examples(
eval_examples, name="medical-eval"
)
# Fine-tuning
model.fit(
train_objectives=[(train_dataloader, train_loss)],
evaluator=evaluator,
epochs=3,
warmup_steps=100,
evaluation_steps=500,
output_path="models/medical-embedding-model",
)
print("Fine-tuning completato! Modello salvato.")
# Test del modello fine-tuned
finetuned_model = SentenceTransformer("models/medical-embedding-model")
emb1 = finetuned_model.encode("infarto miocardico acuto")
emb2 = finetuned_model.encode("attacco di cuore")
from sentence_transformers import util
score = util.cos_sim([emb1], [emb2])
print(f"Similarità post fine-tuning: {score.item():.4f}")
미세 조정을 위해 얼마나 많은 데이터가 필요합니까?
일반적으로 다음과 같습니다. 최소 1,000쌍 개선된 모습을 보려면, 5,000-10,000쌍 확실한 결과를 위해, 50,000개 이상의 쌍 최적의 결과를 위해. 데이터의 양보다 질이 더 중요합니다: 쌍 시끄럽거나 라벨이 잘못 지정되어 모델의 품질이 저하됩니다. 주석이 달린 데이터가 충분하지 않은 경우 다음과 같은 기술을 고려하십시오. 하드 네거티브 마이닝 또는 합성 세대 LLM으로.
11. 이탈리아어용 임베딩
대부분의 임베딩 모델은 주로 영어 텍스트를 대상으로 학습됩니다. 이탈리아어 애플리케이션의 경우 모델 선택이 특히 중요합니다. 보자 사용 가능한 옵션과 성능.
11.1 다국어 모델
다중 언어 모델은 수십 개의 언어를 동시에 학습합니다. 품질 이탈리아어의 경우 일반적으로 좋지만 영어의 성능보다는 낮습니다. 이탈리아어에 권장되는 모델은 다음과 같습니다.
- 다국어-e5-대형: 우수한 다국어 품질, 접두사 "query:"/"passage:"가 필요합니다.
- BGE-M3: 균일한 품질로 100개 이상의 언어 지원, 덴시티+스파스+콜베르 지원
- 다른 말로 표현-다국어-MiniLM-L12-v2: 50개 이상의 언어에 대한 적절한 속도/품질 절충
- 텍스트 임베딩-3-소형/대형(OpenAI): API를 통한 우수한 다국어 성능
11.2 품질 개선 전략
- 이탈리아 데이터 미세 조정: 이탈리아어로 주석이 달린 수천 쌍이라도 성능을 크게 향상시킬 수 있습니다.
- 데이터 증대: LLM을 사용하여 문서의 이탈리아어 버전 생성
- 하이브리드 검색: 모델의 언어적 약점을 보완하기 위해 의미 검색과 BM25(키워드) 결합
- 쿼리 번역: 고품질 영어 모델의 경우 삽입하기 전에 쿼리를 영어로 번역하세요(실용적인 해결 방법)
이탈리아어 연습 시험
이탈리아어로 된 프로젝트 템플릿을 선택하기 전에 항상 테스트를 수행하십시오. 귀하의 쿼리와 실제 문서가 포함된 매뉴얼입니다. 소규모 평가 데이터 세트 만들기 (예상 응답이 포함된 쿼리 50~100개), 정밀도@k 및 리콜@k를 측정합니다. 모델 영어에서 MTEB 점수가 높으면 성적이 훨씬 나빠질 수 있습니다. 이탈리아어로. MTEB 벤치마크에는 다국어 하위 작업이 포함되어 있습니다. - 확인 구체적으로 그.
12. RAG 컨텍스트의 임베딩
임베딩은 기반 각 RAG 시스템의 품질 전체 파이프라인의 검색은 품질에 직접적으로 좌우됩니다. 벡터 표현. 이것이 임베딩이 아키텍처에 통합되는 방식입니다. RAG가 완료되었습니다.
FASE DI INDICIZZAZIONE (offline):
Documenti ──> Chunking ──> [EMBEDDING MODEL] ──> Vettori ──> Vector DB
|
Stesso modello per query!
FASE DI QUERY (online):
Query utente ──> [EMBEDDING MODEL] ──> Vettore query
|
v
[VECTOR DB: similarity search]
|
v
Top-k chunk rilevanti
|
v
[LLM + Contesto] ──> Risposta con citazioni
REGOLE D'ORO:
1. Usare SEMPRE lo stesso modello per indicizzazione e query
2. La qualità degli embeddings determina la qualità del retrieval
3. Scegliere il modello in base al dominio e alla lingua
4. Normalizzare i vettori per cosine similarity
5. Monitorare retrieval precision e recall in produzione
RAG 시스템에서 가장 흔한 실수는 선택의 중요성을 과소평가하는 것입니다. 임베딩 모델. LLM 프롬프트 최적화에 몇 주를 투자하는 팀 그들은 종종 다른 임베딩 모델을 테스트한 적이 없습니다. 실제로는 all-MiniLM-L6-v2를 e5-large-v2 또는 BGE-M3과 같은 모델로 변경하면 15~25%의 검색률이 최종 답변의 품질에 직접적인 영향을 미칩니다.
RAG용 체크리스트 임베딩
- 데이터 세트에 대해 최소 3개의 서로 다른 모델을 테스트해 보셨나요?
- 쿼리와 예상 응답이 포함된 평가 세트가 있나요?
- 모델이 귀하의 언어를 충분한 품질로 지원합니까?
- 차원성이 스토리지 예산과 호환됩니까?
- 청크에 대한 최대 컨텍스트 길이(최대 토큰)가 적절합니까?
- 도메인에 대한 미세 조정을 고려해 보셨나요?
- 생산 과정에서 검색 품질을 모니터링하고 있습니까?
13. 생산 비용 및 규모 조정
자체 호스팅 임베딩과 클라우드 API 중 하나를 선택하면 비용에 상당한 영향을 미칩니다. 대기 시간 및 운영 복잡성에 대해 알아보세요. 취해야 할 핵심 요소를 분석해 보겠습니다. 정보에 근거한 결정.
Embedding API 비용 비교(2024~2025)
| 공급자 | 모델 | 1M 토큰 가격 | 치수 |
|---|---|---|---|
| 오픈AI | 텍스트 삽입-3-작은 | $0.02 | 1536년 |
| 오픈AI | 텍스트 삽입-3-대형 | $0.13 | 3072 |
| 코히어 | 삽입-v3 | $0.10 | 1024 |
| 자체 호스팅 | 모든-MiniLM-L6-v2 | GPU 비용 | 384 |
| 자체 호스팅 | BGE-M3 | GPU 비용 | 1024 |
13.1 셀프호스팅 API와 클라우드 API
- 클라우드 API(OpenAI, Cohere): 제로 인프라, 종량제 결제, 네트워크 대기 시간, 외부 서비스에 대한 의존성, 평가 대상 데이터 개인 정보 보호
- 자체 호스팅(전용 GPU): 높은 초기 비용(GPU ~$1-3/시간), 토큰당 비용 없음, 최소 대기 시간, 전체 데이터 제어, 운영 복잡성
경험 법칙: 월 1,000만 개 미만의 토큰에 대해 클라우드 API 그들은 더 저렴합니다. 월간 토큰 1억 개가 넘으면 자체 호스팅이 가능해집니다. 편리하다. 10M에서 100M 사이는 필요한 대기 시간 및 개인 정보 보호 요구 사항에 따라 다릅니다.
13.2 벡터 저장 비용
각 통신사가 점유하는 dimensioni * 4 bytes (float32). 768 모델의 경우
크기 및 100만 개의 문서: 768 * 4 * 1M = ~3GB 벡터 메모리만,
메타데이터, 인덱스 및 오버헤드를 추가해야 합니다. 제품 수량화를 사용하면 다음을 수행할 수 있습니다.
최대 8~16x까지 압축하지만 재현율이 손실됩니다.
빠른 비용 견적
100만 개의 문서가 있는 RAG 시스템의 경우 청크당 500개의 토큰으로 청크합니다. 768-dim 모델: 벡터에는 약 3GB의 RAM이 필요하고 HNSW 인덱스에는 2-4GB가 필요합니다. 8GB RAM(약 $50-100/월)을 갖춘 클라우드 인스턴스이면 충분합니다. 세대를 위해 OpenAI API를 사용한 초기 임베딩(text-embedding-3-small): 약 5억 개의 토큰 = 10달러. 자체 호스팅 모델 및 T4 GPU 사용: 약 2~3시간의 컴퓨팅 = $3~5.
결론 및 다음 단계
이 기사에서 우리는 텍스트 임베딩의 전체 진화를 다루었습니다. BERT의 문맥 임베딩을 통해 Word2Vec 및 GloVe의 정적 단어 임베딩, 의미 검색에 최적화된 최신 문장 변환기까지. 우리는 바닐라 BERT가 검색에 적합하지 않은 이유와 SBERT가 문제를 해결하는 방법을 확인했습니다. 바이 인코더 아키텍처를 사용합니다.
우리는 Python, 문장 변환기를 사용하여 완전한 의미 검색 엔진을 구현했습니다. FAISS, 일괄 처리 및 대규모 데이터 세트에 대한 전략을 탐색합니다. 우리는 비교했다 주요 벡터 데이터베이스 및 인덱싱 알고리즘, 장단점 분석 속도, 리콜, 메모리 사이.
기억해야 할 핵심 사항:
- 임베딩 모델을 선택하는 것이 가장 중요한 결정입니다. RAG 시스템에서 - LLM 모델보다 더 중요
- 문장 변환기(바이인코더) 검색의 표준입니다. 순위 재지정을 위한 BERT 크로스 인코더
- 점점 더 많은 모델을 테스트하세요. 선택하기 전에 실제 데이터 세트에 대해
- FAISS와 HNSW 수백만 개의 벡터에 대한 검색을 효율적이고 실현 가능하게 만듭니다.
- 미세 조정 전문 도메인의 품질을 획기적으로 향상시킬 수 있습니다.
- 이탈리아어의 경우, 다국어 모델(BGE-M3, multilingual-e5)을 사용하고 미세 조정을 고려합니다.
다음 기사에서
시리즈의 세 번째 기사에서는 벡터 데이터베이스: 내부 아키텍처, 고급 인덱스 구성, 샤딩 전략 복제, 메타데이터 필터링 및 적합한 데이터베이스를 선택하는 방법 귀하의 사용 사례. Qdrant, ChromaDB 및 pgVector를 사용한 완전한 구현을 볼 수 있습니다.







