28 คะแนน โดย xguru 2024-08-26 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • สามารถสร้างเสิร์ชเอนจินแบบไฮบริดภายใน Postgres ที่รองรับทั้ง semantic search, full-text search และ fuzzy search ได้
  • การค้นหาเป็นส่วนสำคัญของแอปจำนวนมาก แต่การทำให้ดีจริงนั้นไม่ง่าย โดยเฉพาะใน RAG pipeline ที่คุณภาพของการค้นหาอาจเป็นตัวตัดสินความสำเร็จหรือความล้มเหลวของทั้งกระบวนการ
  • แม้ semantic search จะเป็นเทรนด์ แต่การค้นหาแบบอิงคำศัพท์ดั้งเดิมก็ยังคงเป็นแกนหลักของระบบค้นหา
  • เทคนิคเชิงความหมายช่วยปรับปรุงผลลัพธ์ได้ แต่จะทำงานได้ดีที่สุดเมื่ออยู่บนพื้นฐานของ text search ที่แข็งแรง

การสร้างเสิร์ชเอนจินด้วย Postgres

  • ผสาน 3 เทคนิคเข้าด้วยกัน:
    • full-text search ด้วย tsvector
    • semantic search ด้วย pgvector
    • fuzzy matching ด้วย pg_trgm
  • แนวทางนี้อาจไม่ได้ดีที่สุดแบบไร้ข้อกังขาในทุกกรณี แต่เป็นทางเลือกที่ยอดเยี่ยมแทนการสร้าง search service แยกต่างหาก
  • เป็นจุดเริ่มต้นที่แข็งแรงซึ่งสามารถนำไปใช้งานและขยายต่อได้ภายในฐานข้อมูล Postgres เดิม
  • เหตุผลที่ควรใช้ Postgres กับทุกอย่าง: Just use Postgres for everything, PostgreSQL is enough, Just use Postgres

การทำ FTS และ semantic search

  • Supabase มี เอกสารเรื่องการทำ hybrid search ที่ยอดเยี่ยม จึงจะใช้เป็นจุดเริ่มต้น
  • ทำ FTS ตามไกด์ด้วย GIN index และทำ semantic search ด้วย pgvector (หรือที่เรียกว่า bi-encoder dense retrieval)
  • จากประสบการณ์ส่วนตัว การเลือก embedding ขนาด 1536 มิติให้ผลลัพธ์ที่ดีกว่ามาก
  • แทนที่ฟังก์ชันของ Supabase ด้วย CTE และ query แล้วเติม $ นำหน้าพารามิเตอร์
  • ที่นี่ใช้ RRF(Reciprocal Ranked Fusion) เพื่อรวมผลลัพธ์
  • วิธีนี้ช่วยให้รายการที่อยู่อันดับสูงในหลายลิสต์ได้รับอันดับสูงในลิสต์สุดท้าย
  • และยังช่วยไม่ให้รายการที่อยู่อันดับสูงในบางลิสต์แต่ต่ำในลิสต์อื่น ได้อันดับสูงเกินไปในผลลัพธ์สุดท้าย
  • การคำนวณคะแนนโดยใช้ลำดับอันดับเป็นตัวหารอาจทำให้เรคอร์ดที่อยู่อันดับต่ำเสียเปรียบ
  • จุดที่น่าสังเกต
    • $rrf_k: เพื่อป้องกันไม่ให้รายการอันดับหนึ่งได้คะแนนสูงเกินไปมาก (เพราะใช้การหารด้วยอันดับ) จึงมักเพิ่มค่าคงที่ k ในตัวส่วนเพื่อทำให้คะแนนเรียบขึ้น
    • $_weight: สามารถกำหนดน้ำหนักให้แต่ละวิธีได้ ซึ่งมีประโยชน์มากในการปรับจูนผลลัพธ์

การทำ fuzzy search

  • แม้ว่าวิธีก่อนหน้านี้จะแก้ปัญหาได้มาก แต่จะมีปัญหาทันทีเมื่อ named entity มีการสะกดผิด
  • semantic search ช่วยลดปัญหาบางส่วนได้ด้วยการจับความคล้ายคลึงกัน แต่ยังลำบากกับชื่อ ตัวย่อ และข้อความอื่นที่ไม่ได้คล้ายกันในเชิงความหมาย
  • เพื่อบรรเทาปัญหานี้ จึงเพิ่มส่วนขยาย pg_trgm เพื่อให้รองรับ fuzzy search
    • มันทำงานด้วย trigram โดย trigram จะแยกคำออกเป็นลำดับอักขระ 3 ตัว จึงเหมาะกับ fuzzy search
    • ทำให้สามารถจับคู่คำที่คล้ายกันได้แม้จะมีการพิมพ์ผิดหรือเปลี่ยนแปลงเล็กน้อย
    • ตัวอย่างเช่น "hello" กับ "helo" มี trigram ร่วมกันหลายชุด จึงจับคู่กันได้ง่ายขึ้นในการค้นหาแบบ fuzzy
  • สร้าง index ใหม่สำหรับคอลัมน์ที่ต้องการ แล้วเพิ่มเข้าไปใน query การค้นหาโดยรวม
  • ส่วนขยาย pg_trgm เปิดเผยตัวดำเนินการ % เพื่อกรองข้อความที่มีความคล้ายมากกว่า pg_trgm.similarity_threshold (ค่าเริ่มต้นคือ 0.3)
  • ยังมีตัวดำเนินการอื่นที่มีประโยชน์อีกหลายตัว

