기술 트렌드 & 새로운 학습
코사인 유사도 검색
by DoRightting
2024. 11. 18.
1. 프로젝트 내 구현 사례
1.1 CustomVectorRepository에서의 코사인 유사도 검색
@Repository
@RequiredArgsConstructor
public class CustomVectorRepository {
private final JdbcTemplate jdbcTemplate;
public List<Prompt> findSimilarPrompts(double[] vector, double threshold, int limit) {
String sql = """
SELECT * FROM prompts
WHERE embedding_vector IS NOT NULL
AND 1 - (embedding_vector <=> cast(? as vector(1536))) > ?
ORDER BY 1 - (embedding_vector <=> cast(? as vector(1536))) DESC
LIMIT ?
""";
// PostgreSQL의 <=> 연산자는 코사인 거리를 계산
// 1 - (코사인 거리) = 코사인 유사도
String vectorStr = vectorToString(vector);
return jdbcTemplate.query(sql, promptRowMapper,
vectorStr, threshold, vectorStr, limit);
}
}
1.2 OpenAI 임베딩과 연동
@Service
public class PromptService {
private final OpenAiApi openAiApi;
private final CustomVectorRepository vectorRepository;
public List<PromptResponse> findSimilarPrompts(String promptText) {
// OpenAI API를 통해 텍스트를 벡터로 변환
double[] embedding = openAiApi.embeddings(promptText);
// 코사인 유사도로 유사한 프롬프트 검색
List<Prompt> similarPrompts = vectorRepository
.findSimilarPrompts(embedding, 0.8, 10);
return similarPrompts.stream()
.map(this::buildPromptResponse)
.collect(Collectors.toList());
}
}
1.3 벡터 정규화 및 저장
-- PostgreSQL에서의 벡터 정규화 및 유사도 계산
CREATE INDEX prompt_vector_idx ON prompts
USING ivfflat (embedding_vector vector_l2_ops)
WITH (lists = 100);
프로젝트에서 코사인 유사도를 선택한 이유
- 텍스트 임베딩의 특성
- 방향성 중심의 유사도 측정
- 벡터 크기의 영향 최소화
- // OpenAI 임베딩은 이미 정규화되어 있어 코사인 유사도가 적합 double[] embedding = openAiApi.embeddings(promptText);
- 성능적 이점
- PostgreSQL의 효율적인 연산자 지원
- 인덱스를 통한 빠른 검색
- -- PGVector의 최적화된 코사인 거리 연산자 활용 1 - (embedding_vector <=> cast(? as vector(1536)))
코사인 유사도 검색의 성능 최적화
- 인덱스 최적화
- IVFFlat 인덱스로 검색 속도 향상
- 적절한 lists 파라미터 설정
- CREATE INDEX prompt_vector_idx ON prompts USING ivfflat (embedding_vector vector_l2_ops) WITH (lists = 100);
- 쿼리 최적화
- public List<Prompt> findSimilarPrompts(double[] vector, double threshold, int limit) { // 임계값과 결과 수 제한으로 성능 최적화 String sql = "... LIMIT ?"; }
유사도 임계값 결정
- 실험적 분석
- // 임계값 0.8 설정 List<Prompt> similarPrompts = vectorRepository .findSimilarPrompts(embedding, 0.8, 10);
- 사용자 요구사항 반영
- // 필요에 따라 임계값 조정 가능한 설계 public List<Prompt> findSimilarPrompts( double[] vector, double threshold, // 조정 가능한 임계값 int limit )
코사인 유사도의 정확도 검증
- 테스트 케이스
- @Test public void testSimilarityAccuracy() { // 알려진 유사 프롬프트로 테스트 String prompt1 = "게임 캐릭터 디자인"; String prompt2 = "게임 케릭터 일러스트"; double[] embedding1 = openAiApi.embeddings(prompt1); double[] embedding2 = openAiApi.embeddings(prompt2); // 높은 유사도 기대 List<Prompt> results = vectorRepository .findSimilarPrompts(embedding1, 0.8, 10); assertTrue(results.stream() .anyMatch(p -> p.getOriginalPrompt().equals(prompt2))); }
- 품질 모니터링
- // 검색 결과 로깅 및 분석 log.info("Similarity score: {}, Prompts: {} <-> {}", similarityScore, prompt1, prompt2);
벡터 정규화 처리
- 임베딩 처리
- OpenAI가 제공하는 정규화된 벡터 사용
- 추가 정규화 불필요
- public double[] embeddings(String text) { // OpenAI API가 반환하는 임베딩은 이미 정규화되어 있음 OpenAiEmbeddingResponse response = callOpenAiApi(text); return response.getData().get(0).getEmbedding(); }
- 저장 및 검색
- PGVector의 벡터 타입 활용
- 정규화 상태 유지
- -- PGVector가 벡터 정규화 상태 유지 embedding_vector vector(1536)
대규모 데이터에서의 성능 보장
- 인덱스 최적화
- IVFFlat 인덱스 활용
- 적절한 파라미터 튜닝
- -- 효율적인 검색을 위한 인덱스 설정 CREATE INDEX prompt_vector_idx ON prompts USING ivfflat (embedding_vector vector_l2_ops) WITH (lists = 100);
- 페이지네이션
- // 대량 데이터 처리를 위한 페이지네이션 public List<Prompt> findSimilarPromptsPaged( double[] vector, double threshold, int page, int size ) { int offset = page * size; String sql = sql + " OFFSET ? LIMIT ?"; }