"ฉันบอกว่าอยากได้หนังบีโว้ย!"
เบื่อกับการเลื่อนดู Netflix ไปเรื่อยๆ จนไม่รู้ว่าจะดูอะไรต่อดีไหม? จะเป็นอย่างไรหากคุณสามารถสร้างระบบแนะนำแบบกำหนดเองที่ขับเคลื่อนด้วย AI ที่สามารถทำนายภาพยนตร์เรื่องโปรดเรื่องต่อไปของคุณได้อย่างแม่นยำ
ในบทช่วยสอนนี้ เราจะแนะนำคุณเกี่ยวกับขั้นตอนการสร้าง ระบบแนะนำภาพยนตร์ โดยใช้ ฐานข้อมูลเวกเตอร์ (VectorDB) คุณจะได้เรียนรู้วิธีการทำงานของเครื่องมือแนะนำ AI สมัยใหม่ และได้สัมผัสประสบการณ์จริงในการสร้างระบบของคุณเองด้วย Superlinked
(ต้องการข้ามไปที่โค้ดโดยตรงหรือไม่ ดูรีโพอของเราบน GitHub ที่นี่ พร้อมที่จะลองใช้ระบบแนะนำสำหรับกรณีการใช้งานของคุณเองหรือยัง รับการสาธิตได้ ที่นี่ )
เราจะติดตาม บันทึก นี้ตลอดบทความนี้ คุณสามารถรันโค้ดได้โดยตรงจากเบราว์เซอร์ของคุณโดยใช้ Colab
อัลกอริทึมการแนะนำของ Netflix ทำงานได้ดีทีเดียวในการแนะนำเนื้อหาที่เกี่ยวข้อง เนื่องจากมีภาพยนตร์และรายการทีวีให้เลือกมากมาย (ประมาณ 16,000 เรื่องในปี 2023) และต้องเสนอรายการต่างๆ ให้กับผู้ใช้อย่างรวดเร็ว Netflix ทำได้อย่างไร? พูดง่ายๆ ก็คือ การค้นหาเชิงความหมาย
การค้นหาเชิงความหมายจะเข้าใจความหมายและบริบท (ทั้งคุณลักษณะและรูปแบบการบริโภค) เบื้องหลังคำค้นหาของผู้ใช้และคำอธิบายภาพยนตร์/รายการทีวี และจึงสามารถปรับแต่งคำค้นหาและคำแนะนำได้ดีกว่าแนวทางการค้นหาตามคำสำคัญแบบดั้งเดิม
อย่างไรก็ตาม การค้นหาตามความหมายนั้นมี ข้อท้าทาย บางประการ ซึ่งประการสำคัญคือ 1) การรับประกันผลลัพธ์การค้นหาที่แม่นยำ 2) ความสามารถในการตีความ และ 3) ความสามารถในการปรับขนาด ซึ่งเป็นข้อท้าทายที่กลยุทธ์การแนะนำเนื้อหาที่ประสบความสำเร็จจะต้องเผชิญ การใช้ไลบรารีของ Superlinked จะช่วยให้คุณ เอาชนะ ปัญหาเหล่านี้ได้
ในบทความนี้ เราจะแสดงวิธี ใช้ไลบรารี Superlinked เพื่อตั้งค่าการค้นหาเชิงความหมายของคุณเอง และ สร้างรายการภาพยนตร์ที่เกี่ยวข้อง ตามความต้องการของคุณ
การค้นหาเชิงความหมายนั้นให้คุณค่ามากมายในการค้นหาเวกเตอร์แต่ก็สร้างความท้าทายที่สำคัญสามประการในการฝังเวกเตอร์สำหรับนักพัฒนา:
ไลบรารี Superlinked ช่วยให้คุณจัดการกับความท้าทายเหล่านี้ได้ ด้านล่างนี้ เราจะสร้างตัวแนะนำเนื้อหา (โดยเฉพาะสำหรับภาพยนตร์) โดยเริ่มต้นด้วยข้อมูลที่เรามีเกี่ยวกับภาพยนตร์ที่กำหนด ฝังข้อมูลนี้เป็นเวกเตอร์มัลติโมดัล สร้างดัชนีเวกเตอร์ที่ค้นหาได้สำหรับภาพยนตร์ทั้งหมดของเรา จากนั้นใช้ค่าถ่วงน้ำหนักแบบสอบถามเพื่อปรับแต่งผลลัพธ์และไปสู่คำแนะนำภาพยนตร์ที่ดี มาเริ่มกันเลย
ด้านล่างนี้ คุณจะดำเนินการค้นหาความหมายบนชุดข้อมูลภาพยนตร์ Netflix โดยใช้องค์ประกอบต่อไปนี้ของไลบรารี Superlinked:
การแนะนำภาพยนตร์ให้ประสบความสำเร็จนั้นเป็นเรื่องยาก โดยเฉพาะอย่างยิ่งเนื่องจากมีตัวเลือกมากมาย (มากกว่า 9,000 เรื่องในปี 2023) และผู้ใช้ต้องการคำแนะนำตามความต้องการทันที ลองใช้ แนวทางที่ขับเคลื่อนด้วยข้อมูล เพื่อค้นหาสิ่งที่เราต้องการดู ใน ชุดข้อมูล ภาพยนตร์ของเรา เราทราบ:
เราสามารถฝังอินพุตเหล่านี้ และรวบรวมดัชนีเวกเตอร์ไว้ด้านบนของข้อมูลฝังของเรา เพื่อสร้างพื้นที่ที่เราสามารถค้นหาตามความหมายได้
เมื่อเรามีเวกเตอร์สเปซที่สร้างดัชนีแล้ว เราจะ:
ขั้นตอนแรกของคุณคือการติดตั้งไลบรารีและนำเข้าคลาสที่จำเป็น
(หมายเหตุ: ด้านล่างนี้ ให้เปลี่ยน alt.renderers.enable(“mimetype”)
เป็น alt.renderers.enable('colab')
หากคุณกำลังเรียกใช้ใน Google colab ให้คง “mimetype” ไว้หากคุณกำลังเรียกใช้ใน GitHub )
%pip install superlinked==5.3.0 from datetime import timedelta, datetime import altair as alt import os import pandas as pd from superlinked.evaluation.charts.recency_plotter import RecencyPlotter from superlinked.framework.common.dag.context import CONTEXT_COMMON, CONTEXT_COMMON_NOW from superlinked.framework.common.dag.period_time import PeriodTime from superlinked.framework.common.schema.schema import schema from superlinked.framework.common.schema.schema_object import String, Timestamp from superlinked.framework.common.schema.id_schema_object import IdField from superlinked.framework.common.parser.dataframe_parser import DataFrameParser from superlinked.framework.dsl.executor.in_memory.in_memory_executor import ( InMemoryExecutor, InMemoryApp, ) from superlinked.framework.dsl.index.index import Index from superlinked.framework.dsl.query.param import Param from superlinked.framework.dsl.query.query import Query from superlinked.framework.dsl.query.result import Result from superlinked.framework.dsl.source.in_memory_source import InMemorySource from superlinked.framework.dsl.space.text_similarity_space import TextSimilaritySpace from superlinked.framework.dsl.space.recency_space import RecencySpace alt.renderers.enable("mimetype") # NOTE: to render altair plots in colab, change 'mimetype' to 'colab' alt.data_transformers.disable_max_rows() pd.set_option("display.max_colwidth", 190)
นอกจากนี้ เรายังต้องเตรียมชุดข้อมูลด้วย กำหนดค่าคงที่ของเวลา ตั้งค่าตำแหน่ง URL ของข้อมูล สร้างพจนานุกรมที่จัดเก็บข้อมูล อ่าน CSV ใน Pandas DataFrame ทำความสะอาดข้อมูลใน DataFrame และข้อมูลเพื่อให้สามารถค้นหาได้อย่างถูกต้อง และทำการตรวจสอบและภาพรวมอย่างรวดเร็ว (ดูรายละเอียด ในเซลล์ 3 และ 4 )
ตอนนี้เมื่อเตรียมชุดข้อมูลเสร็จเรียบร้อยแล้ว คุณสามารถเพิ่มประสิทธิภาพการดึงข้อมูลโดยใช้ไลบรารี Superlinked ได้
ไลบรารีของ Superlinked ประกอบด้วยชุดส่วนประกอบพื้นฐานที่เราใช้ในการสร้างดัชนีและจัดการการเรียกค้น คุณสามารถอ่านรายละเอียดเพิ่มเติมเกี่ยวกับส่วนประกอบพื้นฐานเหล่านี้ ได้ที่นี่
ขั้นแรก คุณต้องกำหนด Schema ของคุณเพื่อแจ้งให้ระบบทราบเกี่ยวกับข้อมูลของคุณ
# accommodate our inputs in a typed schema @schema class MovieSchema: description: String title: String release_timestamp: Timestamp genres: String id: IdField movie = MovieSchema()
ขั้นต่อไป คุณใช้ Spaces เพื่อระบุว่าคุณต้องการจัดการกับข้อมูลแต่ละส่วนอย่างไรเมื่อทำการฝัง Spaces ที่ใช้จะขึ้นอยู่กับประเภทข้อมูลของคุณ แต่ละ Spaces ได้รับการปรับให้เหมาะสมเพื่อฝังข้อมูลเพื่อให้ได้ผลลัพธ์การดึงข้อมูลที่มีคุณภาพสูงสุด
ในคำจำกัดความของพื้นที่ เราอธิบายว่าควรฝังอินพุตอย่างไรเพื่อสะท้อนความสัมพันธ์ด้านความหมายในข้อมูลของเรา
# textual fields are embedded using a sentence-transformers model description_space = TextSimilaritySpace( text=movie.description, model="sentence-transformers/paraphrase-MiniLM-L3-v2" ) title_space = TextSimilaritySpace( text=movie.title, model="sentence-transformers/paraphrase-MiniLM-L3-v2" ) genre_space = TextSimilaritySpace( text=movie.genres, model="sentence-transformers/paraphrase-MiniLM-L3-v2" ) # release date are encoded using our recency space # periodtimes aim to reflect notable breaks in our scores recency_space = RecencySpace( timestamp=movie.release_timestamp, period_time_list=[ PeriodTime(timedelta(days=4 * YEAR_IN_DAYS)), PeriodTime(timedelta(days=10 * YEAR_IN_DAYS)), PeriodTime(timedelta(days=40 * YEAR_IN_DAYS)), ], negative_filter=-0.25, ) movie_index = Index(spaces=[description_space, title_space, genre_space, recency_space])
เมื่อคุณตั้งค่าพื้นที่และสร้างดัชนีแล้ว คุณจะใช้ส่วนซอร์สและตัวดำเนินการของไลบรารีเพื่อตั้งค่าแบบสอบถามของคุณ ดู เซลล์ 10-13 ในสมุดบันทึก
ตอนนี้เตรียมการสอบถามเรียบร้อยแล้ว เรามาเริ่มการรันสอบถามและเพิ่มประสิทธิภาพการดึงข้อมูลโดยการปรับน้ำหนักกัน
พื้นที่ความใหม่ช่วยให้คุณเปลี่ยนแปลงผลลัพธ์ของการค้นหาได้โดยดึงข้อมูลรุ่นเก่าหรือใหม่กว่าจากชุดข้อมูลของคุณ เราใช้ 4, 10 และ 40 ปีเป็นเวลาของช่วงเวลา เพื่อให้สามารถเน้นปีที่มีชื่อเรื่องมากขึ้นได้ (ดู เซลล์ 5 )
สังเกตการหยุดพักคะแนนที่ 4, 10 และ 40 ปี ชื่อเรื่องที่มีอายุมากกว่า 40 ปีจะได้รับคะแนน negative_filter
เรามากำหนดฟังก์ชันการใช้งานด่วนเพื่อแสดงผลลัพธ์ของเราในสมุดบันทึกกัน
def present_result( result: Result, cols_to_keep: list[str] = ["description", "title", "genres", "release_year", "id"], ) -> pd.DataFrame: # parse result to dataframe df: pd.DataFrame = result.to_pandas() # transform timestamp back to release year df["release_year"] = [ datetime.fromtimestamp(timestamp).year for timestamp in df["release_timestamp"] ] return df[cols_to_keep]
ไลบรารี Superlinked ช่วยให้คุณสามารถดำเนินการค้นหาได้หลากหลายประเภท โดยในที่นี้เราจะกำหนดไว้ 2 ประเภท ประเภทการค้นหาของเราทั้งสองประเภท (แบบง่ายและขั้นสูง) ช่วยให้ฉันสามารถชั่งน้ำหนักช่องว่างแต่ละช่อง (คำอธิบาย ชื่อเรื่อง ประเภท และแน่นอน ความใหม่) ตามความต้องการของฉัน ความแตกต่างระหว่างทั้งสองประเภท คือ เมื่อใช้ การค้นหาแบบง่าย ฉันจะตั้งค่าข้อความค้นหาหนึ่งข้อความ จากนั้นจึงแสดงผลลัพธ์ที่คล้ายคลึงกันในช่องคำอธิบาย ชื่อเรื่อง และประเภท
ด้วย แบบสอบถามขั้นสูง ฉันสามารถควบคุมได้ละเอียดมากขึ้น หากต้องการ ฉันสามารถป้อนข้อความแบบสอบถามที่แตกต่างกันในแต่ละช่องคำอธิบาย ชื่อเรื่อง และประเภท นี่คือรหัสแบบสอบถาม:
query_text_param = Param("query_text") simple_query = ( Query( movie_index, weights={ description_space: Param("description_weight"), title_space: Param("title_weight"), genre_space: Param("genre_weight"), recency_space: Param("recency_weight"), }, ) .find(movie) .similar(description_space.text, query_text_param) .similar(title_space.text, query_text_param) .similar(genre_space.text, query_text_param) .limit(Param("limit")) ) advanced_query = ( Query( movie_index, weights={ description_space: Param("description_weight"), title_space: Param("title_weight"), genre_space: Param("genre_weight"), recency_space: Param("recency_weight"), }, ) .find(movie) .similar(description_space.text, Param("description_query_text")) .similar(title_space.text, Param("title_query_text")) .similar(genre_space.text, Param("genre_query_text")) .limit(Param("limit")) )
ในการสอบถามแบบง่าย ฉันตั้งค่าข้อความสอบถามและกำหนดน้ำหนักที่แตกต่างกันขึ้นอยู่กับความสำคัญที่มีต่อฉัน
result: Result = app.query( simple_query, query_text="Heartfelt romantic comedy", description_weight=1, title_weight=1, genre_weight=1, recency_weight=0, limit=TOP_N, ) present_result(result)
ผลลัพธ์ของเราประกอบด้วยชื่อเรื่องบางเรื่องที่ฉันเคยดูไปแล้ว ฉันสามารถจัดการกับปัญหานี้ได้โดยการถ่วงน้ำหนักความใหม่เพื่อให้ผลลัพธ์ของฉันเอนเอียงไปทางชื่อเรื่องล่าสุด น้ำหนักจะถูกทำให้เป็นมาตรฐานเพื่อให้มีผลรวมเป็นหน่วย (กล่าวคือ น้ำหนักทั้งหมดได้รับการปรับแล้วเพื่อให้ผลรวมทั้งหมดเท่ากับ 1 เสมอ) ดังนั้นคุณจึงไม่ต้องกังวลว่าจะตั้งค่าน้ำหนักอย่างไร
result: Result = app.query( simple_query, query_text="Heartfelt romantic comedy", description_weight=1, title_weight=1, genre_weight=1, recency_weight=3, limit=TOP_N, ) present_result(result)
ผลลัพธ์ของฉัน (ด้านบน) ทั้งหมดเป็นผลงานหลังปี 2021
ด้วยการใช้แบบสอบถามง่ายๆ ฉันสามารถกำหนดน้ำหนักให้กับช่องว่างเฉพาะใดๆ (คำอธิบาย ชื่อเรื่อง ประเภท หรือความใหม่) เพื่อให้มีความสำคัญมากขึ้นเมื่อส่งคืนผลลัพธ์ มาลองทำกันดู ด้านล่างนี้ เราจะกำหนดน้ำหนักให้กับประเภทมากขึ้นและลดน้ำหนักให้กับชื่อเรื่องลง ข้อความแบบสอบถามของฉันเป็นเพียงประเภทที่มีบริบทเพิ่มเติม ฉันยังคงรักษาความใหม่ไว้เช่นเดิมเพราะฉันยังต้องการให้ผลลัพธ์ของฉันเอนเอียงไปทางภาพยนตร์ล่าสุด
result = app.query( simple_query, query_text="Heartfelt romantic comedy", description_weight=1, title_weight=0.1, genre_weight=2, recency_weight=1, limit=TOP_N, ) present_result(result)
แบบสอบถามนี้เลื่อนปีที่วางจำหน่ายกลับไปเล็กน้อยเพื่อให้ฉันได้ผลลัพธ์ที่ถ่วงน้ำหนักตามประเภทมากขึ้น (ด้านล่าง)
แบบสอบถามขั้นสูงช่วยให้ฉันควบคุมรายละเอียดได้ละเอียดยิ่งขึ้น ฉันยังคงควบคุมความใหม่ได้ แต่ยังสามารถระบุข้อความค้นหาสำหรับคำอธิบาย ชื่อเรื่อง และประเภท และกำหนดน้ำหนักเฉพาะให้กับแต่ละข้อความตามความต้องการของฉันได้ ดังด้านล่าง (และ เซลล์ 19-21 )
result = app.query( advanced_query, description_query_text="Heartfelt lovely romantic comedy for a cold autumn evening.", title_query_text="love", genre_query_text="drama comedy romantic", description_weight=0.2, title_weight=3, genre_weight=1, recency_weight=5, limit=TOP_N, ) present_result(result)
สมมติว่าในผลลัพธ์ภาพยนตร์เรื่องล่าสุดของฉัน ฉันพบภาพยนตร์เรื่องหนึ่งที่ฉันเคยดูไปแล้วและต้องการดูเรื่องที่คล้ายกัน สมมติว่าฉันชอบ White Christmas ซึ่งเป็นภาพยนตร์ตลกโรแมนติกปี 1954 (id = tm16479) เกี่ยวกับนักร้องและนักเต้นที่มารวมตัวกันเพื่อแสดงบนเวทีเพื่อดึงดูดแขกให้มาพักที่โรงแรมแห่งหนึ่งในเวอร์มอนต์ที่กำลังประสบปัญหา การเพิ่มประโยค with_vector
พิเศษ (พร้อมพารามิเตอร์ movie_id
) ลงใน advanced_query ช่วยให้ with_movie_query ช่วยให้ฉันค้นหาโดยใช้ภาพยนตร์เรื่องนี้ (หรือภาพยนตร์เรื่องใดก็ได้ที่ฉันชอบ) และช่วยให้ฉันควบคุมข้อความค้นหาย่อยและการถ่วงน้ำหนักแยกจากกันได้อย่างละเอียด
ก่อนอื่น เราเพิ่มพารามิเตอร์ movie_id ของเรา:
with_movie_query = advanced_query.with_vector(movie, Param("movie_id"))
จากนั้นฉันสามารถตั้งค่าแบบสอบถามย่อยอื่นๆ ให้เป็นค่าว่างหรืออะไรก็ได้ที่เกี่ยวข้องที่สุด พร้อมกับการถ่วงน้ำหนักใดๆ ที่สมเหตุสมผล สมมติว่าแบบสอบถามแรกของฉันส่งคืนผลลัพธ์ที่สะท้อนถึงการแสดงบนเวที/วงดนตรีใน White Christmas (ดู เซลล์ 24 ) แต่ฉันต้องการชมภาพยนตร์ที่เน้นครอบครัวมากกว่า ฉันสามารถป้อน description_query_text เพื่อเบี่ยงเบนผลลัพธ์ไปในทิศทางที่ต้องการ
result = app.query( with_movie_query, description_query_text="family", title_query_text="", genre_query_text="", description_weight=1, title_weight=0, genre_weight=0, recency_weight=0, description_query_weight=1, movie_id="tm16479", limit=TOP_N, ) present_result(result)
แต่ตอนนี้เมื่อเห็นผลลัพธ์แล้ว ฉันก็รู้ว่าจริงๆ แล้วฉันอยากได้อะไรเบาๆ และตลกๆ มากกว่า มาปรับแบบสอบถามของฉันให้เหมาะสมกันดีกว่า:
Result = app.query( with_movie_query, description_query_text="", title_query_text="", genre_query_text="comedy", description_weight=1, title_weight=0, genre_weight=2, recency_weight=0, description_query_weight=1, movie_id="tm16479", limit=TOP_N, ) present_result(result)
โอเค ผลลัพธ์ดีขึ้นแล้ว ฉันจะเลือกอันหนึ่ง เอาป๊อปคอร์นมาเลย!
Superlinked ช่วยให้คุณทดสอบ ทำซ้ำ และปรับปรุงคุณภาพการค้นหาได้อย่างง่ายดาย ด้านบน เราได้แนะนำวิธีใช้ไลบรารี Superlinked เพื่อค้นหาความหมายบนเวกเตอร์สเปซ เช่นเดียวกับที่ Netflix ทำ และส่งคืนผลลัพธ์ภาพยนตร์ที่เกี่ยวข้องและแม่นยำ นอกจากนี้ เรายังได้เห็นวิธีการปรับแต่งผลลัพธ์ ปรับแต่งน้ำหนักและเงื่อนไขการค้นหาจนกว่าจะได้ผลลัพธ์ที่ต้องการ
ตอนนี้ลองใช้ สมุดบันทึก ด้วยตัวคุณเองแล้วดูว่าคุณจะทำอะไรได้บ้าง!
เครื่องมือแนะนำกำลังกำหนดรูปแบบการค้นหาเนื้อหา ไม่ว่าจะเป็นภาพยนตร์ เพลง หรือผลิตภัณฑ์ การค้นหาเวกเตอร์คืออนาคต และตอนนี้คุณมีเครื่องมือสำหรับสร้างเครื่องมือของคุณเองแล้ว
ผู้แต่ง : มอร์ คาปรอนไซ