Tech Blog

อย่าปล่อยให้หน้ารายละเอียดผลงานเป็น "ทางตัน" — ส่งผู้ชมไปยังเรื่องถัดไปด้วย "สำหรับคนที่ชอบเรื่องนี้"

レコメンド UX embedding 回遊 設計

บทนำ

ในแพลตฟอร์มวิดีโอและเพลงที่สร้างเอง ผมเพิ่มรายการผลงานที่คล้ายกัน “สำหรับคนที่ชอบเรื่องนี้” ลงในหน้ารายละเอียดของผลงาน เป็นฟีเจอร์ที่เรียงผลงานที่ใกล้เคียงกับเรื่องที่กำลังดูอยู่ ไว้ด้านล่างของรายละเอียด

แก่นทางเทคนิคคือแค่การนำดัชนีที่สร้างไว้สำหรับการค้นหาเชิงความหมายมาใช้ซ้ำเท่านั้น เรื่องการทำ semantic search โดยไม่เพิ่มฐานข้อมูลเวกเตอร์เฉพาะทาง อยู่ใน ค้นด้วย “ความหมาย” ไม่ใช่ “ตัวอักษร” บทความนี้ไม่ใช่เรื่องนั้น แต่เป็นเรื่องการออกแบบว่า ทำให้ดัชนีนั้นทำงานเพื่อ “การเวียนชมในหน้ารายละเอียด” ได้อย่างไร และเพิ่มเข้าไปโดยไม่ทำลายเนื้อหาหลักได้อย่างไร

ส่วน "สำหรับคนที่ชอบเรื่องนี้" ด้านล่างของหน้ารายละเอียดผลงาน เรียงผลงานที่คล้ายกันพร้อมค่าความคล้าย ส่งผู้ชมไปยังหน้ารายละเอียดถัดไป (เพื่อวัตถุประสงค์พอร์ตโฟลิโอ ผลงานเป็นข้อมูลตัวอย่าง)


หน้ารายละเอียดผลงานกลายเป็นทางตันได้ง่าย

ในประสบการณ์การเวียนชมแคตตาล็อก ผู้ใช้จะเข้าจากรายการไปยังหน้ารายละเอียด แล้วตัดสินใจที่นั่นว่า “จะเช่าหรือไม่เช่า” ปัญหาคือ ตอนที่คิดว่า “อันนี้ไม่ใช่แฮะ” ถ้าหน้ารายละเอียดไม่มีเบาะแสถัดไป ผู้ใช้ก็จะกลับไปที่รายการ หรือไม่ก็ออกจากแอปไปเลย

ถ้าปล่อยไว้เฉยๆ หน้ารายละเอียดจะ กลายเป็นทางตัน คนที่เพิ่งสนใจผลงานหนึ่งเรื่องจนคลิกเข้ามาดู จะถูกส่งกลับไปโดยไม่ได้เจอตัวเลือกถัดไปเลย

【ทางตัน】รายการ ─▶ รายละเอียด ─(ไม่โดนใจ)─▶ กลับ/ออกจากแอป   … ไม่ได้เจอเรื่องถัดไป

【การเวียนชม】รายการ ─▶ รายละเอียด ─(ไม่โดนใจ)─▶ ผลงานที่คล้ายกัน ─▶ รายละเอียดถัดไป ─▶ …
                                              └───────── การเวียนชมดำเนินต่อไป

สิ่งที่อยากทำคือ วางคำถามที่ว่า “แล้วอันนี้ล่ะ?” ไว้ที่ด้านล่างสุดของรายละเอียดเสมอ ยื่นเรื่องถัดไปไว้ตรงหน้าผู้ใช้


ดึงดัชนีเดียวกันมาใช้ในบทบาท “ผลงานที่คล้ายกับเรื่องนี้”