การปรับจูน full-text search

  • ปรับน้ำหนักของ tsvector: เอกสารจริงไม่ได้มีแค่ชื่อเรื่อง แต่ยังมีเนื้อหาด้วย
  • ถึงจะมีหลายคอลัมน์ ก็ยังคงเก็บ embedding ไว้เพียงคอลัมน์เดียว
  • โดยส่วนตัวพบว่าการเก็บ title และ body ไว้ใน embedding เดียวกันไม่ได้ต่างด้านประสิทธิภาพมากนักเมื่อเทียบกับการเก็บหลาย embedding
  • ท้ายที่สุดแล้ว title ควรเป็นการสรุปเนื้อหาแบบสั้น ๆ อยู่แล้ว จึงควรทดลองตามความเหมาะสม
  • title มักสั้นและอุดมด้วยคีย์เวิร์ด ขณะที่ body ยาวกว่าและมีรายละเอียดมากกว่า
  • ดังนั้นจึงควรปรับว่าน้ำหนักของคอลัมน์ full-text search ต่าง ๆ จะสัมพันธ์กันอย่างไร
  • สามารถกำหนดลำดับความสำคัญตามตำแหน่งหรือความสำคัญของคำในเอกสารได้
    • A-weight: สำคัญที่สุด (เช่น ชื่อเรื่อง, หัวข้อ) ค่าเริ่มต้น 1.0
    • B-weight: สำคัญ (เช่น ช่วงต้นเอกสาร, บทสรุป) ค่าเริ่มต้น 0.4
    • C-weight: ความสำคัญมาตรฐาน (เช่น เนื้อความหลัก) ค่าเริ่มต้น 0.2
    • D-weight: สำคัญน้อยที่สุด (เช่น เชิงอรรถ, หมายเหตุ) ค่าเริ่มต้น 0.1
  • ปรับน้ำหนักตามโครงสร้างเอกสารและความต้องการของแอปเพื่อจูน relevance ให้ละเอียดขึ้น
  • เหตุผลที่ให้น้ำหนักกับชื่อเรื่องมากกว่า
    • เพราะชื่อเรื่องมักสื่อหัวข้อหลักของเอกสารได้อย่างกระชับ
    • ผู้ใช้มักกวาดตาดูชื่อเรื่องก่อนตอนค้นหา ดังนั้นการตรงกับคีย์เวิร์ดในชื่อเรื่องจึงมักสัมพันธ์กับเจตนาของผู้ใช้มากกว่าการตรงกันในเนื้อหา

การปรับตามความยาว

  • หากอ่านเอกสารของ ts_rank_cd จะพบว่ามีพารามิเตอร์ normalization
    • ฟังก์ชันจัดอันดับทั้งสองใช้ตัวเลือกจำนวนเต็ม normalization เพื่อระบุว่าความยาวของเอกสารควรมีผลต่ออันดับอย่างไร ตัวเลือกจำนวนเต็มนี้เป็น bit mask ที่ควบคุมพฤติกรรมได้หลายแบบ: สามารถใช้ | เพื่อระบุมากกว่าหนึ่งพฤติกรรมได้ (เช่น 2|4)

  • ตัวเลือกต่าง ๆ เหล่านี้สามารถใช้เพื่อ
    • ปรับอคติจากความยาวเอกสาร
    • ปรับสมดุล relevance ในชุดเอกสารที่หลากหลาย
    • ปรับผลการจัดอันดับให้มีความสม่ำเสมอในการแสดงผล
  • การตั้งค่า 0 (ไม่ normalize) ให้กับชื่อเรื่อง และ 1 (log document length) ให้กับเนื้อหา ให้ผลลัพธ์ที่ดี
  • แต่ก็ควรทดลองหลายตัวเลือกเพื่อหาค่าที่เหมาะกับ use case ของตัวเองที่สุด

