”მე ვთქვი, რომ მინდა B-Movie ჯანდაბა!”
დაიღალეთ Netflix-ის უსასრულოდ სქროლვით, არ იცით რა უყუროთ შემდეგ? რა მოხდება, თუ თქვენ შეგეძლოთ შექმნათ თქვენი საკუთარი მორგებული, ხელოვნური ინტელექტის დაფუძნებული სარეკომენდაციო სისტემა, რომელიც სიზუსტით წინასწარმეტყველებს თქვენს მომავალ საყვარელ ფილმს?
ამ გაკვეთილში ჩვენ გაგიძღვებით ფილმის რეკომენდაციის სისტემის შექმნის პროცესს ვექტორული მონაცემთა ბაზების (VectorDBs) გამოყენებით. თქვენ შეიტყობთ, თუ როგორ მუშაობს თანამედროვე ხელოვნური ინტელექტის სარეკომენდაციო ძრავები და მიიღებთ პრაქტიკულ გამოცდილებას Superlinked- ით საკუთარი სისტემის შექმნისას.
(გსურთ პირდაპირ კოდზე გადასვლა? იხილეთ ჩვენი რეპო GitHub-ზე აქ . მზად ხართ სცადოთ სარეკომენდაციო სისტემები თქვენივე გამოყენების შემთხვევაში? მიიღეთ დემო აქ .)
ჩვენ მივყვებით ამ რვეულს მთელი სტატიის განმავლობაში. თქვენ ასევე შეგიძლიათ გაუშვათ კოდი პირდაპირ თქვენი ბრაუზერიდან Colab-ის გამოყენებით.
Netflix-ის სარეკომენდაციო ალგორითმი საკმაოდ კარგ საქმეს აკეთებს შესაბამისი შინაარსის შეთავაზებაში - იმის გათვალისწინებით, რომ პარამეტრების დიდი მოცულობა (~ 16 ათასი ფილმი და სატელევიზიო პროგრამები 2023 წელს) და რამდენად სწრაფად უნდა შესთავაზოს მას შოუები მომხმარებლებს. როგორ აკეთებს ამას Netflix? ერთი სიტყვით, სემანტიკური ძიება .
სემანტიკური ძიება აცნობიერებს მნიშვნელობას და კონტექსტს (როგორც ატრიბუტებს, ასევე მოხმარების შაბლონებს) მომხმარებლის მოთხოვნებისა და ფილმების/სატელევიზიო შოუს აღწერილობების მიღმა და, შესაბამისად, შეუძლია უზრუნველყოს უკეთესი პერსონალიზება თავის შეკითხვებში და რეკომენდაციებში, ვიდრე ტრადიციული საკვანძო სიტყვებზე დაფუძნებული მიდგომები.
მაგრამ სემანტიკური ძიება აჩენს გარკვეულ გამოწვევებს - მათ შორის უპირველეს ყოვლისა: 1) ზუსტი ძიების შედეგების უზრუნველყოფა, 2) ინტერპრეტაცია და 3) მასშტაბურობა - გამოწვევები ნებისმიერი წარმატებული შინაარსის რეკომენდაციის სტრატეგიის წინაშე. Superlinked-ის ბიბლიოთეკის გამოყენებით შეგიძლიათ გადალახოთ ეს სირთულეები.
ამ სტატიაში ჩვენ გაჩვენებთ, თუ როგორ გამოიყენოთ Superlinked ბიბლიოთეკა საკუთარი სემანტიკური ძიების დასაყენებლად და შესაბამისი ფილმების სიის შესაქმნელად თქვენი პრეფერენციების მიხედვით.
სემანტიკური ძიება დიდ მნიშვნელობას ანიჭებს ვექტორულ ძიებას, მაგრამ დეველოპერებს უქმნის ვექტორის ჩაშენების სამ მნიშვნელოვან გამოწვევას:
Superlinked ბიბლიოთეკა გაძლევთ საშუალებას გაუმკლავდეთ ამ გამოწვევებს. ქვემოთ, ჩვენ ავაშენებთ კონტენტის რეკომენდატორს (სპეციალურად ფილმებისთვის), დაწყებული მოცემული ფილმის შესახებ არსებული ინფორმაციით, ჩავნერგავთ ამ ინფორმაციას მულტიმოდალურ ვექტორად, შევქმნით საძიებო ვექტორულ ინდექსს ჩვენი ყველა ფილმისთვის და შემდეგ გამოვიყენებთ შეკითხვის წონებს ჩვენი შედეგების შესაცვლელად და კარგი ფილმის რეკომენდაციების მისაღებად. მოდი შევიდეთ.
ქვემოთ, თქვენ შეასრულებთ სემანტიკურ ძიებას Netflix-ის ფილმების მონაცემთა ბაზაში Superlinked ბიბლიოთეკის შემდეგი ელემენტების გამოყენებით:
ფილმების წარმატებით რეკომენდაცია რთულია ძირითადად იმიტომ, რომ ამდენი ვარიანტია (>9000 სათაური 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 პანდას DataFrame-ში, გავასუფთავოთ მონაცემთა ჩარჩო და მონაცემები, რათა მოხდეს მისი სწორად ძებნა, და გავაკეთოთ სწრაფი გადამოწმება და მიმოხილვა. (დეტალებისთვის იხილეთ უჯრედები 3 და 4. )
ახლა, როდესაც მონაცემთა ნაკრები მომზადებულია, შეგიძლიათ ოპტიმიზაცია მოახდინოთ თქვენი მოძიება Superlinked ბიბლიოთეკის გამოყენებით.
Superlinked-ის ბიბლიოთეკა შეიცავს ძირითადი სამშენებლო ბლოკების ერთობლიობას, რომელსაც ვიყენებთ ინდექსის ასაგებად და მოძიების სამართავად. ამ სამშენებლო ბლოკების შესახებ უფრო დეტალურად შეგიძლიათ წაიკითხოთ აქ .
პირველ რიგში, თქვენ უნდა განსაზღვროთ თქვენი სქემა, რათა სისტემას უთხრათ თქვენი მონაცემების შესახებ.
# accommodate our inputs in a typed schema @schema class MovieSchema: description: String title: String release_timestamp: Timestamp genres: String id: IdField movie = MovieSchema()
შემდეგ, თქვენ იყენებთ 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 ბიბლიოთეკა გაძლევთ საშუალებას შეასრულოთ სხვადასხვა სახის მოთხოვნები; აქ განვსაზღვრავთ ორს. ჩვენი შეკითხვის ორივე ტიპის მოთხოვნა (მარტივი და გაფართოებული) ნება მომეცით ავწონო ინდივიდუალური სივრცეები (აღწერილობა, სათაური, ჟანრი და, რა თქმა უნდა, უახლესი) ჩემი პრეფერენციების მიხედვით. მათ შორის განსხვავება ისაა, რომ მარტივი შეკითხვით ვაყენებ ერთი შეკითხვის ტექსტს და შემდეგ გამოვყოფ მსგავს შედეგებს აღწერილობაში, სათაურში და ჟანრის სივრცეებში.
გაფართოებული შეკითხვით , მე უფრო წვრილმარცვლოვანი კონტროლი მაქვს. თუ მსურს, შემიძლია შევიტანო მოთხოვნის სხვადასხვა ტექსტი აღწერილობის, სათაურის და ჟანრის თითოეულ სივრცეში. აქ არის შეკითხვის კოდი:
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)
თქვით ჩემი ბოლო ფილმის შედეგებში, ვიპოვე ფილმი, რომელიც უკვე ნანახი მაქვს და მინდა მსგავსი რამის ნახვა. დავუშვათ, მომწონს თეთრი შობა, 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"))
და შემდეგ შემიძლია დავაყენო ჩემი სხვა ქვეძიების მოთხოვნები ცარიელი ან რაც ყველაზე აქტუალურია, ნებისმიერ წონით, რაც აზრი აქვს. ვთქვათ, ჩემი პირველი შეკითხვა აბრუნებს შედეგებს, რომლებიც ასახავს თეთრი შობის სცენურ შესრულებას/ჯგუფის ასპექტს (იხ. უჯრედი 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 ბიბლიოთეკა ვექტორულ სივრცეში სემანტიკური ძიების გასაკეთებლად, როგორც ამას აკეთებს Netflix და დააბრუნოთ ზუსტი, შესაბამისი ფილმის შედეგები. ჩვენ ასევე ვნახეთ, თუ როგორ უნდა დაარეგულიროთ ჩვენი შედეგები, შევცვალოთ წონა და საძიებო ტერმინები, სანამ არ მივიღებთ სწორ შედეგს.
ახლა, თავად სცადე ნოუთბუქი და ნახე, რისი მიღწევა შეგიძლია!
რეკომენდაციების ძრავები აყალიბებენ იმას, თუ როგორ აღმოვაჩენთ შინაარსს. იქნება ეს ფილმები, მუსიკა თუ პროდუქტები, ვექტორული ძიება არის მომავალი — და ახლა თქვენ გაქვთ ინსტრუმენტები საკუთარი თავის შესაქმნელად.
ავტორი: Mór Kapronczay