해당 포스트는 다음 내용을 포함하고 있습니다.
- 기초 모델의 dense 벡터 임베딩을 사용한 의미 검색
- 검색 증강 생성(RAG)
- 검색과 대규모 언어모델(LLM)을 결합한 질의 응답 및 요약
- Transformer 기반 LLM 파인튜닝
- 사용자 신호 및 벡터 임베딩을 기반으로 한 개인화된 검색
- 사용자 행동 신호 수집 및 신호 강화 모델 구축
- 도메인별 학습을 위한 의미 지식 그래프
- 멀티모달 검색(텍스트, 이미지 및 기타 유형에 대한 하이브리드 쿼리)
- 일반화 가능한 머신러닝 순위 모델 구현(순위 매기기 학습)
- 머신러닝 순위를 자동화하기 위한 클릭 모델 구축
- ANN 검색, 양자화, 표현 학습, 바이 인코더 대 크로스 인코더와 같은 vector search 최적화 기술
- 생성 검색, 하이브리드 검색 및 검색 프런티어
TL;DR AI 기반 검색은 현대 애플리케이션에서 사용자 경험을 개선하기 위한 필수 요소로 자리잡고 있으며, 도메인 인식, 개인화, 멀티모달 처리, 지능형 검색 등을 통해 사용자 의도를 보다 정확하게 파악 자동화된 학습 기술을 활용하여 검색 최적화를 구현하고자 하는 모든 조직에 필수적인 가이드로 작용
AI 기반 검색 시스템의 작동 방식 및 흐름을 시각적으로 표현한 다이어그램
이 섹션에서는 검색과 추천을 별도의 기능 및 팀으로 분리할 때 발생할 수 있는 비효율적인 결과에 대해 설명
Figure 1.1 일반적인 검색 경험을 보여주는 이미지로, 사용자가 쿼리를 입력하고 필터링 옵션을 통해 검색 결과를 세분화할 수 있는 모습
1.1 사용자 의도 검색
1.1.1 검색 엔진
기술적인 요구 사항이 높기 때문에, 검색 엔진 개발은 여러 전문가와 협업이 필요 검색 엔진은 사용자 의도를 완전히 해석하기 위해 콘텐츠, 사용자, 도메인에 대한 철저한 이해가 필요 이는 추천 엔진 관련 주제를 논의한 후 다시 다룰 것임.
p9
검색 엔진은 대규모 확장성을 염두에 두고 설계되며, 분산 시스템과 동시성, 데이터 구조, 운영 체제, 고성능 컴퓨팅에 대한 깊은 이해를 요구 검색 엔진은 인버티드 인덱스와 같은 검색 전용 데이터 구조를 구축하고, 선형 대수학과 벡터 유사도 점수, 텍스트 분석 및 자연어 처리에 대한 경험이 필요
검색 엔진의 성능과 관련성 향상을 위해, 검색 팀은 랭킹 모델, A/B 테스트, 클릭 모델, 피처 엔지니어링, 쿼리 해석 및 의도 발견과 같은 도구를 사용 검색 품질을 평가하고 개선하기 위해 데이터 분석가, DevOps 엔지니어, 제품 관리자 등 다양한 전문가의 협력이 필요
p10
추천 엔진은 일반적으로 사용자가 직접 입력하지 않고, 사용자에 대해 알고 있는 정보를 바탕으로 콘텐츠를 제공하는 시스템으로 인식됨. 하지만 이는 과도한 단순화이며, 실제로는 사용자의 브라우징 및 구매 이력을 기반으로 개인화된 콘텐츠를 제공
Figure 1.2 사용자의 구매 패턴에 기반하여 유사한 항목을 추천하는 모습
p11
추천 알고리즘은 보통 사용자와 항목 간의 속성을 공유함으로써 새로운 콘텐츠를 추천 예를 들어, 구직 웹사이트에서 사용자는 “직업명”, “산업”, “연봉 범위”, “경력”, “기술”과 같은 속성을 가질 수 있음. 콘텐츠 기반 추천 알고리즘은 이런 속성을 분석하여 사용자가 원하는 속성과 가장 잘 맞는 작업을 추천 이를 사용자-항목 추천이라고
Figure 1.3 사용자가 관심을 가진 항목의 속성을 기반으로 유사한 항목을 추천하는 콘텐츠 기반 추천 시스템의 예
TL;DR
#
ai-powered-search/
패션 상품 검색에 AI 기반의 검색 및 추천 시스템을 적용하면, 사용자의 구매 및 브라우징 이력, 선호 스타일 등의 데이터를 기반으로 개인화된 상품을 추천할 수 있음. 예를 들어, 사용자가 특정 스타일의 옷을 검색하면, 유사한 스타일이나 색상, 브랜드의 다른 상품을 추천하여 구매 경험을 향상시킬 수 있음. 이런 시스템은 이미지 메타정보를 활용하여 비슷한 패턴이나 색상의 상품을 자동으로 탐색하고 추천함으로써 사용자 만족도를 높일 수 있음.
아이템-아이템 추천의 개념은 콘텐츠 기반 추천 시스템에서 모든 추천을 설명할 수 있는 중요한 개념임. 각 아이템은 추천받는 다른 엔티티들과 속성을 공유하는 임의의 엔티티로 간주됨.
협업 필터링의 피쳐
Figure 1.4 여러 사용자 간의 행동 신호의 겹침을 활용한 협업 필터링 기반 추천 예시
Figure 1.5 콘텐츠 기반 매칭과 협업 필터링을 결합한 다중 모드 추천 알고리즘의 작동 방식
핵심 콘텐츠 기반 추천에서 아이템 간 유사성을 활용하여 추천하는 방법을 설명하고, 협업 필터링을 통해 사용자 행동 데이터를 기반으로 더 나은 추천을 제공할 수 있음을 보임. 다만, 콜드 스타트 문제를 해결하기 위해 다중 모드 추천 시스템을 도입하여 콘텐츠 기반과 행동 기반 추천의 Pros(+)을 결합
사례
Figure 1.6 개인화 스펙트럼, 전통적 키워드 검색과 전통적 추천의 연속체
핵심 검색 엔진과 추천 엔진은 사용자 요청을 처리하는 방식에서 차이가 있으며, 개인화된 접근을 통해 사용자 프로필을 활용하여 더 나은 결과를 제공할 수 있음. 개인화 스펙트럼을 통해 다양한 정보 검색 방법을 설명
검색과 추천 시스템은 종종 별개의 문제로 인식되며 이는 DataScience Team과 Engineering Team 간의 분리된 개발로 이어지고, 각 팀은 서로 다른 목표를 가지고 시스템을 구축하게 되는 문제가 발생
Conway’s Law: 조직은 자신의 커뮤니케이션 구조를 반영한 시스템을 설계 이는 검색과 추천 간의 분리를 심화시킴 → 결과적으로 통합된 개인화 스펙트럼을 최적화하기 어려움.
TL;DR
- 검색과 추천을 별개로 인식하면 통합적 성능 저하 발생.
- Conway’s Law에 따라 조직 구조가 시스템 설계에 영향을 미침.
- 통합적 접근 필요.
의미론적 검색은 도메인에 대한 깊은 이해를 요구하며, 지식 그래프를 활용하여 쿼리의 의미를 파악
쿼리 내의 엔티티를 이해하여 ‘문자열’이 아닌 ‘사물’을 검색하는 과정을 시각화한 이미지
대기업들은 엔티티 관계를 이해하기 위해 사전과 지식 그래프를 수작업으로 구축해 왔으나, AI 기반 검색 엔진은 이런 정보를 자동으로 학습하게 되고, 이는 확장 가능한 솔루션을 제공할 수 있음을 의미
TL;DR
- 의미론적 검색은 도메인 지식 필요.
- AI 기반 검색 엔진은 자동화된 학습으로 도메인 지식 획득.
- 지식 그래프는 쿼리의 의미를 효과적으로 파악하는 데 기여.
사용자 의도를 이해하기 위해서는 세 가지 요소가 필요함: 콘텐츠 이해, 사용자 이해, 도메인 이해.
사용자 의도를 이해하기 위한 세 가지 요소 간의 상호작용
이 요소들이 통합되면, AI 기반 검색은 모든 사용자 쿼리에 대해 전문가 수준의 이해와 매칭을 수행 가능
TL;DR
- 사용자 의도 이해에 필요한 세 가지 요소: 콘텐츠, 사용자, 도메인 이해.
- 이들 요소의 통합은 AI 기반 검색의 성능을 극대화
- 전문가 수준의 이해와 매칭 가능.
AI 기반 검색 시스템을 구축하기 위한 주요 기술
추가적으로 고려할 수 있는 검색 엔진
TL;DR
- AI 기반 검색을 위한 주요 기술: Python, Spark, Docker 등
- 다양한 검색 엔진 옵션 제공
- 책의 예제는 이런 기술을 기반으로 설명
TL;DR
해당 기법을 통해 대규모 패션 상품 데이터베이스에서 사용자에게 적합한 상품을 빠르게 추천할 수 있음.
TL;DR
- OpenSearch는 Amazon에 의해 개발된 Elasticsearch의 오픈 소스 포크
- Lucidworks Fusion은 AI 기반 검색 기능을 즉시 제공하는 상업 솔루션
- Milvus와 Vespa는 벡터 및 텍스트 검색 기능을 결합한 차세대 검색 엔진
검색 지능의 전형적인 발전 과정, 기본 키워드 검색에서 시작해 자가 학습 검색 플랫폼으로 발전
이 책은 AI 기반 검색의 다양한 기술을 다루며, 독자가 이런 기술을 효과적으로 활용할 수 있도록 돕는 것을 목표로
TL;DR 검색 기능의 개선은 도메인 이해 주입에서 시작하며, 사용자 쿼리 해석 및 의도 이해가 중요 전통적 키워드 검색이 기초를 제공하며, AI 기반 검색 엔진은 Reflected Intelligence를 통해 지속적인 학습과 개선을 가능하게
TL;DR 피드백 루프는 검색 품질을 지속적으로 개선하며, 사용자 신호와 콘텐츠는 AI 기반 검색 엔진의 지능 엔진을 구동하는 두 가지 주요 요소임. 신호는 사용자의 상호작용을 기록하고, 콘텐츠는 도메인을 이해하는 데 중요한 역할을
이런 방식으로, 패션 상품 검색에 AI 기반 검색 엔진을 적용하면 사용자의 실제 클릭 및 구매 데이터를 활용해 추천 결과를 지속적으로 개선할 수 있음
p32
p33
p34
p35
추가 자료 및 예시
p36
p37
p38
p39
TL;DR
- 비정형 데이터는 텍스트의 의미를 충분히 설명하지 못하며, 구조화된 데이터와 유사한 규칙과 구조를 따름
- 다양한 비정형 데이터 유형은 텍스트뿐 아니라 이미지, 오디오, 비디오를 포함하여 검색 엔진에서 처리됨
- 구조화된 데이터와 비정형 데이터 간의 유사한 조인 기법을 통해 관계 발견 가능
이와 같이, 비정형 데이터는 본래 구조가 없다고 여겨지지만 실제로는 다양한 규칙과 구조를 포함하고 있으며, 이를 통해 검색 및 데이터 처리에 적용할 수 있는 다양한 방법이 존재
p40
TL;DR
- 동일한 용어가 여러 문서에서 사용되며, 이는 비구조적 데이터의 외래 키로 작용
- 비구조적 데이터는 전통적 데이터베이스보다 더 유연
- 데이터는 강력한 문맥적 관계를 통해 연결될 수 있음.
p41
TL;DR
- Fuzzy Foreign Key는 유연한 엔티티 결합을 가능하게
- 자연어 처리 기술이 필요
- 동일한 엔티티를 여러 방식으로 해석하여 문서 간 결합 가능.
p42
TL;DR
- 이미지와 비디오 참조도 동일 엔티티로 해결 가능
- 다의어 문제는 검색 응용에서 큰 문제로 작용
- 문맥 기반의 차이 구별이 필요
p43
TL;DR
- 다의어 문제는 검색 정확도에 큰 영향을 미치며,
- AI는 문맥을 통해 의미 구별을 수행해야 함
- 비구조적 데이터는 많은 관계를 포함한 그래프로 이해 가능
Figure 2.8: 관계의 대규모 그래프
TL;DR
- 문서 간의 엔티티 연결을 통해 대규모 관계 그래프 형성.
- AI 기반 검색 플랫폼에서 이런 그래프를 활용하여 지능적 통찰력 학습.
- 역방향 인덱스를 통해 추가적 모델링 없이 그래프 탐색 가능.
Figure 2.9: 자연어의 의미적 데이터 인코딩
TL;DR
- 자연어는 문자부터 코퍼스까지의 계층적 구조로 구성.
- 용어 시퀀스는 순차적 용어의 조합이며, 구는 그 특별한 형태.
- 텍스트 분석을 통해 문서의 의미를 더 명확히 이해 가능.
c?o
: “CEO”, “CMO”, “CFO” 등 매칭"VP Engineering"~2
: “VP Engineering”, “VP of Engineering” 등 매칭(Microsoft OR MS) AND Word
TL;DR
- 분포 의미론: 비슷한 맥락의 단어는 유사한 의미를 가짐.
- 검색 엔진에서 분포 의미론 활용하여 문서의 맥락 이해 가능.
- 역방향 인덱스를 통한 복잡한 쿼리 지원으로 다양한 검색 가능.
p48
TL;DR
- 벡터는 항목의 속성을 설명하는 값의 목록으로, 다양한 항목을 동일한 벡터 공간 내에서 표현 가능
- 단어 임베딩은 용어의 의미를 수치 벡터로 표현하여 의미적으로 관련 있는 문서를 찾는 데 사용됨.
- 유사성 계산을 통해 벡터 간의 관계를 평가하고, 관련 항목을 효과적으로 탐색 가능
p49
p50
p51
TL;DR
- 쿼리와 문서 간의 유사성은 벡터 스코어링으로 평가되며, 정확한 키워드 일치에는 한계가 있음.
- 차원 축소된 단어 임베딩을 통해 의미적으로 관련 있는 항목을 효과적으로 탐색 가능.
- 벡터 스코어링은 검색 엔진에서 관련 문서를 식별하는 데 중요한 역할을 수행
위 내용을 바탕으로 벡터 및 단어 임베딩의 중요성과 활용 방법에 대해 이해할 수 있음. 더욱 심도 있는 내용은 이후 장에서 다룰 예정임.
TL;DR
지식 모델링과 자연어 이해는 검색 시스템의 정확성을 높이는 데 필수적임. 다양한 모델과 기법을 조합하여 더 나은 검색 결과를 제공할 수 있음.
위 내용은 지식 모델링과 자연어 이해의 다양한 기법과 챌린지를 논리적으로 설명하였음. 각 기법의 정의와 활용 방안을 통해 검색 시스템의 성능 향상을 도모
TL;DR
이런 방식으로 AI 기반의 검색은 사용자에게 보다 개인화되고 정확한 검색 결과를 제공할 수 있음.
Content and User Signals
→ 콘텐츠와 사용자 신호를 효과적으로 활용하는 것은 AI 기반 검색 애플리케이션의 성능을 크게 향상시킴
→ 콘텐츠가 풍부하거나 사용자 신호가 많거나, 최적의 경우 둘 다 풍부하면 더욱 스마트한 검색 애플리케이션 구현 가능
Unstructured Data as Hyper-structured Data
→ 비구조적 데이터는 사실상 도메인 특정 지식을 나타내는 거대한 그래프와 같음
→ 검색 엔진은 분포 의미론을 활용하여 텍스트의 의미 관계를 해석하고, 이를 통해 의미를 해석할 수 있음
Search Engine Functions
→ 콘텐츠를 수집하고, 쿼리와 일치하는 콘텐츠를 반환하며, 그 콘텐츠를 쿼리와의 일치 정도에 따라 정렬함
→ 일치 정도는 일반적으로 쿼리 키워드의 문서 내 일치도를 기반으로 한 점수로 측정됨
Controlling Relevance
→ 관련성 계산은 유연하게 구성 가능하며, 쿼리별로 조정 가능함
→ 도메인별 및 사용자별 관련성 랭킹 기능을 구현할 수 있음
Mapping Text to Vectors
→ 쿼리와 문서를 벡터로 변환하여 유사성을 측정함
→ 각 단어는 벡터의 다양한 차원을 나타내며, 쿼리의 경우 관련 단어에만 1의 값을 부여하고 나머지는 0으로 설정
Cosine Similarity Calculation
→ 두 벡터 간의 코사인 각도를 계산하여 유사성을 측정함
→ 예를 들어, “apple juice”라는 쿼리는 관련 단어의 위치에 1을 부여하는 벡터로 표현됨
Example Implementation
→ 이미지 및 메타 데이터를 기반으로 패션 상품 검색을 수행할 수 있음
→ 예를 들어, 특정 패션 아이템의 이미지가 주어지면, 유사한 아이템을 찾기 위해 해당 이미지의 피쳐을 벡터로 변환하여 검색함
Utilized Models
→ Convolutional Neural Networks (CNNs) 등 최신 이미지 분석 모델을 활용하여 이미지 피쳐 추출
→ 추출된 벡터를 통해 유사성을 계산하고, 관련 상품을 추천함
본 블로그 포스트는 검색 엔진의 핵심 기술과 이를 패션 상품 검색에 적용할 수 있는 방법을 논리적으로 정리하였음. 이미지와 메타 데이터를 기반으로 한 검색 기술은 다양한 분야에서의 활용 가능성을 보여줌.
p64
문서를 순위화하기 위해, 문서와 쿼리 간의 코사인 유사도를 계산하는 과정을 사용 이 코사인 값이 각 문서의 관련성 점수가 되며, 이 점수를 기준으로 문서들을 정렬할 수 있음.
Listing 3.1 코드는 쿼리와 문서 벡터를 어떻게 표현하고, 각각의 문서와 쿼리 간의 코사인 유사도를 계산하는지를 보여줌.
Listing 3.1: 쿼리와 문서 벡터 사이의 코사인 유사도 계산
결과: 흥미로운 점은 두 문서가 전혀 다른 내용을 가지고 있음에도 불구하고 동일한 관련성 점수를 받았다는 것임. 이는 즉각적으로 이해하기 어려울 수 있으므로, 의미 있는 피쳐들만을 집중하여 계산을 단순화해 보겠음.
3.1.2 dense 벡터 표현 간의 유사도 계산
query_vector = np.array([0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
doc1_vector = np.array([0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0])
doc2_vector = np.array([1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1])
def cos_sim(vector1, vector2): return dot(vector1, vector2) / (norm(vector1) * norm(vector2))
doc1_score = cos_sim(query_vector, doc1_vector) doc2_score = cos_sim(query_vector, doc2_vector)
print(“Relevance Scores:\n doc1: “ + num2str(doc1_score) + “\n doc2: “ + num2str(doc2_score))
결과
doc1: 0.2828
doc2: 0.2828
TL;DR
- 문서와 쿼리 벡터 간의 코사인 유사도를 통해 문서의 관련성 점수를 계산
- dense 벡터는 모든 가능한 차원에 대한 값을 포함하므로 계산에 비효율적일 수 있음.
- 의미 있는 피쳐만을 포함하는 희소 벡터를 사용하여 계산을 단순화할 수 있음.
p65
이전 섹션의 계산을 이해하기 위해서는 쿼리와 문서 모두에 공통적으로 나타나는 피쳐만이 중요하다는 점을 이해해야 다른 피쳐들(쿼리에 없는 단어들)은 문서의 순위에 영향을 주지 않음. 결과적으로, 의미 없는 항목을 벡터에서 제거하여 희소 벡터 표현으로 변환 가능
희소 벡터 표현은 계산에 필요한 값만 포함하며, 불필요한 0 값을 제거 이는 검색 엔진의 스코어링 작업에서 효율적임.
Listing 3.2는 의미 있는 항목만 포함한 희소 벡터를 사용하여 쿼리와 문서 벡터 간의 코사인 유사도를 계산하는 방법을 보여줌.
Listing 3.2: 희소 쿼리와 문서 벡터 간의 코사인 유사도 계산
sparse_query_vector = [1, 1] #[apple, juice]
sparse_doc1_vector = [1, 1]
sparse_doc2_vector = [1, 1]
doc1_score = cos_sim(sparse_query_vector, sparse_doc1_vector)
doc2_score = cos_sim(sparse_query_vector, sparse_doc2_vector)
print("Relevance Scores:\n doc1: " + num2str(doc1_score) + "\n doc2: " +
num2str(doc2_score))
p66
희소 벡터 계산은 두 문서가 동일한 상대적 점수를 얻지만, 실제 점수는 1.0임. 이는 코사인 계산에서 벡터가 완벽하게 일치함을 의미
흥미로운 점
검색 엔진은 벡터의 각 피쳐을 1(존재) 또는 0(비존재)로 고려하지 않고, 각 피쳐이 얼마나 잘 일치하는지를 기반으로 점수를 제공
결과
doc1: 1.0
doc2: 1.0
TL;DR
- 희소 벡터 표현은 계산 효율성을 높이고 관련성 점수를 쉽게 비교할 수 있게
- 코사인 유사도를 사용할 때 쿼리와 문서 간의 피쳐 가중치가 중요
- 검색 엔진은 단순한 존재 여부가 아닌 일치도를 기반으로 점수를 조정
p67
이전 섹션의 문제는 용어 벡터의 피쳐이 단순히 단어의 존재 여부만을 나타낸다는 것임. 이는 두 문서(doc1, doc2)가 항상 동일한 코사인 유사도 점수를 가지게 만듦.
용어 빈도(term frequency)를 사용하여 문서가 단어를 얼마나 잘 대표하는지를 측정 가능 특정 문서에서 용어가 더 자주 나타날수록, 해당 문서가 쿼리와 더 관련이 있을 가능성이 높음.
Listing 3.3은 용어 빈도 기반의 벡터로 코사인 유사도를 계산하는 방법을 보여줌.
Listing 3.3: Raw term counts 기반의 용어 빈도 벡터의 코사인 유사도
doc1_tf_vector = [1, 1] #[apple:1, juice:1]
doc2_tf_vector = [3, 4] #[apple:3, juice:4]
query_vector = [1, 1] #[apple:1, juice:1]
doc1_score = num2str(cos_sim(query_vector, doc1_tf_vector))
doc2_score = num2str(cos_sim(query_vector, doc2_tf_vector))
print("Relevance Scores:\n doc1: " + str(doc1_score) + "\n doc2: " +
str(doc2_score))
결과:
doc1: 1.0
doc2: 0.9899
TL;DR
- 용어 빈도는 문서가 쿼리와 얼마나 잘 일치하는지를 나타내는 중요한 지표로
- 용어 빈도를 사용하여 코사인 유사도 점수를 조정하면 문서의 관련성을 더 정확하게 측정할 수 있음.
- 다른 스코어링 함수로 전환하여 피쳐 가중치의 증가에 따라 점수가 증가하도록 조정할 수 있음.
Figure 3.3 Term Frequency Calculation.
p72
정규화된 TF(Term Frequency) 도입으로 인해 doc2가 가장 높은 순위를 기록, 이는 예상된 결과임. 이는 doc1이 특정 단어를 너무 많이 포함하여 발생하는 가중치 감소 효과 때문임. 그러나 doc1이 여전히 두 번째로 높은 순위를 차지, 개선된 TF 함수가 doc3를 상위로 끌어올리기에 부족했음.
단어 빈도(TF)는 문서와 쿼리의 일치도를 측정하는 데 유용하지만, 각 단어의 중요성을 구별하는 데 한계가 있음. 이 섹션에서는 특정 키워드의 중요성을 문서 빈도(DF)를 통해 평가하는 방법을 설명
문서 빈도 계산을 나타냄. 모든 문서 집합 D와 입력 단어 t가 주어지며, DF는 입력 단어를 포함하는 문서의 수를 나타냄. 숫자가 낮을수록 쿼리에서 중요성이 더 크다는 것을 의미
labels: [the, cat, in, the, hat]
doc1_vector: [0.0942, 0.0, 0.0769, 0.0942, 0.0]
doc2_vector: [0.0456, 0.0456, 0.0456, 0.0456, 0.0456]
doc3_vector: [0.0, 0.0385, 0.0544, 0.0, 0.0385]
query_vector: [0.0942, 0.0456, 0.0769, 0.0942, 0.0456]
Relevance Scores:
doc2: 0.9222
doc1: 0.9559
doc3: 0.5995
p73
수식: $$ \text{IDF}(t) = 1 + \log\left(\frac{ | D | }{\text{DF}(t) + 1}\right) $$ |
역문서 빈도 계산을 나타냄. | D | 는 전체 문서 수, t는 단어, DF(t)는 해당 단어를 포함하는 문서 수임. 숫자가 낮을수록 단어의 중요성이 적음을 의미하며, 숫자가 높을수록 쿼리의 관련성 점수에 더 큰 영향을 미침. |
df_map = {"the": 9500, "cat": 100, "in":9000, "hat":50}
totalDocs = 10000
def idf(term):
return 1 + np.log(totalDocs / (df_map[term] + 1))
idf_vector = np.array([idf("the"), idf("cat"), idf("in"), idf("the"), idf("hat")])
print ("labels: [the, cat, in, the, hat]\nidf_vector: " + vec2str(idf_vector))
labels: [the, cat, in, the, hat]
idf_vector: [1.0512, 5.5952, 1.1052, 1.0512, 6.2785]
p74
TF와 IDF의 결합으로 균형 잡힌 관련성 점수인 TF-IDF를 생성할 수 있음.
TF-IDF는 문서와 쿼리의 텍스트 유사성을 평가하는 데 널리 사용됨. 이는 두 요소를 결합하여 텍스트 기반 관련성 점수를 계산
p75
코드 예제: 쿼리 “the cat in the hat”에 대한 TF-IDF 순위 계산
def tf_idf(tf, idf):
return tf * idf**2
query = "the cat in the hat"
print ("labels: [the, cat, in, the, hat]")
doc1_tfidf = [
tf_idf(tf(doc1, "the"), idf("the")),
tf_idf(tf(doc1, "cat"), idf("cat")),
tf_idf(tf(doc1, "in"), idf("in")),
tf_idf(tf(doc1, "the"), idf("the")),
tf_idf(tf(doc1, "hat"), idf("hat"))
]
print("doc1_tfidf: " + vec2str(doc1_tfidf))
doc2_tfidf = [
tf_idf(tf(doc2, "the"), idf("the")),
tf_idf(tf(doc2, "cat"), idf("cat")),
tf_idf(tf(doc2, "in"), idf("in")),
tf_idf(tf(doc2, "the"), idf("the")),
tf_idf(tf(doc2, "hat"), idf("hat"))
]
print("doc2_tfidf: " + vec2str(doc2_tfidf))
doc3_tfidf = [
tf_idf(tf(doc3, "the"), idf("the")),
tf_idf(tf(doc3, "cat"), idf("cat")),
tf_idf(tf(doc3, "in"), idf("in")),
tf_idf(tf(doc3, "the"), idf("the")),
tf_idf(tf(doc3, "hat"), idf("hat"))
]
print("doc3_tfidf: " + vec2str(doc3_tfidf))
query_tfidf = np.maximum.reduce([doc1_tfidf, doc2_tfidf, doc3_tfidf])
doc1_relevance = cos_sim(query_tfidf,doc1_tfidf)
doc2_relevance = cos_sim(query_tfidf,doc2_tfidf)
doc3_relevance = cos_sim(query_tfidf,doc3_tfidf)
print("\nRelevance Scores:\n doc2: " + num2str(doc2_relevance)
+ "\n doc3: " + num2str(doc3_relevance)
+ "\n doc1: " + num2str(doc1_relevance))
labels: [the, cat, in, the, hat]
doc1_tfidf: [0.1041, 0.0, 0.094, 0.1041, 0.0]
doc2_tfidf: [0.0504, 1.4282, 0.0557, 0.0504, 1.7983]
doc3_tfidf: [0.0, 1.2041, 0.0664, 0.0, 1.5161]
Relevance Scores:
doc2: 0.9993
doc3: 0.9979
doc1: 0.0758
TL;DR
- 정규화된 TF는 문서의 단어 일치도를 개선하지만, 단어 중요성을 반영하지 못하게 되며,
- IDF를 통해 드문 단어의 중요성을 반영하여 단어의 상대적 중요성을 평가.
- TF-IDF는 문서와 쿼리의 유사성을 균형 있게 평가하는 데 사용되며, 중요한 단어가 더 많은 가중치를 얻음.
패션 상품을 검색할 때도 TF-IDF를 활용할 수 있음. 예를 들어, 사용자가 “red dress with floral pattern”이라는 쿼리를 입력했을 때, 각 패션 아이템의 텍스트 설명을 분석하여 쿼리와의 관련성을 평가
TF-IDF는 “red”, “dress”, “floral”, “pattern”과 같은 단어의 출현 빈도와 중요성을 고려하여 검색 결과를 순위화 이 방법은 이미지 검색에서도 메타 데이터를 활용하여 유사성을 평가할 수 있음.
이런 접근 방식은 검색 결과의 정확성을 높이고, 사용자에게 더 만족스러운 검색 경험을 제공할 수 있음.
검색 엔진에서 문서의 관련성을 평가하기 위한 다양한 방법이 존재 대표적으로 사용되는 기법은 Cosine Similarity와 TF-IDF이며, 최근에는 BM25가 이런 방법을 보다 개선한 형태로 주목받고 있음. 본 포스트에서는 각 기법의 수학적 배경과 실제 활용 사례를 중심으로 설명
[1, 1]
일 때, 문서 벡터 [2, 2]
, [3, 3]
, [N, N]
은 모두 코사인 값 1.0
을 가짐. 이는 모든 문서가 쿼리와 동일한 비율로 키워드를 포함하고 있음을 의미TL;DR
- Cosine Similarity는 벡터의 각도를 기반으로 유사도를 계산하며, 벡터 크기를 무시
- TF-IDF는 단어의 빈도와 중요도를 고려하여 문서의 관련성을 평가
- BM25는 TF-IDF와 유사하지만, 문서 길이 정규화 및 빈도 포화점 조절 기능을 추가하여 보다 정교한 유사도 계산을 가능하게
$$ | D | $$: 문서의 길이 |
collection = "cat_in_the_hat"
create_collection(collection)
upsert_text_field(collection, "title")
upsert_text_field(collection, "description")
docs = [
{"id": "doc1", "title": "Worst", "description": "The interesting thing..."},
{"id": "doc2", "title": "Best", "description": "My favorite book..."},
{"id": "doc3", "title": "Okay", "description": "My neighbors let..."}
]
response = requests.POST(solr_url + collection + "/update?commit=true", json=docs).json()
검색 및 결과 확인 생성된 컬렉션에 대해 쿼리를 실행하여 BM25 점수를 확인
Figure 3.6 BM25 Scoring Function BM25의 수식 구조를 시각적으로 설명하며, 각 단어의 추가 발생이 점수에 얼마나 기여하는지를 조절하는 \(k\) 파라미터와, 문서 길이에 따른 정규화를 수행하는 \(b\) 파라미터를 설명
이와 같은 방식으로 BM25를 활용하여 패션 상품을 검색할 때, 이미지 및 텍스트 메타 정보를 활용하여 보다 정확한 검색 결과를 제공할 수 있음. 예를 들어, 특정 색상이나 스타일의 이미지를 입력하여 유사한 패션 아이템을 추천받을 수 있으며, 텍스트 설명과 결합하여 보다 풍부한 검색 경험을 제공
p80
목표 주어진 쿼리와 문서에 대해 BM25 알고리즘을 사용하여 문서의 유사도 점수를 계산하고, 순위 매김 과정을 점검
query = "the cat in the hat"
설정request = {
"query": query,
"fields": ["id", "title", "description", "score", "[explain style=html]"],
"params": {
"qf": "description",
"defType": "edismax",
"indent": "true"
}
}
response = str(requests.POST(solr_url + collection + "/select",
json=request).json()["response"]["docs"]).replace('\\n', '').replace(", '", ",<br/>'")
display(HTML(response))
the cat in the hat
Best
My favorite book is the cat in the hat, which is about a crazy cat who breaks into a house and creates a crazy afternoon for two kids.
0.6823196
0.6823196 = sum of: [...]
p81
계산 과정
freq
: 문서 내에서의 용어 출현 횟수k1
: 용어 포화 파라미터 (대부분 1.2 사용)b
: 길이 정규화 파라미터 (주로 0.75 사용)dl
: 필드의 길이avgdl
: 필드의 평균 길이n
: 용어를 포함한 문서 수N
: 필드에 존재하는 전체 문서 수p82
Okay
My neighbors let the stray cat stay in their garage, which resulted in my favorite hat that I let them borrow being ruined.
0.62850046
p83
TL;DR
- BM25 알고리즘은 TF와 IDF를 결합하여 문서의 유사도를 계산
- 각 용어별로 개별 계산된 점수들이 합산되어 최종 BM25 점수가 도출됨.
- Solr 쿼리를 통해 BM25 점수를 함수로 표현하여 동일한 결과를 얻을 수 있음.
query = ‘{!func}query(“the”) {!func}query(“cat”) {!func}query(“in”) {!func}query(“the”) {!func}query(“hat”)’
fields
: 검색 결과에 포함될 필드 지정params
: 쿼리의 다양한 파라미터 설정 예시request = {
"query": query,
"fields": ["id", "title", "score"],
"params": {
"qf": "description",
"defType": "edismax",
"indent": "true"
}
}
response = str(requests.POST(solr_url + collection + "/select",
json=request).json()["response"]["docs"]).replace('\\n', '').replace(", ", ",<br/>'")
TL;DR
fields
와 params
를 통해 구성되고, 결과는 HTML로 출력됨.q={!type=edismax}the cat in the hat
qf="title^10 description^2.5"
Boost docs containing the exact phrase “the cat in the hat” in the title field
TL;DR
edismax
쿼리 파서를 통해 특정 필드에 대한 가중치를 조정 가능기능 쿼리 활용 다양한 기능을 통해 복합적인 정렬 및 가중치 조정 가능
q={!func}scale(query($keywords),0,25)
{!func}recip(geodist($lat_long_field,$user_latitude,$user_longitude),
1,25,1)
{!func}recip(ms(NOW/HOUR,modify_date),3.16e-11,25,1)
{!func}scale(popularity,0,25)
가중치 조정 각 기능의 상대적 기여도를 설정하여 최종 관련성 계산에 반영
TL;DR
TL;DR
검색 엔진에서 문서의 관련성을 평가하고 순위를 매기는 방법은 중요 이 과정에서 주로 사용되는 두 가지 부스팅 기법은 덧셈 부스팅 (Additive Boosting)과 곱셈 부스팅 (Multiplicative Boosting)임.
곱셈 부스팅의 예를 들자면, Solr에서 다음과 같이 사용할 수 있음
q=the cat in the hat&
defType=edismax&
boost=mul(popularity,10)
또는
q=the cat in the hat
{!boost b=mul(popularity,10)}
TL;DR
- 덧셈 부스팅은 각 피처의 기여도를 명확히 조정할 수 있지만 복잡도가 높아질 수 있음.
- 곱셈 부스팅은 다양한 피처를 조합할 수 있는 유연성을 제공하지만, 특정 피처가 다른 피처를 압도할 위험이 있음.
- 두 가지 부스팅 방식은 상황에 따라 유용하게 사용될 수 있으며, 실험을 통해 최적의 결과를 찾아야
검색 엔진에서의 매칭은 크게 두 가지 단계로 나눌 수 있음
특히, 필터링은 성능 최적화에 기여하며, 논리적으로 일치하는 문서 집합을 줄임으로써 검색 엔진의 응답 시간을 단축할 수 있음. 필터링을 통해 검색 결과의 총 문서 수를 줄이고, 사용자가 결과 집합을 탐색하고 세분화할 수 있도록 도움.
Solr의 mm
파라미터는 최소 매칭 임계값을 설정하여 각 쿼리의 논리적 구조를 쉽게 제어할 수 있음. 예를 들어, 다음과 같이 사용할 수 있음:
mm=100% ## 모든 쿼리 용어가 일치해야 함
mm=0% ## 적어도 한 개의 쿼리 용어가 일치해야 함
mm=2 ## 적어도 두 개의 쿼리 용어가 일치해야 함
Solr에서는 필터링과 스코어링을 제어하는 두 가지 주요 방법이 있음:
q
): 검색 쿼리 자체를 정의.fq
): 필터링을 통해 결과 집합을 좁힘.예를 들어, 다음과 같은 쿼리를 통해 특정 카테고리 및 관객을 대상으로 필터링할 수 있음:
q=the cat in the hat&
fq=category:books&
fq=audience:kid&
defType=edismax&
mm=100%&
qf=description
p92
q
(query) 파라미터
fq
(filter query) 파라미터
bq
(boost query) 파라미터
q
는 필터링과 관련성 랭킹을 동시에 수행fq
는 필터링 전용bq
는 관련성 부스트 전용#> 예시 쿼리
q={!func}query("{!type=edismax qf=description mm=100% v=$query}")&
fq={!cache=false v=$query}&
query=the cat in the hat
q=*:*
&bq={!type=edismax qf=description mm=100% v=$query}&
fq={!cache=false v=$query}&
query=the cat in the hat
cache=false
: 필터 캐시 비활성화
TL;DR
q
, fq
, bq
파라미터는 각각 다른 역할을 수행하며, q
는 두 가지 역할을 하며,p93
TL;DR
p94
TL;DR
p95
TL;DR
이 문서는 다양한 검색 관련성 최적화 기법을 설명하며, 각 파라미터의 역할과 이들의 조합을 통해 검색 결과를 어떻게 최적화할 수 있는지를 다루고 있음. 특히, AI 및 머신 러닝을 활용한 자동화된 관련성 최적화 방법에 초점을 맞추고 있음.
사용자 신호의 정의 사용자가 검색 쿼리를 발행하고, 결과를 클릭하거나, 제품을 구매하는 등의 행동을 통해 사용자의 의도를 인퍼런스할 수 있는 신호 생성 → 이런 신호는 사용자의 의도를 이해하는 데 중요한 역할을
핵심 사용자 신호는 사용자의 의도를 인퍼런스하는 데 중요하며, 콘텐츠는 검색의 대상이 됨. 두 가지 접근을 통합하여 더 정확한 검색 시스템 구축 가능. 콘텐츠와 신호의 조화로운 활용이 중요
RetroTech 사례 소개 전자상거래 검색은 다양한 AI 기반 검색 기법을 탐구할 수 있는 좋은 기회 제공. RetroTech는 빈티지 하드웨어, 소프트웨어, 멀티미디어 제품을 제공하는 이커머스 플랫폼임.
데이터셋 설정 RetroTech 웹사이트의 약 50,000개 제품을 검색 엔진에 로드하여 검색 가능하도록 설정 → 제품 카탈로그와 사용자 신호 데이터를 포함한 CSV 파일을 통해 데이터셋 구성.
코드 실행 및 결과
create_collection(products_collection)
## Create Products Collection
products_collection = "products"
create_collection(products_collection)
## Modify Schema to make some fields explicitly searchable by keyword
upsert_text_field(products_collection, "upc")
upsert_text_field(products_collection, "name")
upsert_text_field(products_collection, "longDescription")
upsert_text_field(products_collection, "manufacturer")
print("Loading Products...")
csvFile = "../data/retrotech/products.csv"
product_update_opts = {"zkhost": "aips-zk", "collection": products_collection,
"gen_uniq_key": "true", "commit_within": "5000"}
csvDF = spark.read.format("com.databricks.spark.csv").option(
"header", "true").option("inferSchema", "true").load(csvFile)
csvDF.write.format("solr").options(**product_update_opts).mode(
"overwrite").save()
print("Products Schema: ")
csvDF.printSchema()
print("Status: Success")
핵심 RetroTech의 사례를 통해 전자상거래의 AI 기반 검색 기법을 탐구하고, 제품 및 사용자 신호 데이터셋 설정을 통해 검색 엔진에 데이터를 로드하여 검색 가능하도록 Spark를 활용하여 CSV 파일을 Solr로 저장하여 데이터 인덱싱.
이미지 및 메타 정보 활용 패션 상품 검색에서 이미지는 중요한 역할을 하며, 메타 정보(e.g., 색상, 크기, 브랜드 등)와 결합하여 검색 정확도를 높임.
핵심 패션 상품 검색에서 이미지는 중요한 요소로, 메타 정보와 결합하여 검색 정확도를 향상시킴. CNN 모델을 활용하여 이미지 피쳐을 추출하고, 사용자에게 적합한 패션 아이템을 추천 가능.
p.100: Indexing and Searching Product Catalog
root
|-- upc: long (nullable = true)
|-- name: string (nullable = true)
|-- manufacturer: string (nullable = true)
|-- shortDescription: string (nullable = true)
|-- longDescription: string (nullable = true)
{
"query": "ipod",
"fields": ["upc", "name", "manufacturer", "score"],
"limit": 5,
"params": {
"qf": "name manufacturer longDescription",
"defType": "edismax",
"sort": "score desc, upc asc"
}
}
p.101: 제품 검색 결과
Figure 4.1 설명
‘ipod’를 검색한 결과가 화면에 나타남. 제품 카탈로그가 색인되어 있으며, 검색 쿼리에 대한 결과를 반환
향후 계획
p.102: 신호 데이터 로딩 및 구조
root
|-- query_id: string (nullable = true)
|-- user: string (nullable = true)
|-- type: string (nullable = true)
|-- target: string (nullable = true)
|-- signal_time: timestamp (nullable = true)
p.103: 신호 데이터의 예시와 구조
응용 예시: 패션 상품 검색
query
, results
신호는 각각 별도의 신호로 분리 구성
query_id
는 사용자 쿼리의 특정 시간 인스턴스를 식별
results
신호는 문서의 순서를 유지하는 정렬된 리스트
purchase
신호 생성
TL;DR
- 사용자 신호는 쿼리의 결과와 사용자의 상호작용을 상세히 추적할 수 있도록 구조화됨.
query_id
와 신호의 순서를 통해 사용자의 쿼리 반응 분석 및 결과 정렬 가능.- 신호를 기반으로 사용자 상호작용 패턴을 학습하고, 검색 결과의 관련성을 개선 가능한 구조 제공.
User ID
, Device ID
, Browser ID
, Session ID
, Request ID
Browser ID
→ 여러 Session ID
포함 가능Session ID
→ 여러 Request ID
포함 가능TL;DR
- 사용자 식별 계층을 통해 다양한 수준의 사용자 데이터 추적 가능
- 식별자의 지속성을 유지하여 사용자 상호작용 패턴을 학습하고 분석
- 브라우저 쿠키는 식별자 간의 지속적 연결을 위한 핵심 매개체 역할
TL;DR
- Reflected Intelligence는 사용자 상호작용을 통한 지속적 학습과 검색 결과 개선을 목표로
- 학습된 관련성 모델은 사용자 쿼리에 대한 더 나은 검색 결과 제공 가능.
- 지속적 피드백 루프를 통해 검색 시스템은 시간이 지남에 따라 자동으로 스마트해짐.
예시: 패션 상품 검색에의 응용
- 이미지 및 상품 메타 정보를 통한 검색: 이미지에서 색상, 스타일, 패턴 등의 속성을 추출하여 검색
- → 인풋: 사용자 업로드 이미지 또는 선택한 스타일 속성
- → 아웃풋: 유사한 스타일의 패션 상품 리스트
이와 같은 방법의 적용은 AI 기반 검색 엔진의 효율성을 극대화하고, 사용자 경험의 향상에 기여 Reflected Intelligence는 검색 시스템의 지속적인 최적화를 가능케
select q.target as query, c.target as doc, count(c.target) as boost
from signals c left join signals q on c.query_id = q.query_id
where c.type = 'click' AND q.type = 'query'
group by query, doc
order by boost desc
TL;DR
p112
query = "ipad"
signals_boosts_query = {
"query": query,
"fields": ["doc", "boost"],
"limit": 10,
"params": {
"defType": "edismax",
"qf": "query",
"sort": "boost desc"
}
}
signals_boosts = requests.POST(solr_url + signals_boosting_collection +
"/select", json=signals_boosts_query).json()["response"]["docs"]
print("Boost Documents: \n")
print(signals_boosts)
query
변수에 “ipad”를 설정product_boosts = ""
for entry in signals_boosts:
if len(product_boosts) > 0product_boosts += " "
product_boosts += '"' + entry['doc'] + '"^' + str(entry['boost'])
print("\nBoost Query: \n" + product_boosts)
signals_boosts
리스트에서 각 문서에 대해 부스트 쿼리를 생성.collection = "products"
request = {
"query": query,
"fields": ["upc", "name", "manufacturer", "score"],
"limit": 5,
"params": {
"qf": "name manufacturer longDescription",
"defType": "edismax",
"indent": "true",
"sort": "score desc, upc asc",
"boost": "sum(1,query({! df=upc v=$signals_boosting}))",
"signals_boosting": product_boosts
}
}
search_results = requests.POST(solr_url + collection + "/select", json=request)
.json()["response"]["docs"]
display(HTML(render_search_results(query, search_results)))
[{'doc': '885909457588', 'boost': 966}, {'doc': '885909457595', 'boost': 205}, {'doc': '885909471812', 'boost': 202},
{'doc': '886111287055', 'boost': 109}, {'doc': '843404073153', 'boost': 73}, {'doc': '635753493559', 'boost': 62},
{'doc': '885909457601', 'boost': 62}, {'doc': '885909472376', 'boost': 61}, {'doc': '610839379408', 'boost': 29},
{'doc': '884962753071', 'boost': 28}]
p113
"boost": "sum(1,query({! df=upc v=$signals_boosting}))"
"signals_boosting": product_boosts
product_boosts
는 부스트 쿼리를 생성한 리스트로, 검색 엔진에 전달됨.p114
p115
이 가이드를 통해 Signals Boosting 및 Collaborative Filtering을 활용하여 검색 관련성을 어떻게 개선할 수 있는지 이해할 수 있음. 이런 기법들은 검색 엔진의 성능을 높이는 데 중요한 역할을 하며, 사용자 경험을 크게 개선 다음 장에서는 이런 기법들의 심화된 내용을 다룰 예정임.
Figure 4.6 사용자-아이템 추천을 위한 Collaborative Filtering. 사용자는 과거 행동을 기반으로 다른 사용자가 좋아했던 아이템을 추천받음. 이때, 다른 사용자들도 동일한 아이템과 상호작용
Figure 4.7 Learning to Rank의 일반화된 흐름. 사용자 판단을 기반으로 랭킹 분류기를 구축하여 검색 결과 재정렬.
Collaborative Filtering과 Learning to Rank의 이론적 배경과 활용 방법을 상세히 설명하며, 패션 상품 검색에의 적용 가능성을 제시
p.120
Figure 4.8: 다양한 Reflected Intelligence 모델
예시
p.121
p.122
TL;DR
p.123
예시: 패션 상품 검색
이와 같은 방식으로 Reflected Intelligence와 지식 그래프를 활용하여 다양한 도메인에서의 검색 관련성을 높일 수 있음.
지식 그래프 개념 도입
→ 지식 그래프는 노드와 엣지를 통해 표현됨
→ 노드는 엔티티(e.g., 용어, 사람, 장소, 개념 등)
→ 엣지는 두 노드 간의 관계를 나타냄
→ Figure 5.1 참고
지식 그래프 예시 설명
→ 저자, 연구 논문, 학술 대회, 도시, 국가, 날짜 등의 노드
→ 엣지를 따라 탐색하여 정보 인퍼런스 가능
→ 예: 저자가 2016년 10월 캐나다 몬트리올에 있었음을 인퍼런스
TL;DR
- 지식 그래프는 노드와 엣지로 구성되어 다양한 엔티티와 관계를 표현
- 노드와 엣지를 따라 탐색하여 새로운 정보를 인퍼런스 가능
- 지식 그래프는 사실적 지식을 표현하며, 이를 통해 다양한 분석과 인퍼런스이 가능
Apache Solr의 활용
→ 명시적 그래프 탐색 기능 내장
→ 외부 시스템 필요 없이 지식 그래프 구현 가능
외부 그래프 데이터베이스 사용의 복잡성
→ 요청 조율, 데이터 동기화, 인프라 관리 복잡
→ 검색 엔진에서 일부 그래프 작업의 효율적 수행 가능
통합 플랫폼으로서의 검색 엔진
→ 검색 기능과 지식 그래프 기능의 결합으로 시너지 효과 제공
TL;DR
- 명시적 그래프 탐색 기능이 포함된 Apache Solr을 통해 지식 그래프를 효과적으로 구현 가능.
- 외부 그래프 데이터베이스 사용 시 복잡성 증가, 검색 엔진 내 구현이 효율적.
- 검색 엔진을 통합 플랫폼으로 활용하여 검색 및 지식 그래프 기능 결합.
Open Information Extraction의 역할
→ 자연어 처리(NLP)를 통한 텍스트에서의 사실 추출
→ 의존성 그래프 사용: 문장의 구문적 구조 분석
임의 관계와 하위 관계 추출
→ 명사와 동사 분석을 통한 RDF 트리플 형성
→ 예: “Colin attends Riverside High School” → (“Colin”, “attends”, “Riverside High School”)
SpaCy 라이브러리를 통한 예시
→ 자연어 처리 라이브러리로 텍스트 구문 분석 및 관계 추출
→ Listing 5.1에서 Python 코드를 통해 관계 생성
TL;DR
- 자연어 처리를 통해 텍스트에서 관계를 추출하고, RDF 트리플로 표현.
- SpaCy와 같은 NLP 라이브러리로 자동 팩트 추출 가능.
- 명사, 동사 분석을 통해 지식 그래프의 노드와 엣지를 형성하여 관계 파악.
예시: 패션 상품 검색의 적용
이미지와 메타정보 기반 검색
→ 이미지 처리 모델을 통해 상품의 특성 추출
→ 메타정보와 결합하여 지식 그래프의 노드 및 엣지 생성
→ 예: 이미지에서 색상, 스타일, 브랜드 정보 추출 → RDF 트리플 형성
모델 활용
→ CNN(Convolutional Neural Network)를 통한 이미지 피쳐 추출
→ NLP 모델로 제품 설명 텍스트 처리 및 관계 분석
검색 엔진 통합
→ Apache Solr와 같은 플랫폼에 지식 그래프 통합
→ 사용자가 입력한 키워드와 이미지 정보를 기반으로 검색 결과 제공
이와 같이, 지식 그래프를 다양한 방법으로 구축하고 활용함으로써, 정확한 정보 추출 및 검색 서비스를 제공할 수 있음. SpaCy와 같은 NLP 도구를 사용하여 텍스트로부터 직접 정보를 추출하고, 이를 지식 그래프로 통합하여 복합적인 정보 검색이 가능하도록
simple_hearst_patterns = [
('(NP_\\w+ (, )?such as (NP_\\w+ ?(, )?(and |or )?)+)', 'first'),
('(such NP_\\w+ (, )?as (NP_\\w+ ?(, )?(and |or )?)+)', 'first'),
('((NP_\\w+ ?(, )?)+(and |or )?other NP_\\w+)', 'last'),
('(NP_\\w+ (, )?include (NP_\\w+ ?(, )?(and |or )?)+)', 'first'),
('(NP_\\w+ (, )?especially (NP_\\w+ ?(, )?(and |or )?)+)', 'first'),
]
text_content = """
Many data scientists have skills such as machine learning, python,
deep learning, apache spark, or collaborative filtering, among others
...
"""
h = HearstPatterns()
extracted_relationships = h.find_hyponyms(text_content)
facts = list()
for pair in extracted_relationships:
facts.append([pair[0], "is_a", pair[1]])
print(*facts, sep="\n")
이와 같은 방법은 이미지와 상품 메타 정보를 기반으로 패션 상품을 검색하는 데에도 적용 가능 예를 들어, “셔츠”라는 하이퍼님에 대해 “드레스 셔츠”, “티셔츠” 등의 하이포님을 추출하여 보다 정확한 검색 결과 제공 가능.
TL;DR
위와 같은 방식으로 Semantic Knowledge Graph는 다양한 도메인에서의 쿼리 확장 및 관련 데이터 탐색에 강력한 도구로 활용될 수 있음.
Semantic knowledge graphs represent an advanced abstraction layer built on top of existing inverted indexes. These graphs enhance traditional search engines by not only matching and ranking documents but also focusing on the semantic relationships between terms. This allows for more nuanced and context-aware search results.
Key Insight Semantic similarity is not just about co-occurrence but the statistical significance of this co-occurrence.
Summary High relatedness scores indicate strong semantic relationships.
{
"params": {
"qf": "title body",
"fore": "{!type=$defType qf=$qf v=$q}",
"back": "*:*",
...
},
"query": "advil",
...
}
Expanded Query Example
advil OR motrin^0.59897 OR aleve^0.4662 OR ibuprofen^0.3824
Semantic knowledge graphs significantly enhance search engine capabilities by understanding and utilizing the semantic relationships between terms. This allows for more accurate and contextually relevant search results, improving user satisfaction and search engine performance.
In summary, semantic knowledge graphs build on existing search engine indexes to uncover and leverage semantic relationships between terms, offering an advanced layer for query expansion and relevance ranking, ultimately leading to more precise search results.
p.140
"qf"
: “title body”"fore"
: {!type=$defType qf=$qf v=$q}
"back"
: “:”"defType"
: “edismax”"rows"
: 0"echoParams"
: “none”"omitHeader"
: “true”relatedness($fore,$back)
search_results = requests.POST(solr_url + collection + “/select”, json=request).json()
for bucket in search_results[“facets”][“body”][“buckets”]: print(str(bucket[“val”]) + “ “ + str(bucket[“relatedness”][“relatedness”]))
vibranium 0.92227
wakandan 0.75429
wakanda 0.75295
adamantium 0.7447
panther's 0.69835
klaue 0.68083
klaw 0.65195
panther 0.65169
Summary Semantic knowledge graphs help discover context for unknown terms by expanding queries with related concepts, improving both recall and precision of search results.
p.141
query_expansion = “”
terms = search_results[“facets”][“body”][“buckets”] for bucket in search_results[“facets”][“body”][“buckets”]: term = bucket[“val”] boost = bucket[“relatedness”][“relatedness”] if len(query_expansion) > 0: query_expansion += “ “ query_expansion += “ “ + term + “^” + str(boost)
expanded_query = query + “^5” + query_expansion
print(“Expanded Query:\n” + expanded_query)
Expanded Query:
vibranium^5 vibranium^0.92228 wakandan^0.75429 wakanda^0.75295 adamantium^0.7447 panther's^0.69835 klaue^0.68083 klaw^0.65195 panther^0.65169
Summary Query expansion uses semantic graphs to include related terms, boosting recall by enhancing context, while the original query term maintains higher relevance.
p.142
q={!edismax qf="title body" mm="0%"}vibranium vibranium^0.92227 wakandan^0.75429 wakanda^0.75295 adamantium^0.7447 panther's^0.69835 klaue^0.68083 klaw^0.65195 panther^0.65169
q={!edismax qf="title body" mm="30%"}vibranium vibranium^0.92227 wakandan^0.75429 wakanda^0.75295 adamantium^0.7447 panther's^0.69835 klaue^0.68083 klaw^0.65195 panther^0.65169
Summary Different strategies for query augmentation balance between increasing precision and recall by setting minimum match criteria or boosting specific terms.
p.143
q={!edismax qf="title body" mm="2"}vibranium &boost=query($expanded_query) &expanded_query=vibranium^5 vibranium^0.92227 wakandan^0.75429 wakanda^0.75295 adamantium^0.7447 panther's^0.69835 klaue^0.68083 klaw^0.65195 panther^0.65169
Summary By utilizing semantic knowledge graphs, queries can be rewritten to enhance recall and precision, adapt to user needs, and provide better domain-specific search results.
p144
p145
import collections from mergedeep import merge
solr_url = "http://example-solr-url.com"
collection = "stackexchange"
classification = "star wars"
document = """this doc contains the words luke, magneto, cyclops, darth vader,
princess leia, wolverine, apple, banana, galaxy, force, blaster,
and chloe."""
parsed_document = ["this", "doc", "contains", "the", "words", "luke", \
"magneto", "cyclops", "darth vader", "princess leia", \
"wolverine", "apple", "banana", "galaxy", "force", \
"blaster", "and", "chloe"]
request = {"query": classification, "params": {}, "facet": {}}
i = 0
for term in parsed_document:
i += 1
key = "t" + str(i)
key2 = "${" + key + "}"
request["params"][key] = term
request["facet"][key2] = {
"type": "query",
"q": "{!edismax qf=${qf} v=" + key2 + "}",
"facet": {"stats": "${relatedness_func}"}
}
print(json.dumps(request, indent=" "))
full_request = merge(request_template, request)
search_results = requests.POST(solr_url + collection + "/select",
json=full_request).json()
def parse_scores(search_results):
results = collections.OrderedDict()
for key in search_results["facets"]:
if key != "count" and key != "" and "stats" in search_results["facets"][key]:
relatedness = search_results["facets"][key]["stats"]["relatedness"]
results[key] = relatedness
return list(reversed(sorted(results.items(), key=lambda kv: kv[1])))
scored_terms = parse_scores(search_results)
for scored_term in scored_terms:
print(scored_term)
p146
{ “query”: “star wars”, “params”: { “t1”: “this”, “t2”: “doc”, “t3”: “contains”, “t4”: “the”, “t5”: “words”, “t6”: “luke”, “t7”: “magneto”, “t8”: “cyclops”, “t9”: “darth vader”, “t10”: “princess leia”, “t11”: “wolverine”, “t12”: “apple”, “t13”: “banana”, “t14”: “galaxy”, “t15”: “force”, “t16”: “blaster”, “t17”: “and”, “t18”: “chloe” }, “facet”: { “${t1}”: { “type”: “query”, “q”: “{!edismax qf=${qf} v=${t1}}”, “facet”: { “stats”: “${relatedness_func}” } }, … “${t18}”: { “type”: “query”, “q”: “{!edismax qf=${qf} v=${t18}}”, “facet”: { “stats”: “${relatedness_func}” } } } }
p147
rec_query = ""
for scored_term in scored_terms:
term = scored_term[0]
boost = scored_term[1]
if len(rec_query) > 0:
rec_query += " "
if boost > 0.25:
rec_query += term + "^" + str(boost)
print("Expanded Query:\n" + rec_query)
luke^0.66366 "darth vader"^0.6311 force^0.59269 galaxy^0.45858 blaster^0.39121 "princess leia"^0.25119
import collections
## 검색 요청 설정
collection = "stackexchange"
request = {
"params": {
"qf": "title body",
"defType": "edismax",
"rows": 5,
"echoParams": "none",
"omitHeader": "true",
"mm": "0",
"fl": "title",
"fq": "title:[* TO *]" ## 제목이 있는 문서만 표시
},
"query": rec_query
}
## 검색 실행 및 output
search_results = requests.POST(solr_url + collection + "/select",
json=request).json()
print(json.dumps(search_results, indent=" "))
TL;DR
- Semantic Knowledge Graph를 통해 문서의 용어를 주제와 관련한 스코어로 변환하여 관련 쿼리를 생성.
- Python 코드로 스코어링된 용어를 기반으로 쿼리를 확장하고, 0.25 이상의 관련성만 필터링하여 유의미한 결과 도출.
- 생성된 쿼리를 사용하여 콘텐츠 기반 추천을 실행하고, 상위 랭킹 문서를 반환.
이와 같은 방식으로 시맨틱 지식 그래프는 다양한 도메인에 적용 가능하며, 특히 사용자 신호가 부족한 상황에서 효율적인 도구로 활용될 수 있음.
지식 그래프는 도메인 내 엔티티들 간의 관계를 모델링하는 기술이며, 명시적인 관계를 통해 구축하거나 콘텐츠로부터 동적으로 추출 가능 이 과정에서 Open Information Extraction(OIE) 기술을 활용하여 주관, 관계, 객체의 삼중항(triples)을 추출하여 지식 그래프를 형성 이를 통해 의미 기반 검색에서 콘텐츠를 직접 활용하여 데이터 모델링 없이도 검색을 수행할 수 있음.
지식 그래프는 의미 검색에서 도메인 내 엔티티들 간의 관계를 모델링하여 사용자 의도 이해를 돕고, 쿼리 확장 및 재작성에 기여 이를 통해 사용자의 검색 경험을 향상시키고, 다양한 도메인에서 더욱 정교한 검색 결과 제공 가능. 다음 장에서는 이런 지식 그래프를 활용한 쿼리 파싱 및 통합 방법에 대해 심도 있게 다룰 예정임.
def run_query_classification(query, keywords_field="body", classification_field="category", classification_limit=5, min_occurrences=5):
classification_query = {
"params": {
"qf": keywords_field,
"fore": "{!type=$defType qf=$qf v=$q}",
"back": "*:*",
"defType": "edismax",
"rows": 0,
"echoParams": "none",
"omitHeader": "true"
},
"query": query,
"facet": {
"classification": {
"type": "terms",
"field": classification_field,
"sort": {"classification_relatedness": "desc"},
"mincount": min_occurrences,
"limit": classification_limit,
"facet": {
"classification_relatedness": {
"type": "func",
"func": "relatedness($fore,$back)"
}
}
}
}
}
search_results = requests.POST(solr_url + collection + "/select", json=classification_query).json()
## output
print("Query: " + query)
print(" Classifications: ")
for classification_bucket in search_results["facets"]["classification"]["buckets"]:
print(" " + str(classification_bucket["val"]) + " " + str(classification_bucket["classification_relatedness"]["relatedness"]))
print("\n")
예시 output
Query: docker
Classifications:
devops 0.8376
Query: airplane
Classifications:
travel 0.20591
Query: airplane AND crash
Classifications:
scifi 0.01938
travel -0.01068
Query: camping
Classifications:
outdoors 0.40323
travel 0.10778
Query: alien
Classifications:
scifi 0.51953
Query: passport
Classifications:
travel 0.73494
Query: driver
Classifications:
travel 0.23835
devops 0.04461
Query: driver AND taxi
Classifications:
travel 0.1525
scifi -0.1301
Query: driver AND install
Classifications:
devops 0.1661
travel -0.03103
이 문서는 시맨틱 지식 그래프를 활용하여 쿼리를 분류하고 다의어를 해석하는 방법에 대해 설명합니다. 각 단계는 논리적 전개와 수학적 배경을 포함하여 상세히 설명되었으며, 실용적인 예시와 결과도 포함되었습니다.
검색 엔진이 다의어의 의미를 분별하여 각 문맥에 맞는 관련 용어 리스트를 생성하는 것이 중요 이는 사용자의 명확한 의도를 이해하고 반영하기 위함임. 다의어의 의미를 분별하지 않고 단순히 관련 용어를 한 리스트로 묶는 것은 사용자의 명확한 의도를 반영하지 못할 수 있음.
def run_disambiguation_query(query, keywords_field="body", context_field="category",
keywords_limit=10, context_limit=5, min_occurrences=5):
disambiguation_query = {
"params": {
"qf": keywords_field,
"fore": "{!type=$defType qf=$qf v=$q}",
"back": "*:*",
"defType": "edismax",
"rows": 0,
"echoParams": "none",
"omitHeader": "true"
},
"query": query,
"facet": {
"context": {
"type": "terms",
"field": context_field,
"sort": {"context_relatedness": "desc"},
"mincount": min_occurrences,
"limit": context_limit,
"facet": {
"context_relatedness": {
"type": "func",
"func": "relatedness($fore,$back)"
},
"keywords": {
"type": "terms",
"field": keywords_field,
"mincount": min_occurrences,
"limit": keywords_limit,
"sort": {"keywords_relatedness": "desc"},
"facet": {
"keywords_relatedness": {
"type": "func",
"func": "relatedness($fore,$back)"
}
}
}
}
}
}
}
search_results = requests.POST(solr_url + collection + "/select", json=disambiguation_query).json()
print("Query: " + query)
for context_bucket in search_results["facets"]["context"]["buckets"]:
print(" Context: " + str(context_bucket["val"]) + " " +
str(context_bucket["context_relatedness"]["relatedness"]))
print(" Keywords: ")
for keywords_bucket in context_bucket["keywords"]["buckets"]:
print(" " + str(keywords_bucket["val"]) + " " +
str(keywords_bucket["keywords_relatedness"]["relatedness"]))
print("\n")
run_disambiguation_query(query="server", context_field="category", keywords_field="body")
run_disambiguation_query(query="driver", context_field="category", keywords_field="body", context_limit=2)
run_disambiguation_query(query="chef", context_field="category", keywords_field="body", context_limit=2)
devops
(0.787)server
는 도구 관리, 빌딩, 배포에 집중scifi
문맥에서는 일반적인 컴퓨팅 관련 의미travel
문맥에서는 식당에서 일하는 사람에 대한 의미travel
(0.23835)taxi
, car
, license
등이 포함이와 같은 기술을 통해 다양한 문맥에서 다의어의 의미를 분별하고 사용자의 의도에 맞는 정확한 검색 결과를 제공할 수 있음.
puppet
, ansible
, docs.chef.io
등.recipe
, taste
, restaurant
등.TL;DR
- 쿼리의 의미를 문맥에 따라 해석하여 사용자 의도를 정확히 파악할 수 있음.
- 쿼리 로그를 분석하여 사용자의 검색 패턴과 관련 키워드를 학습할 수 있음.
- AI 검색 엔진에서 이 정보를 활용하여 보다 정교하고 문맥화된 검색 결과 제공 가능.
Listing 6.4
: 각 쿼리의 발생을 검색한 사용자와 매핑.## Calculation: Mapping signals into keyword, user pairs
spark.sql("""
select lower(searches.target) as keyword, searches.user as user
from signals as searches
where searches.type='query'
""").createOrReplaceTempView('user_searches')
## Show Results:
spark.sql("""select count(*) from user_searches """).show(1)
spark.sql("""select * from user_searches """).show(3)
Listing 6.5
: 사용자들이 검색한 쿼리의 총 발생 수와 공동 발생 수를 계산하여 관련성 높은 쿼리 쌍 식별.## Calculation: Total occurrences and coocurrences of queries
spark.sql('''
select k1.keyword as keyword1, k2.keyword as keyword2,
count(distinct k1.user) users_cooc
from user_searches k1 join user_searches k2 on k1.user = k2.user where
k1.keyword > k2.keyword
group by k1.keyword, k2.keyword ''').createOrReplaceTempView('keywords_users_cooc')
spark.sql('''
select keyword , count(distinct user) users_occ
from user_searches
group by keyword ''').createOrReplaceTempView('keywords_users_oc')
## Show Results:
spark.sql('''select * from keywords_users_oc order by users_occ desc''').show(10)
spark.sql('''select * from keywords_users_cooc order by users_cooc desc''').show(10)
Query Pair Cooccurrence
→ 사용자가 동시에 검색한 쿼리 쌍의 총 개수 분석
→ 예시: iphone 4s
와 iphone
은 자주 함께 검색됨
Generalization Hierarchy
→ 예: iphone
> iphone 4
> iphone 4s
→ 일반적인 형태에서 구체적인 형태로 발전하는 계층 구조
데이터 희소성 문제
→ 가장 높은 cooccurrence를 보인 쿼리 쌍조차 23명의 사용자에 불과함
→ 이는 데이터 포인트가 희소하다는 것을 의미하며, 노이즈가 포함될 가능성 높음
PMI 정의
→ 두 이벤트 간의 연관성을 측정하는 척도
→ 자연어 처리에서 두 단어가 함께 나타날 확률과 우연히 함께 나타날 확률 비교
PMI 계산 공식
\(\text{PMI}(k1, k2) = \log{\frac{P(k1, k2)}{P(k1) \times P(k2)}}\)
→ $P(k1, k2)$: 두 키워드가 동시에 검색된 빈도
→ $P(k1)$, $P(k2)$: 각각의 키워드가 개별적으로 검색된 빈도
PMI2 변형
→ $k = 2$로 설정하여 빈도에 상관없이 일관된 점수 유지
→ 스파크 SQL을 사용한 PMI2 계산 예시:
spark.sql('''
select k1.keyword as k1, k2.keyword as k2, k1_k2.users_cooc, k1.users_occ
as n_users1, k2.users_occ as n_users2,
log(pow(k1_k2.users_cooc,2) / (k1.users_occ*k2.users_occ)) as pmi2
from keywords_users_cooc as k1_k2
join
keywords_users_oc as k1 on k1_k2.keyword1= k1.keyword
join
keywords_users_oc as k2 on k1_k2.keyword2 = k2.keyword
''').registerTempTable('user_related_keywords_pmi')
Composite Score 정의
→ cooccurrence와 PMI 점수를 결합하여 쿼리의 연관성을 측정
→ 인기와 연관성의 균형을 맞춰 점수 산출
Composite Score 공식
\(\text{comp_score}(q1,q2) = \frac{((r1(q1,q2) + r2(q1,q2))/(r1(q1,q2) \times r2(q1,q2)) )}2\)
→ $r1$: cooccurrence 순위
→ $r2$: PMI 순위
Composite Score 계산 예시
spark.sql('''
select *, (r1 + r2 /( r1 * r2))/2 as comp_score from (
select *,
rank() over (partition by 1 order by users_cooc desc ) r1 ,
rank() over (partition by 1 order by pmi2 desc ) r2
from user_related_keywords_pmi ) a '''
).registerTempTable('users_related_keywords_comp_score')
고찰
→ Composite Score는 cooccurrence와 PMI의 한계를 극복하여 합리적인 결과 제공
→ 예시: green lantern
과 captain america
는 높은 연관성을 가짐
Noise 문제
→ 특정 쿼리 쌍에서의 낮은 데이터 포인트로 인해 노이즈 발생 가능성 존재
→ 작은 사용자 그룹에서의 빈도는 쉽게 노이즈로 작용할 수 있음
패션 상품 검색
→ 이미지와 상품 메타 정보를 기반으로 한 검색 시, PMI와 Composite Score 활용 가능
→ 예: nike
와 running shoes
는 함께 검색될 가능성이 높음
모델 활용
→ 인풋: 상품 이미지 및 메타 정보 (e.g., 브랜드, 색상)
→ 아웃풋: 유사 상품 추천 리스트 제공
TL;DR
- Query Pair Analysis에서는 사용자의 검색 쿼리 쌍을 PMI를 이용하여 관련성을 평가
- PMI는 두 단어의 연관성을 측정하여 우연히 함께 나타날 확률과 비교함으로써 키워드의 의미적 연관성을 파악
- Composite Score를 통해 cooccurrence와 PMI의 Pros(+)을 결합하여 보다 신뢰할 수 있는 쿼리 연관성을 제공
이미지 설명 쿼리 쌍의 cooccurrence와 PMI를 시각화하여 연관성을 평가하는 과정의 흐름을 나타냄. 각 쿼리 쌍의 빈도와 관련성 점수가 그래프로 표현됨.
이 자료는 사용자 검색 패턴에 따른 쿼리 쌍의 관련성을 파악하는 데 있어 PMI와 Composite Score의 활용 가능성을 보여주는 예시임. 이런 방법은 특히 패션 상품 검색과 같은 비슷한 분야에서의 응용 가능성을 확장할 수 있음.
사용자가 쿼리를 통해 상호 작용하는 제품을 분석하여 관련 쿼리를 찾는 방법에 대해 설명 많은 사용자가 겹치는 쿼리를 검색하는 사례를 통해 관련 용어를 찾는 기술은 데이터가 희소할 수 있으므로 종종 사용자 외의 기준으로 집계를 수행하는 것이 유리
spark.sql("""select lower(searches.target) as keyword,
searches.user as user, clicks.target as product
from signals as searches right join signals as clicks on searches.query_id
= clicks.query_id
where searches.type='query' and clicks.type = 'click'""").
createOrReplaceTempView('keyword_click_product')
#Calculation:
spark.sql('''
select k1.keyword as k1, k2.keyword as k2, sum(p1) n_users1,sum(p2) n_users2,
sum(p1+p2) as users_cooc, count(1) n_products
from
(select keyword, product, count(1) as p1 from keyword_click_product group by
keyword, product) as k1
join
(select keyword, product, count(1) as p2 from keyword_click_product group by
keyword, product) as k2
on k1.product = k2.product
where k1.keyword > k2.keyword
group by k1.keyword, k2.keyword
''').createOrReplaceTempView('keyword_click_product_cooc')
#Calculation:
spark.sql('''
select keyword, count(1) as n_users from keyword_click_product group by
keyword
''').registerTempTable('keyword_click_product_oc')
사용자 쿼리를 엔티티로 취급 → RetroTech 전자상거래 사이트와 같은 사례에서 효과적임
핵심
복잡한 쿼리는 노이즈를 포함할 수 있음 → 엔티티로 직접 사용하기 어려움
엔티티 추출 전략
예시: 상품 메타 정보를 기반으로 한 패션 상품 검색
상품 검색 시스템의 흐름을 보여주는 이미지. 사용자는 입력된 쿼리를 통해 상품 이미지를 업로드하고, 메타 정보를 제공. 시스템은 이미지와 메타 정보를 기반으로 유사 상품을 검색하여 추천
p176-179 요약
관련 문서 및 링크
{
"collection": "products",
"query": "moden",
"request": {
"params": {
"q.op": "and",
"rows": 0,
"indent": "on"
},
"query": "moden"
}
}
SELECT LOWER(searches.target) AS keyword, searches.user AS user
FROM signals AS searches
WHERE searches.type='query'
GROUP BY keyword, user
이 블로그 포스트는 AI 기반의 검색 시스템에서 철자 교정과 대체 표현을 효과적으로 처리하는 방법을 다루며, 사용자의 검색 경험을 향상시키는 기술적 접근 방식을 제시
p.184 - 토큰화 및 정제 과정
stop_words = set(stopwords.words('english'))
word_list = defaultdict(int)
for row in query_signals:
query = row["keyword"]
tokenizer = RegexpTokenizer(r'\w+')
tokens = tokenizer.tokenize(query)
for token in tokens:
if token not in stop_words and len(token) > 3 and not token.isdigit():
word_list[token] += 1
quantiles_to_check = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
quantile_values = np.quantile(np.array(list(word_list.values())), quantiles_to_check)
quantiles = dict(zip(quantiles_to_check, quantile_values))
p.185 - 분위와 오타 탐지
misspell_candidates = []
correction_candidates = []
misspell_counts = []
correction_counts = []
misspell_length = []
correction_length = []
misspell_initial = []
correction_initial = []
for k, v in word_list.items():
if v <= quantiles[0.2]:
misspell_candidates.append(k)
misspell_counts.append(v)
misspell_length.append(len(k))
misspell_initial.append(k[0])
if v >= quantiles[0.8]:
correction_candidates.append(k)
correction_counts.append(v)
correction_length.append(len(k))
correction_initial.append(k[0])
p.186 - 후보군 데이터프레임 생성
misspell_candidates_df = pd.DataFrame({
"misspell": misspell_candidates,
"misspell_counts": misspell_counts,
"misspell_length": misspell_length,
"initial": misspell_initial
})
correction_candidates_df = pd.DataFrame({
"correction": correction_candidates,
"correction_counts": correction_counts,
"correction_length": correction_length,
"initial": correction_initial
})
misspell_candidates_df.head(10)
p.187 - 철자 교정을 위한 매칭
def good_match(len1, len2, edit_dist):
match = 0
min_length = min(len1, len2)
if min_length < 8:
if edit_dist == 1: match = 1
elif min_length < 11:
if edit_dist <= 2: match = 1
else:
if edit_dist == 3: match = 1
return match
matches_candidates = pd.merge(misspell_candidates_df, correction_candidates_df, on="initial")
matches_candidates["edit_dist"] = matches_candidates.apply(lambda row: nltk.edit_distance(row.misspell, row.correction), axis=1)
matches_candidates["good_match"] = matches_candidates.apply(lambda row: good_match(row.misspell_length, row.correction_length, row.edit_dist), axis=1)
matches = matches_candidates[matches_candidates["good_match"] == 1].drop(["initial", "good_match"], axis=1)
matches_final = matches.sort_values(by=['correction_counts'], ascending=[False])[["misspell", "correction", "misspell_counts", "correction_counts", "edit_dist"]].head(20)
위 내용은 철자 교정 시스템을 구현하는 과정에서 발생하는 다양한 단계와 수학적 접근을 설명 이를 통해 철자 오류를 정정하고 검색 품질을 향상시킬 수 있음.
Page 188부터 시작하여, 이 문서에서는 사용자 신호 기반의 철자 교정 모델과 문맥에 따라 의미를 해석하는 방법을 다루고 있음. 특히 철자 교정에 있어 문서 기반 접근 방식과 사용자 신호 기반 접근 방식의 차이를 설명하고, 이를 통해 얻은 철자 교정의 결과를 어떻게 활용할 수 있는지에 대해 서술
Results:
misspell correction misspell_counts correction_counts edit_dist
latop laptop 5 14258 1
touxhpad touchpad 5 11578 1
...
TL;DR
TL;DR
TL;DR
이 장에서는 철자 교정 및 의미론적 검색 기술을 통해 사용자 의도를 더 잘 이해하고 해석하는 방법을 탐구 이런 기술은 검색 엔진에서 쿼리 해석 능력을 향상시키는 데 중요한 역할을 하며, 다음 장에서는 이런 기술을 통합하여 최적의 의미론적 검색 시스템을 구축하는 방법을 설명할 예정임.
신호 집계 쿼리
select lower(q.target) as query, c.target as doc, count(c.target) as boost from signals c left join signals q on c.query_id = q.query_id where c.type = ‘click’ AND q.type = ‘query’ group by query, doc order by boost desc
정규화된 신호 부스팅 적용
이와 같은 방식으로 신호 부스팅 모델은 다양한 도메인에서 검색 결과의 관련성을 높이는 데 사용될 수 있음. 이를 통해 사용자 경험을 개선하고 검색의 정확성을 높일 수 있음.
import datetime
spam_user = "u8675309"
spam_query = "star wars"
spam_signal_boost_doc_upc = "45626176"
num = 0
while (num < 5000):
query_id = "u8675309_0_" + str(num)
next_query_signal = {
"query_id": query_id,
"user": spam_user,
"type": "query",
"target": spam_query,
"signal_time": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
"id": "spam_signal_query_" + str(num)
}
next_click_signal = {
"query_id": query_id,
"user": spam_user,
"type": "click",
"target": spam_signal_boost_doc_upc,
"signal_time": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
"id": "spam_signal_click_" + str(num)
}
collection = "signals"
requests.POST(solr_url + collection + "/update/json/docs", json=next_query_signal)
requests.POST(solr_url + collection + "/update/json/docs", json=next_click_signal)
num += 1
requests.POST(solr_url + collection + "/update/json/docs?commit=true")
signals_collection = "signals"
signals_aggregation_collection = "signals_boosts_with_spam"
aggregate_signals(signals_collection, signals_aggregation_collection, normalized_signals_aggregation_query)
query = "star wars"
collection = "products"
signals_boosts = get_query_time_boosts(query, "signals_boosts_with_spam")
boosted_query = get_main_query(query, signals_boosts)
search_results = requests.POST(solr_url + collection + "/select", json=boosted_query).json()["response"]["docs"]
print(search_results)
display(HTML(render_search_results(query, search_results)))
핵심
위 내용은 검색 엔진 최적화를 위한 신호 처리 기법에 대한 이해를 돕고, 악의적인 데이터 조작을 방지하기 위한 전략을 제시하는 데 기여
위의 설명은 검색 엔진 최적화 과정에서 신호 스팸을 다루는 방법과 이를 방지하기 위한 전략을 제시하며, 특히 패션 상품 검색에의 적용 가능성을 탐색 검색 엔진의 신호 부스팅 모델이 어떻게 조작될 수 있는지를 설명하고, 이런 문제를 방지하기 위한 방법을 제시
p200
p201
select query, doc, count(doc) as boost from (
select lower(q.target) as query, c.target as doc, c.user as boost, max(c.signal_time)
from signals c left join signals q on c.query_id = q.query_id
where c.type = 'click' AND q.type = 'query'
group by c.user, q.target, c.target
) as x
group by query, doc
order by boost desc
196
p202
197
p203
select query, doc,
( (1 * click_boost) + (10 * add_to_cart_boost) + (25 * purchase_boost) ) as boost
from (
select query, doc, sum(click) as click_boost, sum(add_to_cart) as add_to_cart_boost, sum(purchase) as purchase_boost
from (
select lower(q.target) as query, cap.target as doc,
if(cap.type = 'click', 1, 0) as click,
if(cap.type = 'add-to-cart', 1, 0) as add_to_cart,
if(cap.type = 'purchase', 1, 0) as purchase
from signals cap left join signals q on cap.query_id = q.query_id
where (cap.type != 'query' AND q.type = 'query')
) raw_signals
group by query, doc
) as per_type_boosts
이런 방법을 이미지와 상품 메타 정보를 기반으로 한 패션 상품 검색에 적용하면, 사용자가 특정 패션 아이템을 검색할 때 클릭(1), 장바구니 추가(10), 구매(25)와 같은 신호를 반영하여 검색 결과의 순위를 조정할 수 있음. 이는 검색 엔진이 사용자 의도를 더 잘 파악하고 개인화된 검색 결과를 제공하는데 기여
여러 신호를 결합하여 전체 부스트 값을 최적화하는 과정에서 각 신호 타입은 독립적으로 합산된 후 결합됨.
→ 제안된 가중치:
이런 구성은 대부분의 전자상거래 시나리오에서 효과적일 수 있음. 예를 들어, 식료품 배달 앱의 경우 대부분의 사용자가 장바구니에 추가한 제품을 구매할 가능성이 높으며, 이런 경우 장바구니에 추가하는 것이 추가 가치를 제공하지 않을 수 있음.
TL;DR
- 신호의 상대적 가중치는 도메인에 따라 조정 가능하며, 부정적 신호도 중요하게 다룰 수 있음.
- 신호는 다양한 입력 파라미터를 받아 가중치에 따라 랭킹 점수를 반환하는 단순한 선형 함수로 처리 가능.
- 신호의 가중치를 튜닝하는 과정은 수동으로 수행하거나, Learning to Rank와 같은 머신러닝 기법을 활용할 수 있음.
신호의 시간 감쇠를 모델링하는 가장 쉬운 방법은 반감기 함수 사용:
\[time\_based\_signal\_weight = starting\_weight \times 0.5^{(signal\_age/half\_life)}\]starting_weight
: 신호의 타입에 따라 상대적 가중치 (e.g., 클릭: 1, 장바구니 추가: 10, 구매: 25)signal_age
: 신호의 연령half_life
: 신호가 절반의 가치를 잃는 시간[Figure 8.4: 감쇠 함수가 시간에 따라 신호 가중치에 미치는 영향]
신호 기반 랭킹 모델은 다중 입력 파라미터를 통해 구성 가능하며, 다양한 신호의 상대적 가중치를 조정하여 모델의 강건성을 향상시킬 수 있음. 최적의 균형을 달성하기 위해 수동으로 조정하거나 Learning to Rank 기술을 활용할 수 있음. 신호의 시간적 가치를 고려한 설정은 특정 도메인에 따라 달라질 수 있으며, 특히 뉴스 검색 엔진과 같은 시나리오에서는 반감기 함수를 활용하여 최신 신호에 더 높은 가중치를 할당하는 것이 중요
Figure 8.4는 시간이 지남에 따라 다양한 반감기 값을 기반으로 시그널의 감소를 보여줌. 반감기가 증가하면 개별 시그널이 부스팅 효과를 더 오래 유지
TL;DR
- 시그널의 반감기는 시그널의 유효기간을 결정하는 중요한 요소임.
- 반감기가 길수록 시그널의 부스팅 효과가 지속됨.
- 다양한 반감기를 통해 시그널의 중요성을 조정할 수 있음.
Listing 8.7에서는 반감기 30일을 설정한 시그널 부스팅 모델에 시간 감쇠 함수를 적용하는 방법을 설명
half_life_days
: 반감기 설정, 기본 30일signal_weight
: 시그널 유형에 따른 가중치 (e.g., 클릭=1, 장바구니 추가=10, 구매=25 등)target_date
: 시그널의 최대 가치를 얻는 날짜, 보통 현재 날짜를 사용시간 감쇠 함수는 다음과 같은 수식을 사용하여 시그널의 가중치를 계산함:
\[\text{time\_weighted\_boost} = \text{signal\_weight} \times \left(0.5^{\frac{\text{datediff}(\text{target\_date}, \text{signal\_time})}{\text{half\_life\_days}}}\right)\]signal_weight
: 시그널의 종류에 따른 초기 가중치datediff(target_date, signal_time)
: target_date와 signal_time 간의 날짜 차이half_life_days
: 반감기 설정이 수식은 시간 경과에 따른 시그널의 가중치 감소를 설명 datediff
가 클수록 시그널의 가중치는 작아짐.
TL;DR
- 시간 감쇠 함수는 반감기를 통해 시그널의 가중치를 계산.
- 반감기를 조정하여 시그널의 유효성을 조정 가능.
- 시그널의 목적과 특성에 따라 반감기와 가중치를 설정하여 최적화 가능.
TL;DR
- 쿼리 시점 부스팅은 유연성을 제공하지만 성능 문제를 수반.
- 각 쿼리에 대해 개별 시그널 업데이트 가능.
- 성능 문제로 인해 모든 사용 사례에 적합하지 않을 수 있음.
패션 상품 검색에서는 이미지와 상품 메타 정보를 기반으로 한 시그널 부스팅이 효과적임. 예를 들어, 특정 계절이나 이벤트에 따라 검색 빈도가 달라지는 상품에 대해 시그널 부스팅을 적용할 수 있음.
예시 시나리오
이를 통해 사용자 경험을 향상시키고, 검색 결과의 관련성을 높일 수 있음.
signals_boosts
필드 생성DelimitedPayloadBoostFilter
사용하여 쿼리와 Boost 함께 인덱싱– Solr를 사용하여 ‘signals_boosts’ 필드에 쿼리와 Boost 값 저장
signals_boosts_collection="normalized_signals_boosts"
signals_boosts_opts={"zkhost": "aips-zk", "collection": signals_boosts_collection}
df = spark.read.format("solr").options(**signals_boosts_opts).load()
df.registerTempTable(signals_boosts_collection)
products_collection="products_with_signals_boosts"
products_read_boosts_opts={"zkhost": "aips-zk", "collection": products_collection}
df = spark.read.format("solr").options(**products_read_boosts_opts).load()
df.registerTempTable(products_collection)
boosts_query = """
SELECT p.*, b.signals_boosts from (
SELECT doc, concat_ws(',',collect_list(concat(query, '|', boost))) as signals_boosts
FROM """ + signals_boosts_collection + """ GROUP BY doc
) b inner join """ + products_collection + """ p on p.upc = b.doc
"""
products_write_boosts_opts={"zkhost": "aips-zk", "collection": products_collection, "gen_uniq_key": "true", "commit_within": "5000"}
spark.sql(boosts_query).write.format("solr").options(**products_write_boosts_opts).mode("overwrite").save()
payload()
함수를 사용하여 인덱싱된 Boost 값 기반으로 점수 조정signals_boosts
필드 일치 시 점수 증가query = "ipad"
def get_query(query, signals_boosts_field):
request = {
"query": query,
"fields": ["upc", "name", "manufacturer", "score"],
"limit": 3,
"params": {
"qf": "name manufacturer longDescription",
"defType": "edismax",
"indent": "true",
"sort": "score desc, upc asc",
"qf": "name manufacturer longDescription",
"boost": "payload(" + signals_boosts_field + ", \"" + query + "\", 1, first)"
}
}
return request
collection = "products_with_signals_boosts"
boosted_query = get_query(query, signals_boosts_field)
search_results = requests.POST(solr_url + collection + "/select", json=boosted_query).json()["response"]["docs"]
TL;DR
- Index-time Boosting은 쿼리 인기도를 문서에 반영하여 보다 안정적이고 일관된 검색 결과 제공.
- 쿼리 수행 시
payload()
함수를 사용하여 인덱싱된 Boost 값을 기반으로 검색 점수 조정.- 모든 문서에 Boost 적용이 가능하며, 사용자 경험과 검색 결과의 통일성을 극대화
→ 인덱스 시간 부스팅은 모든 쿼리가 일관되고 완전한 부스팅을 받도록 보장 → 쿼리 실행 전 추가 조회 제거로 쿼리 속도 향상 가능.
→ 이런 인덱싱 압력은 검색 엔진의 운영 복잡성을 증가시킴. → 쿼리 성능을 유지하기 위해 인덱스 서버와 검색 인덱스 서버를 분리하여 운영하는 것이 바람직
→ 샤드를 추가하여 더 많은 문서를 더 짧은 시간에 검색 가능. → 복제본 추가로 내결함성 증가 및 더 많은 검색 가능.
TL;DR
예시: 패션 상품 검색
→ 인덱스 시간 부스팅을 적용하여 유명한 패션 아이템을 검색 결과 상위에 배치.
q=title:(${keywords})^10 overview:(${keywords})^20 {!func}release_year^0.01
TL;DR
이 문서는 Learning to Rank의 개념과 구현 방법을 다양한 예시와 수식을 통해 설명하며, 전통적인 머신러닝과의 차이점도 명확히 비교합니다. 이는 검색 엔진의 성능을 최적화하기 위한 중요한 기법으로, LTR 모델을 통해 사용자에게 보다 관련성 높은 검색 결과를 제공할 수 있습니다.
0
은 관련 없는 문서, 1
은 관련 있는 문서.from ltr.judgments import Judgment
Judgment(grade=1, keywords='social network', doc_id=37799)
q=title:(${keywords})^10 overview:(${keywords})^20 {!func}release_year^0.01
title_bm25
), 개요 필드 관련 점수(overview_bm25
), 영화의 개봉 연도(release_year
) 등 정의.feature_set = [
{
"name" : "title_bm25",
"store": "movies",
"class" : "org.apache.solr.ltr.feature.SolrFeature",
"params" : {
"q" : "title:(${keywords})"
}
},
{
"name" : "overview_bm25",
"store": "movies",
"class" : "org.apache.solr.ltr.feature.SolrFeature",
"params" : {
"q" : "overview:(${keywords})"
}
},
{
"name" : "release_year",
"store": "movies",
"class" : "org.apache.solr.ltr.feature.SolrFeature",
"params" : {
"q" : "{!func}release_year"
}}]
TL;DR
다음 단계에서는 학습된 모델을 활용하여 실제 검색 시스템에 적용하는 방법을 탐구하며, 이 과정에서 구체적인 사례 연구를 통해 이해를 돕고, 피처 엔지니어링의 중요성을 강조
AI 기술이 발전하면서 패션 산업에서도 이미지와 메타 정보를 활용한 검색 시스템이 주목받고 있음. 이런 시스템은 사용자가 입력한 사진이나 텍스트를 기반으로 관련 상품을 효과적으로 검색하여 추천 이 글에서는 Solr와 같은 검색 엔진을 활용한 머신러닝 기반의 학습-순위(Learning to Rank, LTR) 시스템을 중심으로 이미지와 메타 정보를 활용한 패션 상품 검색 기술을 자세히 설명
fl
파라미터를 통해 필요한 기능 데이터를 각 문서 응답에 추가.efi
파라미터를 사용하여 쿼리 키워드를 전달하고 추가 쿼리 시간 정보를 사용하여 각 기능을 계산.logging_solr_query = {
"fl": "id,title,[features store=movies efi.keywords=\"social network\"]",
'q': "id:37799 OR id:267752 OR id:38408 OR id:28303",
'rows': 10,
'wt': 'json'
}
resp = requests.POST('http://aips-solr:8983/solr/tmdb/select', data=logging_solr_query)
resp.json()
title_bm25
, overview_bm25
, release_year
과 같은 기능 값 로깅.title_bm25=0.0, overview_bm25=4.353118, release_year=2010.0
Solr를 이용해 기능 로깅을 수행하여 쿼리와 관련된 각 문서의 기능 값을 수집 이를 통해 관련성과 비관련성을 판단할 수 있음. 다음 단계로는 LTR을 머신러닝 문제로 변환하여 학습을 시작
LTR을 통해 각 쿼리의 결과 집합을 이상적인 순서로 배치하는 것이 목표임. 이는 개별 문서의 관련성 점수를 예측하는 것과는 다른 접근 방식으로, 쿼리 내에서 상대적 순서가 중요
패션 상품 검색에 이미지와 메타 정보를 활용하는 방법은 사용자 경험을 크게 향상시킬 수 있음. Solr와 같은 검색 엔진과 머신러닝 기법을 결합하여 효과적인 순위 결정과 관련성 높은 검색 결과를 제공할 수 있음. 이런 기술은 패션 산업 뿐만 아니라 다양한 분야에 적용 가능
p232
Figure 10.3 점별 머신러닝은 개별 데이터 포인트(e.g., 주가, 온도)의 예측 최적화를 시도 검색 관련성 문제는 점별 예측과 다름. 대신 검색 쿼리로 그룹화된 예제의 순위를 최적화해야
예: SVMRank 모델을 통해 랭킹 태스크 변환 방법 살펴보기
TL;DR
- LTR은 검색 쿼리로 그룹화된 예제의 순위를 최적화하는 문제임.
- SVMRank는 순위 문제를 이진 분류 문제로 변환하여 접근
- LTR의 핵심은 관련성과 무관성의 관계를 학습하는 모델임.
p233
SVMRank의 주요 개념
Figure 10.4 동물의 높이와 무게를 기반으로 개와 고양이를 구분하는 SVM 예제. 검색 결과를 관련성과 무관성으로 구분하는 데 유사한 방법 사용 가능.
Figure 10.5 SVM이 피쳐의 범위에 민감하게 반응함을 보여주는 예제. 피쳐의 범위가 모델에 부당한 영향을 미치지 않도록 정규화 필요.
TL;DR
- SVM은 두 클래스 간 최적의 분리 초평면을 찾음.
- 분리 초평면은 데이터의 일반화 가능성을 높임.
- SVM은 피쳐의 범위에 민감하므로 정규화가 필요
p234
release_year
의 평균이 1990년일 때 1990년의 영화는 0으로 정규화star wars
, social network
쿼리의 두 가지 피쳐 (title_bm25
, overview_bm25
) 사용Figure 10.6 star wars
와 social network
쿼리의 피쳐 점수
TL;DR
- SVM은 정규화된 데이터에서 최적의 성능을 발휘
- SVMRank는 랭킹 태스크를 이진 분류로 변환
- 두 가지 피쳐을 사용하여 쿼리의 관련성을 평가할 수 있음.
p235
normed_judgments
생성Listing 10.8: LTR 훈련 세트를 정규화된 세트로 변환
title_bm25
, overview_bm25
, release_year
의 원시 값과 정규화된 값 비교title_bm25
의 로그 점수 8.243603은 평균 대비 4.4829 표준편차에 해당Figure 10.7: 정규화된 피쳐 플롯, 각 축의 스케일이 다름
means, std_devs, normed_judgments = normalize_features(logged_judgments) logged_judgments[360], normed_judgments[360]
social network
쿼리의 정규화된 피쳐들TL;DR
- 훈련 데이터의 각 피쳐은 정규화가 필요
- 정규화를 통해 피쳐을 비교 가능한 범위로 조정
- 정규화된 데이터를 통해 SVMRank의 이진 분류 모델을 구축
정규화된 데이터 사용 → 피쳐량들을 일관된 범위로 강제 조정 → SVM이 큰 범위를 가진 피쳐량에 편향되지 않음. 이제 이 작업을 이진 분류 문제로 변환할 준비가 완료됨.
foreach query in queries:
foreach judged_document_1 in query.judgments:
foreach judged_document_2 in query.judgments:
if judged_document_1.grade > judged_document_2.grade:
predictor_deltas.append(+1)
feature_deltas.append(judged_document_1.features - judged_document_2.features)
else if judged_document_1.grade < judged_document_2.grade:
predictor_deltas.append(-1)
feature_deltas.append(judged_document_1.features - judged_document_2.features)
ai-powered-search/
이런 방식으로 SVMRank의 알고리즘은 다양한 분야에 적용 가능 특히, 이미지와 메타 정보를 결합하여 사용자에게 최적화된 검색 결과를 제공할 수 있음.
p240
Figure 10.11 ‘Social Genocide’와 ‘The Social Network’의 비교. 쿼리 ‘social network’에 대한 문서의 관련성을 비교
Listing 10.11 “Social Genocide”와 “The Social Network”를 포인트 와이즈 트레이닝 데이터에 추가
Figure 10.12 두 번째 쿼리 예시 (‘star wars’)에 대한 문서 비교
predictor_deltas.append(+1)
[4.483, 2.100] - [2.234, -0.444] = [2.249, 2.544]
predictor_deltas.append(-1)
[2.234, -0.444] - [4.483, 2.100] = [-2.249, -2.544]
p241
Figure 10.12 ‘Rogue One: A Star Wars Movie’와 ‘Star!’의 쿼리 ‘star wars’에 대한 비교.
문서의 관련성과 비관련성을 구별하기 위해 특성 값의 차이를 계속 계산
predictor_deltas.append(+1)
[2.088, 1.024] - [1.808, -0.444]
predictor_deltas.append(-1)
[1.808, -0.444] - [2.088, 1.024]
p242
Figure 10.13 관련 문서와 비관련 문서를 구분하는 하이퍼플레인.
높은 title_bm25
점수와 overview_bm25
점수가 문서의 관련성과 상관관계가 있음을 시각적으로 확인 가능
LTR 방법 검토 필요
feature_deltas
와 predictor_deltas
를 사용하여 클래식 머신러닝 모델 학습 준비p243
Figure 10.14 하이퍼플레인을 정의하는 수직 벡터. 관련성 방향을 가리킴.
title_bm25
가 강한 영향력, overview_bm25
가 약간의 영향력Listing 10.12
: 하이퍼플레인을 정의하는 벡터 획득TL;DR
feature_deltas
와 predictor_deltas
feature_deltas
: 일반적으로 각 데이터 포인트의 피쳐 차이를 나타냄predictor_deltas
: 예측 값의 차이를 나타내며, +1 또는 -1로 구성됨from sklearn import svm model = svm.LinearSVC(max_iter=10000, verbose=1) model.fit(feature_deltas, predictor_deltas) model.coef_
array([[0.40512169, 0.29006365, 0.14451721]])
title_bm25
: 가장 큰 가중치overview_bm25
: 중간 가중치release_year
: 가장 작은 가중치wrath of khan
을 입력할 경우[5.9217176, 3.401492, 1982.0]
[3.099, 1.825, -0.568]
(3.099 * 0.405) + (1.825 * 0.290) + (-0.568 * 0.1445) = 1.702
proportion_train=0.1
로 설정하여 10%의 데이터를 훈련 데이터로 사용all_qids = list(set([j.qid for j in normed_judgments]))
random.shuffle(all_qids)
test_train_split_idx = int(len(all_qids) * proportion_train)
test_qids=all_qids[:test_train_split_idx]
train_qids=all_qids[test_train_split_idx:]
train_data = []; test_data=[]
for j in normed_judgments:
if j.qid in train_qids:
train_data.append(j)
elif j.qid in test_qids:
test_data.append(j)
def eval_model(test_data, model):
tot_prec = 0
num_queries = 0
for qid, query_judgments in groupby(test_data, key=lambda j: j.qid):
query_judgments = list(query_judgments)
ranked = rank(query_judgments, model)
tot_relevant = 0
for j in ranked[:4]:
if j.grade == 1:
tot_relevant += 1
query_prec = tot_relevant/4.0
tot_prec += query_prec
num_queries += 1
return tot_prec / num_queries
q="title:(${keywords})^10 overview:(${keywords})^20
release_year^0.01"
TL;DR
- Scikit-learn을 사용하여 선형 SVM을 학습시켜 데이터의 차이를 분리하는 모델 생성.
- 모델의 일반화 가능성을 테스트/훈련 데이터 분할을 통해 검증하고, Precision 메트릭을 사용하여 평가.
- Solr에 모델을 배포하여 검색 결과를 최적화하고, 결과를 확인
StandardNormalizer
사용.
PUT http://aips-solr:8983/solr/tmdb/schema/model-store
{
"store": "movies",
"class": "org.apache.solr.ltr.model.LinearModel",
"name": "movie_titles",
"features": [
{
"name": "title_bm25",
"norm": {
"class": "org.apache.solr.ltr.norm.StandardNormalizer",
"params": {
"avg": "1.5939970007512951",
"std": "3.689972140122766"
}
}
},
{
"name": "overview_bm25",
"norm": {
"class": "org.apache.solr.ltr.norm.StandardNormalizer",
"params": {
"avg": "1.4658440933160637",
"std": "3.2978986984657808"
}
}
},
{
"name": "release_year",
"norm": {
"class": "org.apache.solr.ltr.norm.StandardNormalizer",
"params": {
"avg": "1993.3349740932642",
"std": "19.964916628520722"
}
}
}
],
"params": {
"weights": {
"title_bm25": 0.40512169,
"overview_bm25": 0.29006365,
"release_year": 0.14451721
}
}
}
TL;DR
- Solr의 Learning to Rank(LTR) 모델은 피처 스토어를 통해 피처를 관리
- 피처 정규화는 모델 평가 전 필수 단계이며, 가중치는 피처 중요도를 결정
- 최적의 LTR 결과를 위해 모델과 피처의 상호작용을 잘 이해해야
request = {
"fields": ["title", "id", "score"],
"limit": 5,
"params": {
"q": "{!ltr reRankDocs=60000 model=movie_model efi.keywords=\"harry potter\"}"
}
}
resp = requests.POST('http://aips-solr:8983/solr/tmdb/select', json=request)
resp.json()["response"]["docs"]
TL;DR
- LTR 모델은 대규모 문서를 효율적으로 랭킹 가능
- 초기 검색 후 재랭킹으로 비용 절감 및 효율성 증가.
- Solr의 LTR 쿼리 파서를 통해 복잡한 검색 요구 충족 가능.
request = {
"fields": ["title", "id", "score"],
"limit": 5,
"params": {
"rq": "{!ltr reRankDocs=500 model=movie_model efi.keywords=\"harry potter\"}",
"qf": "title overview",
"defType": "edismax",
"q": "harry potter"
}
}
resp = requests.POST('http://aips-solr:8983/solr/tmdb/select', json=request)
resp.json()["response"]["docs"]
TL;DR
- 복잡한 모델은 높은 정확도를 제공하지만 계산 비용 증가.
- 상위 문서 재랭킹으로 검색 효율성 향상.
- 피처 복잡성과 수는 모델 성능에 큰 영향을 미침.
예시: 패션 상품 검색
- 인풋 상품 이미지, 텍스트 설명.
- 아웃풋 유사 상품 리스트.
- 모델 활용 이미지 및 텍스트 기반 피처 추출 후 LTR 모델 적용.
- 모델 인풋/아웃풋 이미지 피처, 텍스트 피처 / 유사 상품 순위.
이와 같은 과정은 패션 상품 검색에서 이미지 및 텍스트 메타 정보를 활용하여 유사한 패션 아이템을 검색하는 데 적용될 수 있음. 이때, LTR 모델은 이미지 피처와 텍스트 피처를 기반으로 검색 결과의 순위를 매김으로써 사용자의 검색 의도에 맞는 결과를 반환 가능.
p252
이전 장에서 Learning to Rank (LTR) 모델을 단계별로 학습하는 과정을 다루었음. 이 장에서는 LTR 학습 프로세스를 블랙 박스로 취급하여, LTR을 자율주행차처럼 최적의 목적지로 향하게 조정하는 방법을 탐구
TL;DR
- LTR의 효과성은 정확한 training dataset에 달려 있음.
- 사용자 행동 신호를 통해 자동으로 LTR을 재학습할 수 있음.
- training dataset의 품질을 향상시키는 것이 중요
Figure 11.1: Automated Learning to Rank 시스템이 사용자 신호로부터 자동으로 학습하고 재학습하는 방식
p253
자동화된 LTR 프로세스 단계:
이 프로세스는 연속적으로 반복되어 관련성을 자동으로 최적화 검색 팀은 자동화된 LTR의 성능을 모니터링하고 필요 시 개입
p254
클릭을 LTR training dataset로 사용할 때의 함의:
사용자 사이에 차이가 있으며, 검색 상호작용은 추가적인 불확실성을 야기
#> 예시: 영화 쿼리의 암시적 판단 생성
Judgment
객체를 사용하여 각 영화의 관련성을 확률적으로 레이블링grade
는 결과가 관련성이 있을 확률을 나타냄.mini_judg_list=[
Judgment(grade=0.99, keywords='star wars', doc_id='11'), ## Star Wars, A New Hope
Judgment(grade=0.80, keywords='star wars', doc_id='1892'), ## Return of the Jedi
Judgment(grade=0.20, keywords='star wars', doc_id='54138'), ## Star Trek Into Darkness
Judgment(grade=0.01, keywords='star wars', doc_id='85783'), ## The Star
Judgment(grade=0.20, keywords='star wars', doc_id='325553'), ## Battlestar Galactica
]
Star Wars, A New Hope
는 높은 관련성 확률을 가짐. The Star
는 낮은 관련성을 가짐.p255
암시적 판단의 Pros(+):
TL;DR
- 암시적 판단은 실제 사용자 상호작용에서 유래하며, 명시적 판단에 비해 저비용임.
- 클릭 기반 데이터는 불확실성을 포함하나, 확률적 판단을 통해 이를 보완
- LTR 재학습을 통해 사용자 기대치에 맞춘 검색 결과를 제공
확률적 등급의 도입
→ 기존의 이진 판단을 넘어, 0~1 사이의 확률적 등급 도입
→ 등급을 정량화하여 학습 가능한 형식으로 변환 필요
→ 예: 0.75 이상을 관련 있음으로 지정하여 학습
LambdaMART와 같은 알고리즘 활용
→ 등급 범위 1~4를 활용하여 특정 임계값 설정
→ 예: 0.25 미만은 1, 0.25 이상 0.5 미만은 2로 지정
SVMRank 알고리즘의 확장
→ 더 관련 있는 항목의 피쳐에서 덜 관련 있는 항목의 피쳐을 뺌
→ 확률적 판단에서도 사용 가능함
→ 예: “Return of The Jedi” (grade=0.8)가 “Star Trek Into Darkness” (grade=0.2)보다 더 관련 있음으로 판단
코드 재활용
→ Chapter 10의 코드를 활용하여 모델 재학습
→ 클릭 모델을 통한 LTR 학습 예시 제공
TL;DR
- 확률적 판단을 도입하여 더 세밀한 LTR 모델 학습 가능.
- LambdaMART와 SVMRank 알고리즘을 확장하여 활용.
- 기존 코드를 활용한 클릭 모델 기반 LTR 학습 예시 제공.
CTR의 정의
→ 검색 결과에 대한 클릭 수를 해당 결과가 검색에 나타난 횟수로 나눔
→ CTR=1: 항상 클릭됨, CTR=0: 전혀 클릭되지 않음
검색 세션 데이터 활용
→ 예시 쿼리: “transformers dark of the moon”
→ 세션 데이터를 활용하여 각 문서의 CTR 계산
QUERY='transformers dark of the moon'
query_sessions = sessions[sessions['query'] == QUERY]
click_counts = query_sessions.groupby('doc_id')['clicked'].sum()
sess_counts = query_sessions.groupby('doc_id')['sess_id'].nunique()
ctrs = click_counts / sess_counts
ctrs.sort_values(ascending=False)
97360810042
24543750949
TL;DR
- CTR은 검색 결과가 얼마나 자주 클릭되는지를 나타내는 지표.
- 세션 데이터를 통해 각 문서의 CTR 계산 가능.
- CTR을 통해 이상적인 검색 결과 순위를 시각적으로 파악 가능.
Figure 11.2 설명
→ “Transformers Dark Of The Moon”에 대한 CTR 기반 이상적 검색 결과
→ LTR 모델의 방향성을 시각적으로 보여줌
결과 분석
→ 가장 높은 CTR을 가진 Blu-ray의 점수가 예상보다 낮음
→ DVD는 관련 없는 항목들 뒤에 위치
TL;DR
- CTR 기반 이상적 검색 결과를 통해 LTR 모델의 방향성을 이해할 수 있음.
- Blu-ray의 CTR이 예상보다 낮고 DVD는 예상보다 뒤에 위치
- CTR은 검색 결과의 품질을 평가하는 데 유용하지만 한계가 존재
CTR의 유용성과 한계
→ CTR은 간단하지만 검색 결과의 품질 평가에 유용
→ 그러나 실제 관련성 판단에서의 한계 존재
향후 방향
→ 더 정교한 클릭 모델 개발 필요
→ 관련 연구 및 선행 기술 참고
추가 참고 자료
→ “Click Models for Web Search” by Chuklin, Markov, and de Rijke
→ 검색 클릭 신호를 처리하는데 내재된 편향 탐구
이런 방식으로 클릭 모델을 활용하여 패션 상품 검색에 적용할 수 있으며, 사용자 클릭 데이터를 분석하여 각 상품의 CTR을 계산하고, 이를 통해 보다 관련성 높은 검색 결과를 제공할 수 있음. 패션 상품의 경우, 이미지 메타 정보와 사용자 클릭 데이터를 결합하여 보다 정확한 추천 시스템 구축 가능.
p.260
Figure 11.3: “Dryer” 검색어에 대한 클릭률 기반 이상적인 검색 결과. “The Independent” 영화는 이 쿼리에 적절하지 않음.
TL;DR
- DVD가 Blu-Ray보다 높은 순위를 받을 것으로 예상되었으나 기존 클릭 데이터는 무작위 결과를 포함
- CTR 기반의 판단이 LTR 모델의 성공적인 구현으로 이어질지 의문이며, 주관적 판단의 오류 가능성 제기.
- 판단 리스트의 유효성 평가 기준 필요성 강조.
p.261
TL;DR
- 클릭 데이터는 사용자 심리 및 인터페이스 디자인에 의해 편향될 수 있음.
- Algorithmic Biases는 검색 결과의 순위 및 표시 방식에 영향을 미침.
- Non-algorithmic Biases는 사용자 경험과 관련된 다양한 이슈를 포함
p.262
TL;DR
- Position Bias는 상위 검색 결과에 대한 사용자 신뢰도에 의해 영향을 받음.
- RetroTech 데이터에서 평균 클릭률을 통해 Position Bias를 측정 가능.
- Position Bias 극복을 위한 새로운 클릭 모델 고려 필요.
p.263
rank=0
의 CTR은 0.25, rank=1
은 0.143 등으로 순위에 따라 CTR이 감소num_sessions = len(sessions['sess_id'].unique())
global_ctrs = sessions.groupby('rank')['clicked'].sum() / num_sessions
global_ctrs
rank
0.0 0.249727
1.0 0.142673
2.0 0.084218
3.0 0.063073
4.0 0.056255
5.0 0.042255
6.0 0.033236
7.0 0.038000
8.0 0.020964
9.0 0.017364
10.0 0.013982
TL;DR
- RetroTech 데이터에서 순위별 클릭률은 상위에 위치한 결과일수록 높음.
- 검색어 ‘Transformers: Dark of the Moon’의 세션 분석을 통해 위치 편향을 확인.
- Position Bias 극복을 위한 클릭 모델 개선 필요.
위 내용은 검색 엔진의 위치 편향 문제를 해결하기 위한 클릭 모델의 발전과 그 적용 사례를 설명하며, 특히 SDBN 모델이 Examines 개념을 통해 위치 편향을 극복하는 방법을 소개
p268
TL;DR
- ‘dryer’ 쿼리에 대한 각 세션의 마지막 클릭 위치를 기반으로 검토된 위치를 표시.
- 각 문서에 대해 클릭 횟수와 검토 횟수를 집계하여 문서의 중요도를 평가.
- sess_id=3의 예시에서는 rank 9까지가 검토된 것으로 표시됨.
p269
TL;DR
- 검토된 행만 필터링하여 문서별 클릭 횟수와 검토 횟수 집계.
- 클릭 횟수를 검토 횟수로 나누어 SDBN 점수를 계산.
- 가장 높은 SDBN 점수를 가진 문서가 가장 관련성이 높은 것으로 평가됨.
p270
p271
QUERY = 'transformers dark of the moon'
sdbn_sessions = sessions[sessions['query'] == QUERY].copy().set_index('sess_id')
last_click_per_session = sdbn_sessions.groupby(['clicked', 'sess_id'])['rank'].max()[True]
sdbn_sessions['last_click_rank'] = last_click_per_session
sdbn_sessions['examined'] = sdbn_sessions['rank'] <= sdbn_sessions['last_click_rank']
sdbn = sdbn_sessions[sdbn_sessions['examined']].groupby('doc_id')['clicked', 'examined'].sum()
sdbn['grade'] = sdbn['clicked'] / sdbn['examined']
sdbn = sdbn.sort_values('grade', ascending=False)
추가 연구 및 링크
베타 분포(Beta Distribution)는 주어진 데이터에 대한 초기 신념(prior belief)을 업데이트하는 데 유용한 도구로, 특히 클릭 데이터와 같은 불확실한 정보에 대해 결론을 쉽게 내리지 않도록 도와줌. 이는 초기 신념을 설정하고, 새로운 데이터가 들어올 때마다 이를 기반으로 신념을 업데이트하는 방식으로 작동 아래에서는 이런 베타 분포의 원리를 단계별로 설명하고, 이를 SDBN(Sequential Dependent Browsing Model) 클릭 모델에 적용하는 방법을 살펴봄.
Figure 11.8: 관련성 점수가 0.125인 경우의 베타 분포. 초기의 관련성 등급에 해당하는 평균과 가능성이 높은 관련성 등급의 분포를 보여줌.
Figure 11.9: 클릭이 추가된 후의 관련성 점수에 대한 베타 분포. 클릭을 통한 업데이트로 초기 신념이 약간 높아짐.
PRIOR_GRADE = 0.3
PRIOR_WEIGHT = 100
sdbn['prior_a'] = PRIOR_GRADE * PRIOR_WEIGHT
sdbn['prior_b'] = (1 - PRIOR_GRADE) * PRIOR_WEIGHT
sdbn['POSTerior_a'] = sdbn['prior_a'] + sdbn['clicked']
sdbn['POSTerior_b'] = sdbn['prior_b'] + (sdbn['examined'] - sdbn['clicked'])
sdbn['beta_grade'] = sdbn['POSTerior_a'] / (sdbn['POSTerior_a'] + sdbn['POSTerior_b'])
sdbn.sort_values('beta_grade', ascending=False)
TL;DR
- 베타 분포를 통해 초기 신념을 설정하고, 새로운 데이터가 들어올 때마다 이를 기반으로 신념을 업데이트
- SDBN 모델에서 초기 관련성 등급을 설정하고, 클릭 및 조회 데이터를 기반으로 관련성을 업데이트하여 보다 정확한 평가를 수행
- 베타 분포의 초기값 설정은 업데이트의 민감도를 결정하며, 이를 적절히 조정하여 원하는 수준의 신뢰성을 확보할 수 있음.
p280
베타 조정 SDBN 모델은 등급의 집중도를 높여 결과의 안정성을 강화 사운드트랙의 관련성 등급은 신뢰도 조정 후 우선값에 가깝게 조정됨. DVD 등급은 상대적으로 변화가 적음.
p281
LTR 모델은 소규모 데이터 문제 해결에 필수적이며, 피드백 루프를 통해 프레젠테이션 편향을 극복 RetroTech 데이터로 LTR 실험을 통해 다양한 기능을 탐색 가능.
p282
LTR 시스템 노트북을 통해 모델을 테스트하고 최적화 가능. 클릭 모델 파라미터와 새로운 기능을 탐색하여 결과 개선 가능.
p283
훈련된 LTR 모델의 순위를 개선할 수 있는 방법 탐색 필요. 프레젠테이션 편향을 극복하여 모델의 실제 사용 시 문제를 최소화해야
이 내용은 LTR(학습-순위화) 모델의 구성과 최적화에 관한 것으로, SDBN 모델과 신뢰도 조정을 통해 검색의 질을 높이고, 프레젠테이션 편향을 극복하려는 다양한 방법을 제시 RetroTech 데이터를 통해 실험적으로 접근하여 결과를 개선할 수 있는 방안을 탐구
p284
p285
p286
표현 편향 사용자가 보는 결과에 따라 모델이 학습
p287
TL;DR
이 문서는 클릭 데이터를 활용한 LTR의 자동화 및 실세계 적용에 대한 내용을 다루고 있음. A/B 테스트를 통해 사용자 피드백을 수집하고, 표현 편향을 극복하여 모델의 성능을 향상시키는 방법을 제시 실시간 사용자를 활용하여 모델의 성능을 검증하고, 새로운 탐색 경로를 통해 training dataset의 범위를 확장하는 전략을 설명
clicked
)와 검사된 횟수(examined
)를 저장하는 구조.
clicked
) 특정 쿼리에 대해 사용자가 클릭한 총 횟수.examined
) 사용자가 결과를 인지한 횟수.grade
: 문서의 관련성을 나타내는 기초 지표, clicked
를 examined
로 단순 나누어 계산.beta_grade
: grade
보다 정보량이 많은 사례에 더 높은 가중치를 두는 수정된 지표.grade
를 사용하였으나, 정보량의 중요성을 고려하여 beta_grade
를 채택.
이미지 설명: 데이터프레임은 쿼리와 문서 ID에 따라 클릭 및 검사를 기록하며,
grade
와 beta_grade
를 계산하여 문서의 관련성을 평가할 수 있음.
long_description_bm25
: 긴 설명에서의 BM25 검색.short_description_constant
: 짧은 설명에서의 상수 검색.random.seed(1234)
feature_set = [
{
"name": "long_description_bm25",
"store": "test",
"class": "org.apache.solr.ltr.feature.SolrFeature",
"params": {"q": "longDescription:(${keywords})"}
},
{
"name": "short_description_constant",
"store": "test",
"class": "org.apache.solr.ltr.feature.SolrFeature",
"params": {"q": "shortDescription:(${keywords})^=1"}
}
]
train, test = test_train_split(sdbn, train=0.8)
ranksvm_ltr(train, model_name='test1', feature_set=feature_set)
precision
사용, 10개의 상위 관련성 점수를 합산하여 평균 계산.Listing 12.3
의 개선된 모델을 기존 모델과 비교.feature_set_better = [
{
"name": "name_fuzzy",
"store": "test",
"class": "org.apache.solr.ltr.feature.SolrFeature",
"params": {"q": "name_ngram:(${keywords})"}
},
{
"name": "name_pf2",
"store": "test",
"class": "org.apache.solr.ltr.feature.SolrFeature",
"params": {"q": "{!edismax qf=name name pf2=name}(${keywords})"}
},
{
"name": "shortDescription_pf2",
"store": "test",
"class": "org.apache.solr.ltr.feature.SolrFeature",
"params": {"q": "{!edismax qf=shortDescription pf2=shortDescription}(${keywords})"}
},
]
train, test = test_train_split(sdbn, train=0.8)
ranksvm_ltr(train, 'test2', feature_set_better)
precision
을 비롯한 다양한 지표로 성능 측정.A/B 테스트는 사용자 경험을 개선하기 위한 중요한 도구로, 자동화된 LTR 시스템에서도 필수적인 역할을 수행 A/B 테스트는 두 가지 이상의 변형을 설정하고, 각 변형에 사용자를 무작위로 할당하여 결과를 비교하는 실험적 방법임.
이런 A/B 테스트는 시스템의 변화가 실제 비즈니스 목표에 어떤 영향을 미치는지 평가할 수 있는 궁극적인 도구로 작용
새로운 모델을 A/B 테스트에 배치하여 성능을 평가함으로써, 기존의 부진한 모델을 대체할 수 있는 가능성을 탐색
이런 과정을 통해 사용자 행동을 분석하고, 각 모델의 실제 성능을 평가할 수 있음.
모델이 실험실에서는 훌륭하지만, A/B 테스트에서는 실패하는 경우도 발생 이는 훈련 데이터의 잘못된 사양 때문일 수 있음.
이와 같은 문제를 통해 모델이 실패한 원인을 파악하고, 향후 개선 방향을 모색할 수 있음.
TL;DR
- A/B 테스트는 두 가지 이상의 변형을 비교하여 시스템의 변화를 평가하는 중요한 도구로, 자동화된 LTR 시스템에서 필수적임.
- 우수한 모델을 A/B 테스트에 투입하여 실제 사용자 행동을 기반으로 성능을 평가하고, 부진한 모델을 대체할 가능성을 탐색
- 실험실에서 훌륭한 모델이라도 A/B 테스트에서 실패할 수 있으며, 이는 훈련 데이터의 잘못된 사양이나 바이어스 문제 때문일 수 있음.
#
p296
검색 시스템에서 미시적 관점(zero in to the microscopic)으로 특정 쿼리와 문서의 관계를 살펴보면, 인과관계가 복잡해짐. 단일 제품은 구매 수가 적음 → 예를 들어, $1000짜리 TV는 많은 사람들이 보지만 구매하는 사람은 적음 → 데이터가 충분하지 않으면 쿼리에 대한 특정 제품의 관련성을 알 수 없음.
Figure 12.4 Presentation Bias를 설명 → 사용자가 볼 수 없는 결과와 상호작용하지 않으므로, 클릭 데이터가 없는 결과를 탐색해야
TL;DR
- Presentation Bias 사용자가 보지 못한 결과는 클릭되지 않기 때문에 관련성을 판단할 수 없고,
- Explore vs Exploit Tradeoff 현재의 지식을 활용(exploit)하면서 새로운 지식을 탐색(explore)하는 균형을 유지해야
- Active Learning 시스템이 스스로 학습에 참여하여 훈련 데이터를 확장하는 방법 필요.
p297
Explore vs Exploit Tradeoff 모델의 현재 지식을 활용하면서 새로운 지식을 탐색하는 균형 유지 필요.
Presentation Bias 해결 훈련 데이터가 부족한 영역을 탐지하고 보완해야
p298
Listing 12.5 새로운 feature_set 생성하여 explore
long_desc_match
, short_desc_match
, name_match
: 기존에 학습된 기능과 일치 여부 확인.has_promotion
: 마케팅 채널을 통해 홍보 중인 경우 1.0 값을 가짐.feature_set = [
{
"name" : "long_desc_match",
"store": "explore",
"class" : "org.apache.solr.ltr.feature.SolrFeature",
"params" : {
"q" : "longDescription:(${keywords})^=1"
}
},
...
{
"name" : "has_promotion",
"store": "explore",
"class" : "org.apache.solr.ltr.feature.SolrFeature",
"params" : {
"q" : "promotion_b:true"
}
},
]
결과 분석 특정 쿼리에서 누락된 문서 유형 식별 → transformers dvd
쿼리의 예시로 blind spots 탐지.
p299
Gaussian Process 예측과 함께 해당 예측의 확률 분포를 제공하는 통계 모델.
transformers dvd
의 결과와 관련된 현재 데이터의 빈틈을 체계적으로 탐색.query grade long_desc_match short_desc_match name_match has_promotion
618 transformers dvd 0.0 1.0 0.0 1.0 0.0
...
625 transformers dvd 0.0 1.0 0.0 1.0 0.0
TL;DR
- Blind Spots 훈련 데이터의 누락된 영역을 탐지하고 보완하여 모델 훈련 개선.
- Gaussian Process 탐색 대상 지역을 선택하여 체계적인 데이터 갭 탐지.
- 자동화된 탐색 수작업 분석을 넘어 체계적이고 자동화된 데이터 탐색 방법 제안.
위의 내용은 검색 시스템에서 발생하는 presentation bias 문제를 해결하기 위한 다양한 방법을 소개하고 있으며, 특히 explore vs exploit tradeoff와 active learning의 중요성을 강조 Gaussian Process를 활용하여 체계적으로 데이터의 누락된 영역을 탐지하고 보완하는 방법을 제안
Gaussian Processes(GP)는 머신러닝에서 중요한 역할을 수행하는 비모수적 베이지안 방법임. 이는 주어진 데이터 포인트들 사이의 관계를 나타내는 함수의 분포를 정의하여, 불확실성을 포함한 예측을 가능하게
\(m(x)\)는 평균 함수, \(k(x, x')\)는 공분산 함수
from sklearn.gaussian_process import GaussianProcessRegressor
y_train = transformers_dvds['grade']
x_train = transformers_dvds[['long_desc_match', 'short_desc_match', 'name_match', 'has_promotion']]
gpr = GaussianProcessRegressor()
gpr.fit(x_train, y_train)
transformers_dvds
데이터셋의 다양한 피쳐을 활용하여 모델 훈련.index = pd.MultiIndex.from_product([zero_or_one] * 4, names=['long_desc_match', 'short_desc_match', 'name_match', 'has_promotion'])
explore_options = pd.DataFrame(index=index).reset_index()
predictions_with_std = gpr.predict(explore_options[['long_desc_match', 'short_desc_match', 'name_match', 'has_promotion']], return_std=True)
explore_options['predicted_grade'] = predictions_with_std[0]
explore_options['prediction_stddev'] = predictions_with_std[1]
TL;DR
p304
long_desc_match
, short_desc_match
, name_match
, has_promotion
등의 속성으로 구성expected_improvement
값에 따라 정렬p305
theta
조정
theta
값 증가 → 표준 편차 높은 후보 선호theta
값 너무 높음 → 유용성 고려 없이 학습 목적의 후보 선택theta
값 너무 낮음 → 기존 지식에 치우침theta
값 선택
theta = 0.6
선택으로 기존 LTR 시스템 보강theta
파라미터는 후보 선택 방식에 큰 영향을 미치며, 적절한 값 설정 중요.p306
expected_improvement
기반 최적 탐색 후보 추출explore
함수: 탐색 벡터 기반 쿼리 변환, 무작위 문서 선택expected_improvement
계산으로 Solr에서 최적 후보 탐색 가능.p307
97360810042
를 검색 결과의 세 번째 위치에 삽입sessions_to_sdbn
함수로 새로운 세션 데이터에서 SDBN 판단 생성transformers dvd
쿼리에 대한 새로운 SDBN 판단 출력이 문서는 기대 개선 알고리즘을 활용한 후보 탐색 및 Solr 검색에 대한 심층적인 설명을 제공합니다. 특히, theta
파라미터 조정과 Solr에서의 탐색 과정을 통해 후보 선택 및 사용자 행동 분석의 중요성을 강조합니다. 추가로, 새로운 LTR 모델 훈련 및 A/B 테스트를 통한 성능 검증 과정을 설명합니다.
97368920347
을 가진 새로운 상품 The Transformers: The Movie - DVD
추가promotion_b
플래그가 true
로 설정됨 → 이전 섹션의 “탐색” 후보로 선정된 상품 중 하나has_promotion
기능을 메인 모델로 이동하여 훈련 실행has_promotion
을 LTR(Learning to Rank) 모델에 추가transformers dvd
검색 시 Precision 상승a_or_b_model
함수를 사용하여 모델 선택test1
과 test3
모델 간의 구매 비교test3
모델에서 구매 증가 확인 → 새로운 모델의 성공적인 탐색 및 학습 증명gpr
모델을 통해 탐색/착취 검색 결과 표시 및 클릭 수집has_promotion
기능을 추가하여 LTR 모델 성능 개선을 확인test3
모델의 우수한 성능을 실증위 과정을 통해 데이터의 맹점을 식별하고, 지속적으로 모델을 개선하여 사용자 경험을 향상시킬 수 있음. 새로운 탐색 기능을 통한 LTR 모델의 개선은 AI 기반 검색 시스템의 핵심 요소로 자리잡고 있음.
p312 - LTR 알고리즘 개요
Listing 12.13 - Fully Automated LTR Algorithm Summarized
exploit_feature_set = [
{
"name" : "name_fuzzy",
"store": "exploit",
"class" : "org.apache.solr.ltr.feature.SolrFeature",
"params" : {
"q" : "name_ngram:(${keywords})"
}
}
...
]
train, test = test_train_split(sdbn, train=0.8)
ranksvm_ltr(train, model_name='exploit', feature_set=exploit_feature_set)
eval_model(test, model_name='exploit', sdbn=new_sdbn)
explore_feature_set = [
{
"name" : "manufacturer_match",
"store": "explore",
"class" : "org.apache.solr.ltr.feature.SolrFeature",
"params" : {
"q" : "manufacturer:(${keywords})^=1"
}
}
...
]
sdbn = sessions_to_sdbn(sessions, prior_weight=10, prior_grade=0.2)
핵심 Fully Automated LTR 알고리즘은 기존의 클릭 데이터를 퇴출하고 새로운 트렌드에 맞는 특성을 선택하여 최적의 검색 순위를 도출. 모델은 블라인드 스팟을 탐색하여 사용자의 기대치를 최대화하도록 설계됨.
p313 - Presentation Bias와 Active Learning
핵심 Presentation Bias를 극복하기 위해 모델이 능동적으로 블라인드 스팟을 탐색하도록 하며, Gaussian Process와 같은 방법을 사용하여 누락된 데이터를 찾아 사용자가 기대하는 검색 결과를 제공.
p314 - Dense Vector 검색의 도입
p315 - 텍스트 임베딩을 통한 의미 표현
핵심 Dense Vector는 텍스트의 의미를 벡터로 표현하여 검색의 효율성을 높임. Transformer 모델 및 NLP 기법을 활용하여 텍스트 임베딩을 통해 검색 엔진의 쿼리 및 문서 해석을 강화.
Fully Automated LTR 알고리즘은 탐색과 Exploit을 통해 검색의 정확성을 높이며, Dense Vector와 NLP 기법은 검색의 효율성을 극대화 이런 기술은 패션 상품 검색과 같은 실제 응용 분야에서 이미지와 메타 정보를 기반으로 최적의 검색 결과를 제공하는 데 기여.
p316
핵심
dense vector search은 희소 vector search과 달리 텍스트의 의미를 포착하여 검색 및 순위 매김을 수행 희소 벡터는 주로 쿼리 용어의 존재와 개수에 의존하는 반면, dense 벡터는 쿼리 간에 비교 가능한 전역적 관련성을 제공 최근접 이웃 검색은 동일한 차원의 수치 벡터 색인을 통해 가장 유사한 벡터를 식별
p317
핵심: 임베딩은 텍스트의 의미를 벡터로 표현하여 동일한 벡터 공간에서 문서와 쿼리를 검색 코사인 유사도를 사용해 쿼리와 문서 벡터 간의 유사성을 측정하며, 동일한 벡터 공간에 존재해야 효과적임. 최근접 이웃은 쿼리와 가장 유사한 문서를 의미
p318
b와 c가 가장 가까우며, 세 벡터 중 b와 c가 가장 유사함
핵심: 세 벡터 간의 코사인 유사도를 계산하여 가장 유사한 벡터를 식별할 수 있음. dense 벡터 임베딩 공간에서는 수백 차원의 벡터를 사용하지만, 코사인 유사도 공식은 동일하게 적용됨. b와 c가 가장 유사한 벡터로 식별됨.
p319
3.1절에서 이 코사인 유사도 계산을 사용해 벡터 간 유사성을 점수화하는 방법 복습
TL;DR Transformer는 텍스트의 의미를 인코딩하여 벡터로 표현하는 D/L 모델로, BERT가 그 대표적인 예tldlau, 인코딩과 디코딩 과정은 분리되어 있어 임베딩을 독립적인 의미 표현으로 활용 가능 코사인 유사도는 벡터 간의 유사성을 측정하는 기본 도구로 사용됨.
TL;DR
이 글에서는 Transformer와 BERT의 역사적 배경, 기술적 구현 및 검색 시스템에서의 응용 방법을 상세히 설명하였음. 이런 지식을 활용하여 다양한 자연어 처리 응용 프로그램을 효과적으로 개발할 수 있음.
POST_type_id
필드 포함
POST_type_id=1
)POST_type_id=2
)url
, POST_type_id
, accepted_answer_id
, parent_id
, score
, view_count
, body
, title
, tags
, answer_count
, owner_user_id
POST_type_id=1
): 질문의 제목 요약POST_type_id=2
): 질문에 대한 답변 제공id=7
, POST_type_id=2
이 문서는 자연어 처리와 검색 시스템 구현에 관한 내용을 다루며, Outdoors 데이터셋을 활용한 인덱싱과 Transformer 모델을 통한 검색 성능 향상을 목표로 한다. SBERT를 통해 문장 유사성을 기반으로 한 검색을 수행하며, 이런 기술을 패션 상품 검색에 적용할 수 있는 가능성을 제시한다.
SBERT(Sentence-BERT)는 문장 임베딩을 생성하기 위한 모델로, RoBERTa 아키텍처를 기반으로
핵심: SBERT는 RoBERTa 기반의 문장 임베딩 모델로, 하이퍼파라미터 조정을 통해 성능을 최적화 NLI와 mean-tokens 기법을 사용하여 768 차원의 임베딩 벡터 생성.
사전 학습된 ‘roberta-base-nli-stsb-mean-tokens’ 모델을 sentence_transformers 라이브러리를 통해 불러와 문장 임베딩 생성.
from sentence_transformers import SentenceTransformer
stsb = SentenceTransformer('roberta-base-nli-stsb-mean-tokens')
문장 간 유사도 계산을 위해 PyTorch의 cosine similarity 함수를 사용하여 임베딩 간의 유사도 비교.
phrases = ["it's raining hard","it is wet outside","cars drive fast","motorcycles are loud"]
embeddings = stsb.encode(phrases, convert_to_tensor=True)
from sentence_transformers import util as STutil
similarities = STutil.pytorch_cos_sim(embeddings, embeddings)
유사도 점수를 기준으로 문장 쌍을 정렬하여 가장 유사한 쌍과 가장 비유사한 쌍을 식별.
import pandas as pd
a_phrases = []
b_phrases = []
scores = []
for a in range(len(similarities)-1):
for b in range(a+1, len(similarities)):
a_phrases.append(phrases[a])
b_phrases.append(phrases[b])
scores.append(float(similarities[a][b]))
df = pd.DataFrame({"phrase a":a_phrases,"phrase b":b_phrases, "score":scores})
df.sort_values(by=["score"], ascending=False, ignore_index=True)
임베딩 기술을 활용하여 자연어 자동 완성 기능 구현.
핵심: 유사도 기반 문장 비교는 의미적 유사성을 효과적으로 평가하며, 자연어 자동 완성에 유용하게 적용 가능. SpaCy와 SBERT를 결합하여 자연어 처리의 실제 활용 사례를 제시
이미지와 상품 메타 정보를 기반으로 SBERT를 활용한 패션 상품 검색 방법 탐색.
## Example for fashion item search
fashion_phrases = ["red dress", "blue jeans", "black shoes"]
fashion_embeddings = stsb.encode(fashion_phrases, convert_to_tensor=True)
fashion_similarities = STutil.pytorch_cos_sim(fashion_embeddings, fashion_embeddings)
핵심 SBERT와 통합 임베딩을 활용한 패션 상품 검색은 이미지와 텍스트 정보를 결합하여 효율적인 검색 결과를 제공하며, 자연어 처리와 컴퓨터 비전의 융합을 통한 혁신적인 검색 방법을 제시
phrases = []
sources = []
matcher = Matcher(nlp.vocab)
nountags = ['NN','NNP','NNS','NOUN']
verbtags = ['VB','VBD','VBG','VBN','VBP','VBZ','VERB']
matcher.add("noun_phrases", [[{"TAG":{"IN": nountags}, "IS_ALPHA": True,"OP":"+"}]])
matcher.add("verb_phrases", [[{"TAG":{"IN": verbtags}, "IS_ALPHA": True,"OP":"+", "LEMMA":{"NOT_IN":["be"]}}]])
for doc,idx in nlp.pipe(yieldTuple(df,"body",total=total), batch_size=40, n_threads=4, as_tuples=True):
text = doc.text
matches = matcher(doc)
for matchid,start,end in matches:
span = doc[start:end]
phrases.append(normalize(span))
sources.append(span.text)
concepts = {}
labels = {}
for i in range(len(phrases)):
phrase = phrases[i]
if phrase not in concepts:
concepts[phrase] = 0
labels[phrase] = sources[i]
concepts[phrase] += 1
concepts, labels = getConcepts(outdoors_dataframe, load_from_cache=True)
topcons = {k: v for (k, v) in concepts.items() if v > 5 }
print('Total number of labels:', len(labels.keys()))
print('Total number of concepts:', len(concepts.keys()))
print('Concepts with greater than 5 term frequency:', len(topcons.keys()))
print(json.dumps(topcons, indent=2))
def get_embeddings(concepts, minimum_frequency, load_from_cache=True):
phrases = [key for (key, tf) in concepts.items() if tf >= minimum_frequency]
if not load_from_cache:
embeddings = stsb.encode(phrases, convert_to_tensor=True)
with open('data/outdoors_embeddings.pickle', 'wb') as fd:
pickle.dump(embeddings, fd)
else:
with open('data/outdoors_embeddings.pickle', 'rb') as fd:
embeddings = pickle.load(fd)
return phrases, embeddings
minimum_frequency = 6
phrases, embeddings = get_embeddings(concepts, minimum_frequency, load_from_cache=True)
print('Number of embeddings:', len(embeddings))
print('Dimensions per embedding:', len(embeddings[0]))
이 블로그 포스트는 자연어 처리 기술을 검색 애플리케이션에 적용하는 방법을 다루고 있으며, SpaCy와 SBERT/RoBERTa를 활용한 개념 추출 및 임베딩 계산을 통해 검색 성능을 향상시키는 방법을 설명합니다. 패션 상품 검색 예시를 통해 실질적인 적용 사례도 제시합니다.
p336
p337
similarities = STutil.pytorch_cos_sim(embeddings[0:505], embeddings[0:505])
a_phrases = []
b_phrases = []
scores = []
for a in range(len(similarities)-1):
for b in range(a+1, len(similarities)):
a_phrases.append(phrases[a])
b_phrases.append(phrases[b])
scores.append(float(similarities[a][b]))
comparisons = pandas.DataFrame({"phrase a":a_phrases,"phrase b":b_phrases,"score":scores,"name":"similarity"})
comparisons = comparisons.sort_values(by=["score"], ascending=False, ignore_index=True)
p338
candidate_synonyms = comparisons[comparisons["score"]>0.0]
{
ggplot(candidate_synonyms, aes('name','score')) +
geom_violin(color='blue')
}
p339
TL;DR
def semantic_suggest(query, k=20):
matches = []
embeddings = stsb.encode([query], convert_to_tensor=True)
ids, distances = index.knnQuery(embeddings[0], k=k)
for i in range(len(ids)):
text = phrases[ids[i]]
dist = 1.0 - distances[i]
if dist > 0.75:
matches.append((text, dist))
if not len(matches):
matches.append((phrases[ids[1]], 1.0 - distances[1]))
return matches
TL;DR
p344
레이블링된 데이터셋를 사용하여 정확도를 측정하는 것이 중요 실제 고객에게 솔루션을 배포하기 전에 이런 방식으로 정확도를 측정해야 14장에서 질의응답 구현 시 레이블링된 데이터를 사용하여 실제 정확도 측정을 시연할 예정임.
추가적인 자동완성 값을 직접 시도해 볼 수 있음. 어떤 값들이 잘 작동하는지, 어떤 값들이 그렇지 않은지 확인할 수 있음.
지금까지 배운 내용을 바탕으로 dense vector search을 다음 단계로 발전시킬 예정임: 검색 시점에 쿼리 임베딩을 문서 임베딩과 쿼리하여 검색을 수행
자동완성을 첫 번째 구현으로 시작한 이유는 언어 유사성의 기본을 이해하는 데 도움이 되었기 때문임. 벡터 공간에서 유사하거나 유사하지 않은 이유에 대한 직관이 있어야 언어 임베딩을 사용할 때 무한히 반복되는 Recall 문제를 피할 수 있음. 이 직관을 쌓기 위해 각각 몇 단어로 이루어진 기본 개념의 매칭과 스코어링부터 시작했음.
이제 전체 문장을 비교하는 단계로 넘어갈 것임. 이를 통해 제목에 대한 의미론적 검색을 수행할 예정임. 쿼리로 시작하여 문서 목록을 반환 Outdoors Stack Exchange 데이터셋를 검색하고 있으므로 문서 제목은 기여자가 질문한 내용을 요약한 것임. 보너스로, 이전 섹션의 동일한 구현을 사용하여 유사한 질문 제목을 검색할 수 있음.
이 섹션은 대부분 이전 섹션의 인코딩 및 유사성 방법의 반복이므로 빠르게 진행될 것임. 코드도 더욱 짧아짐, 개념을 추출할 필요가 없기 때문임.
다음 단계로 진행
NMSLIB 인덱스는 제목 임베딩으로 구성됨. 이전의 자동완성 예제와 동일한 방법을 사용하지만 이제는 개념을 변환하는 대신 아웃도어 커뮤니티가 한 모든 질문의 제목을 변환 Listing 13.16에서는 제목을 임베딩으로 인코딩하는 과정을 보여줌.
p345
Listing 13.16 Encode the titles into embeddings
결과
Figure 13.8 모든 제목 임베딩 간의 유사성
Figure 13.8과 Figure 13.7의 개념 유사성 분포 비교. 제목과 개념 간의 차이로 인해 약간 다른 형태와 점수 분포를 보임. Figure 13.7은 더 긴 ‘바늘’ 모양을 가짐. 이는 제목이 더 구체적이기 때문임.
titles = list(filter(None, list(outdoors_dataframe['title'])))
embeddings = get_embeddings(titles)
print('Number of embeddings:', len(embeddings))
print('Dimensions per embedding:', len(embeddings[0]))
p346
제목이 명사 및 동사 구보다 더 구체적이므로 다르게 연관되고, 임베딩 생성 후 최근접 이웃 인덱스를 쉽게 생성할 수 있음, Listing 13.17 참조. (Listing 13.17 Create the ANN title embeddings index)
새로 생성된 인덱스를 통해 검색이 쉬워짐! Listing 13.18에서는 쿼리에 대한 질문 제목에 대한 ANN 검색을 구현하는 새로운 메서드를 보여줌. 이는 Listing 13.15에서 자동완성을 위해 구현한 semantic_suggest
와 유사
Listing 13.18 Perform a semantic search for titles
import nmslib
index = nmslib.init(method='hnsw', space='cosinesimil')
index.addDataPointBatch(embeddings)
index.createIndex(print_progress=True)
def semantic_search(query, k=10, minimum_similarity=0.6):
matches = []
embeddings = stsb.encode([query], convert_to_tensor=True)
ids, distances = index.knnQuery(embeddings[0], k=k)
for i in range(len(ids)):
text = titles[ids[i]]
dist = 1.0 - distances[i]
if dist > minimum_similarity:
matches.append((text, dist))
if not len(matches):
matches.append((titles[ids[1]], 1.0 - distances[1]))
print_labels(query, matches)
semantic_search('mountain hike')
p347
mountain hike
와 관련된 질문임. 그러나 가장 관련성이 높은 문서인지는 알 수 없음. 이는 mountain hike
가 충분한 맥락을 제공하지 않기 때문임. 따라서 제목이 쿼리와 의미론적으로 유사하지만 사용자가 원하는 문서인지 판단할 수 없음.그럼에도 불구하고 이 임베딩 기반 검색 방법은 매칭 및 랭킹 도구 상자에 흥미로운 새로운 기능을 제공 그러나 결과가 더 나은지는 맥락에 따라 다름.
Listing 13.17에서 기본 minimum_similarity
점수 임계값을 0.6보다 크게 설정 Figure 13.8의 제목 유사성 분포를 검사하고 이 값을 0.6보다 다르게 설정할 것인지 고려할 수 있음.
minimum_similarity
를 0.6보다 낮은 값(e.g., 0.5)으로 설정하면 Recall이 증가할 수 있으며, k
를 10보다 높은 값(e.g., 250)으로 변경하여 리랭크 윈도우 크기로 설정할 수 있음. 그런 다음 이 더 큰 결과 집합을 사용하여 Learning to Rank 리랭킹 단계에서 여러 기능 중 하나로 코사인 유사성을 사용하여 리랭킹을 수행할 수 있음.
Solr는 아직 이 기능을 제공하지 않지만 Vespa.ai와 같은 다른 엔진에서는 가능 10장에서 배운 내용을 사용하여 어떻게 dense 벡터 유사성을 학습-랭크 모델에 통합할 수 있을지 생각해보라.
그래서 다시 자동화된 관련성 조정의 게임으로 돌아옴. 임베딩의 코사인 유사성은 성숙한 AI 기반 검색 스택에서 많은 신호나 기능 중 하나임. 이 유사성은 개인화, 학습-랭크와 함께 사용될 기능임.
Results for: mountain hike
0.725 | How is elevation gain and change measured for hiking trails?
0.706 | Hints for hiking the west highland way
0.698 | Fitness for hiking to Everest base camp
0.697 | Which altitude profile and height is optimal for Everesting by hiking?
0.678 | How to prepare on hiking routes?
0.678 | Long distance hiking trail markings in North America or parts thereof
0.675 | How far is a reasonable distance for someone to hike on their first trip?
0.668 | How to plan a day hike
0.666 | How do I Plan a Hiking Trip to Rocky Mountain National Park, CO
0.665 | Is there special etiquette for hiking the Appalachian Trail (AT) during AT Season
TL;DR