ตรงนี้เองที่ดัชนีซึ่งสร้างไว้สำหรับการค้นหาเชิงความหมายมีประโยชน์โดยตรง ผลงานทั้งหมดถูกแปลงเป็น เวกเตอร์ความหมาย ไว้แล้ว (เนื้อหาของผลงานถูกแปลงเป็นชุดตัวเลข เนื้อหาที่ใกล้เคียงกันก็จะมีเวกเตอร์ใกล้กันด้วย) การค้นหาเชิงความหมายคืนผลงานที่ใกล้กับ “เวกเตอร์ของประโยคคิวรี” สำหรับผลงานที่คล้ายกัน ต่างกันแค่ตรงที่คิวรีไม่ใช่ประโยค แต่คือ “ผลงานที่กำลังดูอยู่ตอนนี้” เอง สอบถามดัชนีด้วยเวกเตอร์ของผลงานปัจจุบัน แล้วคืนผลตามลำดับความใกล้ ต่างกันแค่ทางเข้า แต่กลไกที่ทำงานคือฟังก์ชันเดียวกับการค้นหาเชิงความหมาย

และแทนที่จะแค่เรียงลำดับ ผมยังใส่ ค่าความคล้าย (%) ไว้กับแต่ละผลงานด้วย เพื่อแสดงให้เห็นเป็นตัวเลขว่า “ทำไมถึงแนะนำเรื่องนี้” ถ้าคำแนะนำเป็นกล่องดำ จะทำให้เกิดความไม่ไว้ใจว่า “ทำไมถึงเป็นเรื่องนี้” แต่ถ้าแสดงความใกล้ให้เห็น ผู้ใช้ก็จะยอมรับได้และไปต่อ

// คืนผลงานที่ใกล้กับเวกเตอร์ของผลงานฐาน เรียงตามลำดับความใกล้ (สำหรับคนที่ชอบเรื่องนี้)
List<Map.Entry<Integer, Double>> ranked = index.topSimilar(baseVector, limit * 4 + 1);
for (var entry : ranked) {
    if (entry.getKey() == filmId) continue;          // ไม่รวมตัวเอง
    var film = filmMapper.selectById(entry.getKey());
    if (film == null) continue;
    if (baseIsCd != (film.artist() != null)) continue; // เฉพาะประเภทสื่อเดียวกันเท่านั้น (รายละเอียดด้านล่าง)
    items.add(Map.of("film", film, "score", round(entry.getValue()))); // พร้อมค่าความคล้าย
}

การค้นหาเชิงความหมาย คำแนะนำ และฟีเจอร์นี้ คือ สามใบหน้าของดัชนีเดียวกัน เรื่องการดึงดัชนีเดียวกันด้วย “เซนทรอยด์ของประวัติ” เพื่อให้ได้ “แนะนำสำหรับคุณ” อยู่ใน สร้างคำแนะนำจาก “เวกเตอร์เซนทรอยด์” ของประวัติการรับชม


คำแนะนำต้องไม่ทำลายเนื้อหาหลัก

ตรงนี้คือจุดที่ระวังที่สุดสำหรับฟีเจอร์นี้ คำแนะนำเป็นแค่ข้อมูลเสริม ไม่ใช่ตัวเอกของหน้ารายละเอียด

การคำนวณผลงานที่คล้ายกันอาจล้มเหลวได้ ถ้าเวกเตอร์ของผลงานยังไม่ถูกสร้างขึ้น ผลลัพธ์ก็จะเป็น 0 รายการ หรือการเรียกใช้ภายในอาจคืนค่า error ก็ได้ ตอนนี้เอง การที่ความล้มเหลวของคำแนะนำลากหน้ารายละเอียดหลักพังไปด้วย คือสิ่งที่แย่ที่สุด ผู้ใช้มาดูผลงาน ไม่ได้มาดูคำแนะนำ

จึงทำให้ส่วนคำแนะนำเป็น “มีก็ดี ไม่มีก็ไม่เป็นไร” อย่างเคร่งครัด เมื่อได้ 0 รายการหรือเกิด error ทั้งส่วนนั้นจะหายไปเงียบๆ หน้ารายละเอียดหลัก (เรื่องย่อ ข้อมูลการเช่า ปุ่มจอง) จะยังคงอยู่ครบไม่ว่าคำแนะนำจะแสดงหรือไม่ก็ตาม

