from sentence_transformers import SentenceTransformer
# Load the free, open-source embedding model.# The model downloads from Hugging Face on first run and saves locally.# trust_remote_code=True is required by this model:
model = SentenceTransformer("nomic-ai/nomic-embed-text-v1", trust_remote_code=True)defget_embedding(text, precision="float32"):# Use this function when embedding text you plan to store in MongoDB:return model.encode(text, precision=precision).tolist()defget_query_embedding(text, precision="float32"):# Use this function when embedding a user's search query:return model.encode(text, precision=precision).tolist()ขับเคลื่อนโดย
คำเตือน: trust_remote_code=True อนุญาตให้โมเดลรันโค้ด Python เฉพาะของโมเดลที่ดาวน์โหลดจาก Hugging Face บนเครื่องของคุณ หากซอร์สโค้ดของโมเดลบน Hugging Face ถูกเจาะหรือถูกอัปเดตด้วยโค้ดอันตราย โค้ดนั้นจะถูกรันอัตโนมัติในสภาพแวดล้อมของคุณ ควรใช้ความระมัดระวังก่อนใช้พารามิเตอร์นี้ในการใช้งานจริงในระบบโปรดักชัน
from embedding_utils import get_embedding
from pymongo import MongoClient
# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient("mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search")
collection = mongodb_client["sample_db"]["documents"]# Sample data:
sample_documents =[{"title":"MongoDB Atlas","text":"MongoDB Atlas is a fully managed cloud database."},{"title":"Vector Search","text":"Vector search finds results based on semantic meaning."},{"title":"Nomic AI","text":"nomic-embed-text-v1 is a free, open-source embedding model."},]
docs_to_insert =[]for doc in sample_documents:
embedding = get_embedding(doc["text"])
docs_to_insert.append({"title": doc["title"],"text": doc["text"],# The vector lives alongside your original data in the same document:"embedding": embedding
})# Drop the collection before each run to avoid inserting duplicate documents:
collection.drop()
result = collection.insert_many(docs_to_insert)print(f"Inserted {len(result.inserted_ids)} documents with embeddings.")
mongodb_client.close()ขับเคลื่อนโดย
from pymongo.mongo_client import MongoClient
from pymongo.operations import SearchIndexModel
import time
# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient("mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search")# Point to the same database and collection you used in the previous step:
database = mongodb_client["sample_db"]
collection = database["documents"]# Define the vector search index.# The three required fields tell MongoDB what to index and how to compare vectors:
search_index_model = SearchIndexModel(
definition={"fields":[{"type":"vector","path":"embedding",# The field name from generate_embeddings.py"numDimensions":768,# nomic-embed-text-v1 always outputs 768 dimensions"similarity":"cosine"# Recommended similarity function for this model}]},
name="vector_index",type="vectorSearch")
result = collection.create_search_index(model=search_index_model)print("New search index named "+ result +" is building.")# Poll every five seconds until the index is ready to accept queries:print("Polling to check if the index is ready. This may take up to a minute.")
predicate =lambda index: index.get("queryable")isTruewhileTrue:
indices =list(collection.list_search_indexes(result))iflen(indices)and predicate(indices[0]):break
time.sleep(5)print(result +" is ready for querying.")
mongodb_client.close()ขับเคลื่อนโดย
New search index named vector_index is building.
Polling to check if the index is ready. This may take up to a minute.
vector_index is ready for querying.ขับเคลื่อนโดย
ดัชนีอาจใช้เวลาสร้างถึงหนึ่งนาที ลูปการโพลจะตรวจทุก 5 วินาทีและออกจากลูปก็ต่อเมื่อ MongoDB ยืนยันว่าดัชนีพร้อมคิวรีแล้ว อย่าไปขั้นตอนถัดไปจนกว่าจะเห็นข้อความว่า "vector_index is ready for querying."
from embedding_utils import get_query_embedding
user_query ="I need an automated, scalable system for serious information storage"
query_vector = get_query_embedding(user_query)print(f"Query: '{user_query}'")print(f"Vector dimensions: {len(query_vector)}")print(f"First 5 values: {query_vector[:5]}")ขับเคลื่อนโดย
<All keys matched successfully>
Query: 'I need an automated, scalable system for serious information storage'
Vector dimensions: 768
First 5 values: [0.0025914530269801617, 0.09862980246543884, -0.023092379793524742, -0.0171672236174345, -0.05548065900802612]ขับเคลื่อนโดย
from embedding_utils import get_query_embedding
from pymongo import MongoClient
# Replace the placeholder with your Atlas connection string:
mongodb_client = MongoClient("mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/",
appname="devrel-tutorial-python-semantic-search")
collection = mongodb_client["sample_db"]["documents"]# Generate a query vector from the user's search input:
user_query ="I need an automated, scalable system for serious information storage"
query_vector = get_query_embedding(user_query)# Define the $vectorSearch aggregation pipeline:
pipeline =[{"$vectorSearch":{"index":"vector_index",# The index created in create_vector_index.py"path":"embedding",# The field that holds your stored vectors"queryVector": query_vector,# The vector generated from the user's query"numCandidates":150,# How many neighbors MongoDB considers"limit":3# How many results to return}},{"$project":{"_id":0,"title":1,"text":1,"score":{"$meta":"vectorSearchScore"# Relevance score for each result}}}]
results = collection.aggregate(pipeline)print(f"\nTop results for query: '{user_query}'\n")for doc in results:print(f"Title: {doc['title']}")print(f"Text: {doc['text']}")print(f"Score: {doc['score']:.4f}")print()
mongodb_client.close()ขับเคลื่อนโดย
<All keys matched successfully>
Top results for query: 'I need an automated, scalable system for serious information storage.'
Title: MongoDB Atlas
Text: MongoDB Atlas is a fully managed cloud database.
Score: 0.7211
Title: Nomic AI
Text: nomic-embed-text-v1 is a free, open-source embedding model.
Score: 0.6818
Title: Vector Search
Text: Vector search finds results based on semantic meaning.
Score: 0.6642ขับเคลื่อนโดย
"MongoDB Atlas" ได้คะแนนสูงสุด แม้คำค้นหา "I need an automated, scalable system for serious information storage" จะไม่มีคำใดตรงกับประโยค "MongoDB Atlas is a fully managed cloud database." การค้นหาส่งคืนเพราะเวกเตอร์มีความหมายใกล้กัน: "...ระบบอัตโนมัติที่ปรับขยายได้สำหรับการเก็บข้อมูล" แผนที่เชิงความหมายไปยัง "...ฐานข้อมูลคลาวด์ที่มีการจัดการเต็มรูปแบบ" การค้นหาข้อความสำหรับคำค้นหาเดียวกันจะได้ศูนย์ผลลัพธ์ เพราะไม่มีคำในคำค้นหาปรากฏตรงตัวในเอกสารใดเลย นี่คือ semantic search ที่ทำงานตามวัตถุประสงค์
นี่คือช่วงเวลาที่ การค้นหาข้อความแบบปกติทำได้ไม่ดีพอ ตัวอย่างต่อไปนี้คือสิ่งที่เกิดขึ้นจริง:
Semantic search แก้ปัญหานี้ได้ เพราะเข้าใจความหมายและเจตนาของคำค้นหา แทนการจับคู่คำที่เหมือนกันแบบเป๊ะๆ
บทเรียนนี้จะแสดงวิธีทำ semantic search ใน MongoDB ด้วย Python และโมเดลอิมเบดดิ้งฟรี nomic-embed-text-v1.
หมายเหตุ:
nomic-embed-text-v1มีขนาดประมาณ 0.27 GB เมื่อถูกโหลดเข้าเมมโมรี ดังนั้นควรมี RAM ว่างอย่างน้อย 1 GB ก่อนรันสคริปต์ใดๆ ในบทเรียนนี้ ครั้งแรกจะดาวน์โหลดโมเดลจาก Hugging Face จึงอาจใช้เวลามากขึ้นตามความเร็วอินเทอร์เน็ต ครั้งถัดไปจะโหลดจากแคชในเครื่อง การประมวลผลด้วย CPU จะช้ากว่า GPU มาก บนเครื่องที่ไม่มี GPU เฉพาะ โมเดลจะรันบน CPU และใช้ RAM แทน VRAM ดังนั้นการสร้างอิมเบดดิ้งอาจใช้เวลานานกว่าในบทเรียนนี้จะได้เรียนรู้:
$vectorSearchและตีความผลลัพธ์สามารถดูโค้ดตัวอย่างทั้งหมดของบทเรียนนี้ได้ที่ ที่เก็บ GitHub.
Text Search เทียบกับ Semantic Search
Text search จะจับคูเอกสารที่มีคำเหมือนกับคำค้นหาของคุณแบบตรงตัว ส่วน Semantic search จะจับคูเอกสารที่มีความหมายเดียวกับคำค้นหาของคุณ แม้จะใช้คำที่ต่างกันโดยสิ้นเชิงก็ตาม
ตารางต่อไปนี้แสดงสิ่งที่แต่ละแนวทางส่งคืนสำหรับคำค้นหา "heart problems":
แนวคิดของ Semantic Search
มีสามแนวคิดหลักสำหรับ semantic search ใน MongoDB ซึ่งจะช่วยให้เข้าใจว่าทำไมแต่ละขั้นตอนในบทเรียนนี้จึงทำงานแบบนั้น
เวกเตอร์อิมเบดดิ้ง
โมเดลอิมเบดดิ้งจะแปลงข้อความเป็นรายการตัวเลขความยาวคงที่ที่เรียกว่าเวกเตอร์ โดยแต่ละค่าจะแทนมิติของความหมาย
ข้อความสองชิ้นที่มีความหมายคล้ายกันจะได้เวกเตอร์ที่มีค่าใกล้กันในเชิงตัวเลข "cardiac conditions" และ "heart problems" จะอยู่ใกล้กันในเวกเตอร์สเปซ แม้จะไม่มีคำร่วมกันเลย ความใกล้เคียงนั้นเองที่ทำให้ semantic search เป็นไปได้
บทเรียนนี้ใช้โมเดลอิมเบดดิ้ง
nomic-embed-text-v1ซึ่งเป็นโอเพ่นซอร์ส ฟรี และรันบนเครื่องของคุณทั้งหมด โมเดลจะถูกดาวน์โหลดอัตโนมัติจาก Hugging Face เมื่อรันสคริปต์ครั้งแรก และบันทึกไว้ในเครื่องสำหรับครั้งถัดไป โมเดลนี้ให้ค่า 768 ตัวต่ออินพุตหนึ่งรายการดัชนีการค้นหาเวกเตอร์
ดัชนีการค้นหาเวกเตอร์บอก MongoDB ว่าเขตข้อมูลใดเก็บอิมเบดดิ้งไว้ จำนวนมิติควรเป็นเท่าไร และจะใช้ฟังก์ชันวัดความคล้ายแบบใดในการเปรียบเทียบ ต้องสร้างดัชนีนี้ก่อนจึงจะรันคำสั่ง
$vectorSearchได้สเตจการรวมข้อมูล
$vectorSearch$vectorSearchคือสเตจของการรวมข้อมูลที่ใช้รันการค้นหา รับเวกเตอร์คำค้น ค้นหาในฟิลด์ที่ทำดัชนีไว้ และส่งคืนเอกสารโดยจัดอันดับตามความคล้ายเชิงความหมาย สามารถเชนร่วมกับ$projectและสเตจอื่นๆ ได้เหมือนกับไปป์ไลน์การรวมข้อมูลทั่วไปมาเริ่มกันเลย
สิ่งที่ต้องเตรียม
ก่อนเริ่ม โปรดตรวจสอบให้พร้อมตามนี้:
pymongoเวอร์ชัน 4.7 ขึ้นไป, แพ็กเกจ Pythonsentence-transformersและeinopsติดตั้งแล้วตั้งค่าโปรเจ็กต์
สร้างโฟลเดอร์สำหรับโปรเจ็กต์และเข้าไปยังโฟลเดอร์นั้นในเทอร์มินัล:
สร้างและเปิดใช้งาน virtual environment เพื่อแยกการพึ่งพาแพ็กเกจของโปรเจ็กต์นี้:
บน Windows ให้เปิดใช้งาน virtual environment ด้วย:
จากนั้นติดตั้งแพ็กเกจที่ต้องใช้:
คุณจะสร้างไฟล์ Python หนึ่งไฟล์สำหรับแต่ละขั้นตอนของบทเรียนนี้ โดยเก็บทั้งหมดไว้ในโฟลเดอร์
mongodb-semantic-searchสร้างไฟล์ยูทิลิตีสำหรับอิมเบดดิ้ง
สร้างไฟล์ชื่อ
embedding_utils.pyไฟล์นี้จะเก็บฟังก์ชันอิมเบดดิ้งทั้งสองที่ใช้ในบทเรียนนี้:คำเตือน:
trust_remote_code=Trueอนุญาตให้โมเดลรันโค้ด Python เฉพาะของโมเดลที่ดาวน์โหลดจาก Hugging Face บนเครื่องของคุณ หากซอร์สโค้ดของโมเดลบน Hugging Face ถูกเจาะหรือถูกอัปเดตด้วยโค้ดอันตราย โค้ดนั้นจะถูกรันอัตโนมัติในสภาพแวดล้อมของคุณ ควรใช้ความระมัดระวังก่อนใช้พารามิเตอร์นี้ในการใช้งานจริงในระบบโปรดักชันสร้างและจัดเก็บเวกเตอร์อิมเบดดิ้ง
สร้างไฟล์ชื่อ
generate_embeddings.pyไฟล์นี้จะอิมพอร์ตฟังก์ชันอิมเบดดิ้งจากembedding_utils.pyสร้างเวกเตอร์สำหรับฟิลด์ข้อความของเอกสารแต่ละรายการ และจัดเก็บเอกสารเต็ม รวมถึงอิมเบดดิ้ง ไว้ใน MongoDB:ในโค้ดข้างต้น
collection.drop()จะลบเอกสารและดัชนีทั้งหมดในคอลเลกชันก่อนแต่ละการรัน หากรันgenerate_embeddings.pyซ้ำโดยไม่มีบรรทัดนี้ จะทำให้เพิ่มเอกสารซ้ำ ซึ่งทำให้$vectorSearchส่งคืนเอกสารเดียวกันหลายครั้ง การดร็อปคอลเลกชันจะลบดัชนีเวกเตอร์เสิร์ชไปด้วย ดังนั้นต้องรันcreate_vector_index.pyใหม่ทุกครั้งที่รันgenerate_embeddings.pyอีกครั้งรันสคริปต์:
จะได้เอาต์พุตดังนี้ในเทอร์มินัล:
ขณะนี้แต่ละเอกสารใน MongoDB จะมีทั้งข้อความต้นฉบับและเวกเตอร์ 768 มิติอยู่ในฟิลด์
embeddingเวกเตอร์ถูกเก็บรวมกับข้อมูลเดิมในเอกสารเดียว จึงไม่ต้องมีที่เก็บแยกหรือการค้นหาเพิ่มเติมขณะคิวรีสร้างดัชนี MongoDB Vector Search
สร้างไฟล์ชื่อ
create_vector_index.pyไฟล์นี้จะกำหนดและสร้างดัชนีการค้นหาเวกเตอร์บนฟิลด์embeddingเพื่อให้ MongoDB รันคำสั่ง$vectorSearchกับคอลเลกชันของคุณได้การกำหนดดัชนีต้องใช้สามฟิลด์:
path: ฟิลด์ที่เก็บอิมเบดดิ้ง (embeddingในบทเรียนนี้)numDimensions: ต้องตรงกับขนาดเอาต์พุตของโมเดล (768สำหรับnomic-embed-text-v1)similarity: ฟังก์ชันเปรียบเทียบ (cosineเหมาะกับโมเดลนี้)ค่า
numDimensionsต้องตรงกับโมเดลอิมเบดดิ้งของคุณอย่างแม่นยำ หากไม่ตรง การสร้างดัชนีจะล้มเหลว:รันสคริปต์:
จะได้เอาต์พุตดังนี้ในเทอร์มินัล:
ดัชนีอาจใช้เวลาสร้างถึงหนึ่งนาที ลูปการโพลจะตรวจทุก 5 วินาทีและออกจากลูปก็ต่อเมื่อ MongoDB ยืนยันว่าดัชนีพร้อมคิวรีแล้ว อย่าไปขั้นตอนถัดไปจนกว่าจะเห็นข้อความว่า "vector_index is ready for querying."
แปลงคำค้นหาให้เป็นเวกเตอร์
สร้างไฟล์ชื่อ
generate_query_vector.pyไฟล์นี้จะอิมพอร์ตฟังก์ชันget_query_embedding()จากไฟล์embedding_utils.pyรันไฟล์generate_query_vector.pyเพื่อยืนยันว่าโมเดลอิมเบดดิ้งโหลดได้ถูกต้องและให้เวกเตอร์ความยาว 768 มิติ:get_query_embedding()ที่ขั้นตอนถัดไปจะอิมพอร์ตไปใช้ต้องใช้โมเดลเดียวกันกับขั้นตอนแรก หากใช้คนละโมเดล เวกเตอร์จะอยู่คนละสเปซตัวเลข ทำให้เปรียบเทียบกันไม่ได้:
รันสคริปต์:
คุณจะได้เอาต์พุตคล้ายกับนี้:
ในเอาต์พุตข้างต้น บรรทัด
Vector dimensions: 768ยืนยันว่าผลลัพธ์ของโมเดลตรงกับอิมเบดดิ้งที่จัดเก็บไว้รันคิวรี
$vectorSearchสร้างไฟล์ชื่อ
run_vector_search.pyไฟล์นี้จะอิมพอร์ตฟังก์ชันget_query_embedding()จากไฟล์embedding_utils.pyและแปลงคำค้นหาของผู้ใช้เป็นเวกเตอร์run_vector_search.pyจะรันไปป์ไลน์การรวมข้อมูล$vectorSearchและพิมพ์ผลลัพธ์ที่จัดอันดับตามความใกล้เคียงเชิงความหมาย:ในโค้ดข้างต้น มีพารามิเตอร์สองตัวควบคุมพฤติกรรมการค้นหา:
numCandidatesกำหนดขนาดพูลเริ่มต้นที่ MongoDB จะพิจารณาก่อนคัดไปยังผลลัพธ์สุดท้าย ค่าที่มากขึ้นช่วยเพิ่มการครอบคลุมแต่ใช้เวลานานขึ้นเล็กน้อยlimitกำหนดจำนวนผลลัพธ์ที่ต้องการ ในบทเรียนนี้limitตั้งไว้ที่ 3 และ numCandidates ตั้งไว้ที่ 150 แนวทางเริ่มต้นที่พบบ่อยคือกำหนดnumCandidatesประมาณ 10–15 เท่าของค่าlimitตอนนี้ให้รันสคริปต์:
จะได้เอาต์พุตคล้ายต่อไปนี้:
"MongoDB Atlas" ได้คะแนนสูงสุด แม้คำค้นหา "I need an automated, scalable system for serious information storage" จะไม่มีคำใดตรงกับประโยค "MongoDB Atlas is a fully managed cloud database." การค้นหาส่งคืนเพราะเวกเตอร์มีความหมายใกล้กัน: "...ระบบอัตโนมัติที่ปรับขยายได้สำหรับการเก็บข้อมูล" แผนที่เชิงความหมายไปยัง "...ฐานข้อมูลคลาวด์ที่มีการจัดการเต็มรูปแบบ" การค้นหาข้อความสำหรับคำค้นหาเดียวกันจะได้ศูนย์ผลลัพธ์ เพราะไม่มีคำในคำค้นหาปรากฏตรงตัวในเอกสารใดเลย นี่คือ semantic search ที่ทำงานตามวัตถุประสงค์
สรุปประเด็นสำคัญ
numDimensionsในการกำหนดดัชนีต้องตรงกับขนาดเอาต์พุตของโมเดลอิมเบดดิ้งอย่างแม่นยำnomic-embed-text-v1ให้ค่า 768 มิติเสมอinput_type="document"จะปรับอิมเบดดิ้งให้เหมาะกับการจัดเก็บ ส่วนinput_type="query"จะปรับให้เหมาะกับการดึงคืน ใช้ประเภทให้ถูกต้องในแต่ละขั้นตอนnumCandidatesควบคุมขนาดของตาข่ายการค้นหาที่ MongoDB ครอบไว้ก่อนจำกัดเหลือผลลัพธ์ตามlimitค่าที่มากขึ้นช่วยเพิ่มการครอบคลุมแต่ใช้เวลาเพิ่มvectorSearchScoreจัดอันดับผลลัพธ์ตามความคล้ายเชิงความหมาย ผลลัพธ์ไม่จำเป็นต้องมีคำใดๆ จากคำค้นหาแบบตรงตัว ช่วงคะแนนอาจแตกต่างตามโมเดลและดาต้าเซ็ต แต่ขอบเขตด้านล่างเป็นจุดเริ่มต้นที่มีประโยชน์สำหรับnomic-embed-text-v1กับ cosine similarity:สามารถดูโค้ดตัวอย่างทั้งหมดของบทเรียนนี้ได้ที่ ที่เก็บ GitHub.
อ่านเพิ่มเติม