การ re-rank ด้วย cross-encoder

  • ระบบค้นหาหลายระบบทำงานเป็นสองขั้นตอน
  • กล่าวคือ ใช้ bi-encoder เพื่อดึงผลลัพธ์เริ่มต้น N รายการ แล้วใช้ cross-encoder เปรียบเทียบผลลัพธ์เหล่านี้กับคำค้นเพื่อจัดอันดับใหม่
    • bi-encoder: เร็ว จึงเหมาะกับการค้นหาจากเอกสารจำนวนมาก
    • cross-encoder
      • ช้ากว่าแต่ให้ประสิทธิภาพดีกว่า จึงเหมาะกับการจัดอันดับผลลัพธ์ที่ดึงมาแล้วใหม่
      • ประมวลผล query และ document ร่วมกัน ทำให้เข้าใจความสัมพันธ์ระหว่างทั้งสองได้ละเอียดกว่า
      • แลกกับเวลาในการคำนวณและความสามารถในการขยายระบบ เพื่อให้ได้ความแม่นยำในการจัดอันดับที่ดีกว่า
  • มีเครื่องมือหลายแบบสำหรับทำสิ่งนี้
  • หนึ่งในตัวเลือกที่ดีที่สุดคือ Cohere's Rerank
  • อีกวิธีคือ สร้างเองด้วย GPT ของ OpenAI
  • cross-encoder ช่วยเพิ่มความแม่นยำของผลการค้นหาได้ เพราะเข้าใจความสัมพันธ์ระหว่าง query กับ document ได้ดีกว่า
  • แต่เนื่องจากมีต้นทุนการคำนวณสูง จึงมีข้อจำกัดด้าน scalability
  • ดังนั้นแนวทางแบบสองขั้นตอนจึงได้ผลดี คือใช้ bi-encoder สำหรับการค้นหารอบแรก และใช้ cross-encoder เฉพาะกับเอกสารจำนวนน้อยที่ดึงมาแล้ว

เมื่อไรควรมองหาโซลูชันทางเลือก

  • PostgreSQL เป็นตัวเลือกที่เหมาะกับงานค้นหาหลายแบบ แต่ก็ไม่ได้ไร้ข้อจำกัด
  • การไม่มีอัลกอริทึมขั้นสูงอย่าง BM25 อาจเริ่มรู้สึกได้เมื่อต้องจัดการกับเอกสารที่มีความยาวหลากหลาย
  • full-text search ของ PostgreSQL พึ่งพา TF-IDF จึงอาจลำบากกับเอกสารที่ยาวมากและคำหายากในคอลเลกชันขนาดใหญ่
  • ก่อนจะมองหาโซลูชันทางเลือก ควรวัดผลก่อนเสมอ เพราะอาจไม่คุ้มที่จะเปลี่ยนก็ได้

บทสรุป

  • บทความนี้ครอบคลุมหลายเรื่อง ตั้งแต่ full-text search พื้นฐาน ไปจนถึงเทคนิคขั้นสูงอย่าง fuzzy matching, semantic search และการ boost ผลลัพธ์
  • สามารถใช้ความสามารถอันทรงพลังของ Postgres เพื่อสร้างเสิร์ชเอนจินที่ทั้งยืดหยุ่นและทรงพลังตามความต้องการเฉพาะได้
  • Postgres อาจไม่ใช่เครื่องมือแรกที่คนนึกถึงสำหรับงานค้นหา แต่จริง ๆ แล้วไปได้ไกลมาก
  • หัวใจสำคัญของประสบการณ์การค้นหาที่ดี
    • การทำซ้ำอย่างต่อเนื่องและการปรับจูนอย่างละเอียด
    • ควรใช้เทคนิคดีบักที่กล่าวถึงเพื่อทำความเข้าใจประสิทธิภาพการค้นหา และอย่ากลัวที่จะปรับน้ำหนักกับพารามิเตอร์ตามฟีดแบ็กและพฤติกรรมของผู้ใช้
  • แม้ PostgreSQL อาจขาดฟีเจอร์ค้นหาขั้นสูงบางอย่าง แต่ในกรณีส่วนใหญ่ก็ยังสร้างเสิร์ชเอนจินที่ทรงพลังได้เพียงพอ
  • ก่อนจะมองหาโซลูชันอื่น ควรลองใช้ความสามารถของ Postgres ให้เต็มที่และวัดประสิทธิภาพก่อน ถ้ายังไม่พอค่อยพิจารณาทางเลือกอื่น

2 ความคิดเห็น

 
eajrezz 2024-08-27

สงสัยเหมือนกันว่ารองรับการค้นหาภาษาเกาหลีได้ดีไหม

 
xguru 2024-08-26

หัวข้อ Weekly วันนี้ก็เป็นเรื่อง Postgres เหมือนกัน แล้วนี่ก็ยังเป็น Postgres อีกเหมือนกันนะครับ ดูท่าทางว่ายิ่งฮิตก็ยิ่งมีบทความออกมาเยอะจริง ๆ
เรื่อง BM25 ดูได้จากด้านล่าง

pg_bm25 - ส่วนขยายค้นหาแบบ Full-Text สำหรับ Postgres ที่ให้คุณภาพระดับ Elastic
ParadeDB - PostgreSQL for Search