หน้ารายละเอียดหลัก (เรื่องย่อ・ข้อมูลการเช่า・การจอง)  ← แสดงเสมอ ไม่ขึ้นกับความสำเร็จของคำแนะนำ
        └─ สำหรับคนที่ชอบเรื่องนี้ (ข้อมูลเสริม)
              ├─ มีผลลัพธ์ ─▶ แสดง
              ├─ 0 รายการ (เวกเตอร์ยังไม่ถูกสร้าง) ─▶ ซ่อนทั้งส่วน
              └─ API ล้มเหลว ─▶ ซ่อนทั้งส่วน (ไม่ทำลายเนื้อหาหลัก)

อีกอย่างหนึ่งที่ทำคือ ให้แสดง เฉพาะประเภทสื่อเดียวกัน เท่านั้น (วิดีโอในรายละเอียดวิดีโอ ซีดีในรายละเอียดซีดี) ถ้าดึงจากแค่ความใกล้เชิงความหมายอย่างเดียว ขณะที่กำลังดูวิดีโออยู่ ซีดีเพลงก็อาจปนเข้ามาในฐานะ “คล้ายกัน” ได้ กับดักของความไร้เดียงสาของ embedding ที่ว่า “ใกล้เชิงความหมาย ไม่ได้แปลว่า แสดงได้” นี้ อยู่ใน การค้นหาด้วยเวกเตอร์ปนสิ่งที่ “ใกล้เชิงความหมาย” เข้ามาโดยไม่ได้ตั้งใจ เพราะเป็นข้อมูลเสริม จึงต้องไม่แสดงสิ่งที่ไม่จำเป็นจนทำให้หน้ารายละเอียดดูสับสน


คำแนะนำไม่ใช่ตัวเอก แต่คือสะพานไปสู่เรื่องถัดไป

สิ่งที่รู้สึกเข้าใจได้อย่างแท้จริงหลังจากสร้างฟีเจอร์นี้ คือคุณค่าของมันไม่ได้อยู่ที่ “คำแนะนำที่ฉลาด” ในตัวมันเอง คุณค่าอยู่ที่การ สร้างสะพานหนึ่งเส้นไปสู่เรื่องถัดไป ให้กับหน้ารายละเอียดที่เคยเป็นทางตัน

  • การเวียนชมเกิดขึ้นจาก ท่าเดียวที่ว่า “วางเรื่องถัดไปไว้” มากกว่าอัลกอริทึมที่ฉลาด การมีตัวเลือกอยู่ตรงหน้าหรือไม่ คือสิ่งที่ตัดสินว่าจะกลับหรือจะไปต่อ
  • และสะพานนี้ ต่อให้ข้ามไม่ได้ก็ไม่ทำลายเนื้อหาหลัก ไม่ว่าคำแนะนำจะได้ 0 รายการหรือล้มเหลว หน้ารายละเอียดก็ยังคงอยู่เหมือนเดิม ข้อมูลเสริมจะถูกเพิ่มเข้าไปเฉพาะในขอบเขตที่ไม่รบกวนตัวเอกเท่านั้น

ดัชนีเดียวกันถูกนำไปใช้ซ้ำทั้งในการค้นหา คำแนะนำ และการเวียนชมนี้ แต่สิ่งที่ต่างกันไปในแต่ละฟีเจอร์ไม่ใช่อัลกอริทึม แต่คือ วิธีการวาง การค้นหาถูกดันออกมาข้างหน้าในฐานะตัวเอก ส่วนคำแนะนำนี้เป็นบทสมทบที่ไม่รบกวนหน้ารายละเอียด เครื่องมือเดียวกัน แต่การออกแบบเปลี่ยนไปตามว่าเป็นตัวเอกหรือบทสมทบ — นั่นคือสิ่งที่ได้เรียนรู้จากฟีเจอร์นี้


บทความที่เกี่ยวข้อง

ส่งข้อความได้ตามสบาย

ไม่ว่าจะเป็นการว่าจ้างงาน แนะนำโปรเจกต์ ความคิดเห็น หรือคำถาม ยินดีรับทั้งหมด ผมหวังเป็นอย่างยิ่งว่าจะได้เชื่อมต่อกับผู้ที่มีอุดมการณ์อันสูงส่งเช่นเดียวกัน ผมจะมุ่งมั่นท้าทายในสิ่งที่ทุ่มเททั้งชีวิตต่อไป ขอขอบคุณและฝากเนื้อฝากตัวด้วยครับ