"Sanoin, että haluan B-Movie-elokuvan!"
Oletko kyllästynyt Netflixin loputtomaan selaamiseen, etkä ole varma mitä katsoisi seuraavaksi? Mitä jos voisit rakentaa oman mukautetun tekoälypohjaisen suositusjärjestelmän, joka ennustaa seuraavan suosikkielokuvasi tarkasti?
Tässä opetusohjelmassa opastamme sinua elokuvasuositusjärjestelmän luomisessa käyttämällä vektoritietokantoja (VectorDBs) . Opit kuinka modernit tekoälysuositusmoottorit toimivat ja saat käytännön kokemusta oman järjestelmäsi rakentamisesta Superlinkedin avulla.
(Haluatko siirtyä suoraan koodiin? Tutustu GitHubin repoon täällä . Oletko valmis kokeilemaan suosittelujärjestelmiä omaan käyttötapaasi varten? Hanki demo tästä .)
Seuraamme tätä muistikirjaa koko artikkelin ajan. Voit myös suorittaa koodin suoraan selaimesta Colabin avulla.
Netflixin suositusalgoritmi tekee melko hyvää työtä ehdottaessaan asiaankuuluvaa sisältöä - ottaen huomioon valinnanvaran valtava määrä (noin 16 000 elokuvaa ja TV-ohjelmaa vuonna 2023) ja kuinka nopeasti sen on ehdotettava ohjelmia käyttäjille. Miten Netflix tekee sen? Sanalla sanoen semanttinen haku .
Semanttinen haku ymmärtää käyttäjien kyselyjen ja elokuvien/TV-ohjelmien kuvausten takana olevan merkityksen ja kontekstin (sekä attribuutit että kulutustottumukset), ja voi siksi tarjota kyselyitään ja suosituksiinsa paremmin personoinnin kuin perinteiset avainsanapohjaiset lähestymistavat.
Mutta semanttinen haku asettaa tiettyjä haasteita - ennen kaikkea: 1) tarkkojen hakutulosten varmistaminen, 2) tulkittavuus ja 3) skaalautuvuus - haasteita, joihin jokaisen onnistuneen sisällön suositusstrategian on vastattava. Superlinkedin kirjaston avulla voit voittaa nämä vaikeudet.
Tässä artikkelissa näytämme, kuinka Superlinked-kirjaston avulla voit määrittää oman semanttisen haun ja luoda luettelon oleellisista elokuvista mieltymystesi perusteella.
Semanttinen haku välittää paljon arvoa vektorihaussa, mutta asettaa kehittäjille kolme merkittävää vektorin upottamisen haastetta:
Superlinked-kirjaston avulla voit vastata näihin haasteisiin. Alla rakennamme sisältösuosittelijan (erityisesti elokuvia varten) alkaen tiedoista, joita meillä on tietystä elokuvasta, upotamme nämä tiedot multimodaalisena vektorina, rakennamme haettavan vektoriindeksin kaikille elokuvillemme ja käytämme sitten kyselyn painotuksia tulosten säätämiseen ja hyvien elokuvasuositusten saamiseksi. Mennään asiaan.
Alla teet semanttisen haun Netflix-elokuvatietojoukosta käyttämällä seuraavia Superlinked-kirjaston elementtejä:
Elokuvien suosittelu on vaikeaa lähinnä siksi, että vaihtoehtoja on niin paljon (> 9000 nimikettä vuonna 2023), ja käyttäjät haluavat suosituksia heti pyynnöstä. Otetaan tietolähtöinen lähestymistapa löytääksemme jotain, jota haluamme katsoa. Elokuvistamme tiedämme:
Voimme upottaa nämä syötteet ja koota vektoriindeksin upotusten päälle, jolloin voimme luoda tilan, josta voimme etsiä semanttisesti.
Kun meillä on indeksoitu vektoriavaruutemme, teemme:
Ensimmäinen askel on asentaa kirjasto ja tuoda tarvittavat luokat.
(Huomaa: muuta alla alt.renderers.enable(“mimetype”)
muotoon alt.renderers.enable('colab')
jos käytät tätä google colabissa . Säilytä "mimetype", jos suoritat githubissa .)
%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)
Meidän on myös valmisteltava tietojoukko - määritettävä aikavakiot, asetettava datan URL-sijainti, luotava tietovaraston sanakirja, luettava CSV-tiedosto pandas DataFrame -kehykseen, puhdistettava datakehys ja tiedot, jotta niitä voidaan etsiä oikein, sekä tehtävä nopea tarkistus ja yleiskatsaus. (Katso lisätietoja soluista 3 ja 4. )
Nyt kun tietojoukko on valmis, voit optimoida haun käyttämällä Superlinked-kirjastoa.
Superlinkedin kirjasto sisältää joukon ydinrakennuspalikoita, joita käytämme indeksin rakentamiseen ja haun hallintaan. Näistä rakennuspalikoista voit lukea tarkemmin täältä .
Ensin sinun on määritettävä skeemasi kertoaksesi järjestelmälle tiedoistasi.
# accommodate our inputs in a typed schema @schema class MovieSchema: description: String title: String release_timestamp: Timestamp genres: String id: IdField movie = MovieSchema()
Seuraavaksi käytät välilyöntejä kertoaksesi, kuinka haluat käsitellä kutakin tiedon osaa upotuksen aikana. Mitä välilyöntejä käytetään, riippuu tietotyypistäsi. Jokainen tila on optimoitu upottamaan tiedot niin, että hakutulosten laatu on mahdollisimman korkea.
Avaruusmäärittelyissä kuvataan, kuinka syötteet tulee upottaa, jotta se heijastelee datamme semanttisia suhteita.
# 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])
Kun olet määrittänyt tilat ja luonut hakemiston, käytät kirjaston lähde- ja suorittajaosia kyselyjen määrittämiseen. Katso solut 10-13 muistikirjasta .
Nyt kun kyselyt ovat valmiit, siirrytään kyselyjen suorittamiseen ja haun optimointiin painoja säätämällä.
Äskettäisyystilan avulla voit muuttaa kyselysi tuloksia hakemalla ensisijaisesti vanhemmat tai uudemmat julkaisut tietojoukostasi. Käytämme ajanjaksoina 4, 10 ja 40 vuotta, jotta voimme antaa vuosille enemmän nimikkeitä tarkennetuksi - katso solu 5 ).
Huomaa pisteytyksen tauot 4, 10 ja 40 vuoden kohdalla. Yli 40 vuotta vanhemmille nimikkeille annetaan negative_filter
.
Määritetään nopea apufunktio tulosten esittämiseksi muistikirjassa.
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-kirjaston avulla voit suorittaa erilaisia kyselyitä; tässä määrittelemme kaksi. Molemmat kyselytyypit (yksinkertaiset ja edistyneet) antavat minun punnita yksittäisiä välilyöntejä (kuvaus, otsikko, genre ja tietysti viimeaikaisuus) mieltymysteni mukaan. Niiden välinen ero on se, että yksinkertaisella kyselyllä asetan yhden kyselytekstin ja näytän sitten samanlaisia tuloksia kuvaus-, otsikko- ja genre-tiloissa.
Tarkennetun kyselyn avulla minulla on tarkempi hallinta. Halutessasi voin kirjoittaa eri kyselytekstejä kuhunkin kuvaus-, otsikko- ja genre-tilaan. Tässä on kyselykoodi:
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")) )
Yksinkertaisissa kyselyissä asetan kyselytekstin ja käytän erilaisia painotuksia sen mukaan, miten ne ovat tärkeitä minulle.
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)
Tuloksemme sisältävät joitain otsikoita, jotka olen jo nähnyt. Pystyn käsittelemään tätä painottamalla viimeaikaisuutta ja painottamaan tuloksiani viimeaikaisiin nimikkeisiin. Painot normalisoidaan yksikkösummaksi (eli kaikki painot säädetään niin, että niiden summa on aina 1), joten sinun ei tarvitse huolehtia niiden asettamisesta.
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)
Tulokseni (yllä) ovat nyt vuoden 2021 jälkeisiä.
Yksinkertaisen kyselyn avulla voin painottaa minkä tahansa tietyn tilan (kuvauksen, otsikon, tyylilajin tai äskettäisyyden) saadakseni sen merkityksellisempään tuloksia palauttaessani. Kokeillaan tätä. Alla annamme enemmän painoarvoa genrelle ja pienemmälle otsikolle - kyselytekstini on periaatteessa vain genre, jossa on lisäkontekstia. Pidän äskettäisyyteni ennallaan, koska haluaisin silti tulosteni olevan puolueellisia viimeaikaisia elokuvia kohtaan.
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)
Tämä kysely siirtää julkaisuvuotta hieman taaksepäin, jotta saan enemmän genre-painotettuja tuloksia (alla).
Tarkennettu kysely antaa minulle entistä tarkemman hallinnan. Hallitsen äskettäisyyttä, mutta voin myös määrittää hakutekstin kuvaukselle, otsikolle ja genrelle ja määrittää kullekin tietyn painoarvon mieltymysteni mukaan alla (ja solut 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)
Sanotaan, että viime elokuvatuloksissani löysin elokuvan, jonka olen jo nähnyt ja haluaisin nähdä jotain vastaavaa. Oletetaan, että pidän Valkoisesta joulusta, vuoden 1954 romanttisesta komediasta (id = tm16479), joka kertoo laulajista ja tanssijoista, jotka kokoontuvat näyttämölle houkuttelemaan vieraita vaikeuksissa olevaan Vermontin majataloon. Kun lisäät ylimääräisen with_vector
-lausekkeen (jossa on movie_id
parametri) lisäkyselyyn, with_movie_query antaa minun tehdä hakuja tällä elokuvalla (tai millä tahansa elokuvasta, josta pidän), ja se antaa minulle täydellisen hallinnan erillisen alihakukyselyn tekstin ja painotuksen suhteen.
Ensin lisäämme movie_id-parametrimme:
with_movie_query = advanced_query.with_vector(movie, Param("movie_id"))
Ja sitten voin asettaa muut alihakukyselyni joko tyhjiksi tai mikä tahansa olennaisin, sekä kaikki järkevät painotukset. Oletetaan, että ensimmäinen kyselyni palauttaa tuloksia, jotka kuvastavat White Christmasin näyttämöesitystä/bändiä (katso solu 24 ), mutta haluan katsoa elokuvan, joka on perhekeskeisempi. Voin syöttää description_query_text-arvon vääristääkseni tuloksiani haluttuun suuntaan.
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)
Mutta nyt kun näen tulokset, ymmärrän, että olen itse asiassa enemmän fiiliksissä johonkin kevyempään ja hauskaan. Muokataan kyselyäni vastaavasti:
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)
Okei, tulokset ovat parempia. Valitsen yhden näistä. Laita popcornit päälle!
Superlinkedin avulla on helppo testata, iteroida ja parantaa hakulaatua. Yllä olemme opastaneet sinua Superlinked-kirjaston avulla, jotta voit tehdä semanttisen haun vektoriavaruudesta, kuten Netflix tekee, ja palauttaa tarkkoja, osuvia elokuvatuloksia. Olemme myös nähneet, kuinka voimme hienosäätää tuloksiamme, säätämällä painoja ja hakutermejä, kunnes saavutamme juuri oikean tuloksen.
Kokeile nyt muistikirjaa itse ja katso, mitä voit saavuttaa!
Suositusmoottorit muokkaavat tapaamme löytää sisältöä. Olipa kyse elokuvista, musiikista tai tuotteista, vektorihaku on tulevaisuutta – ja nyt sinulla on työkalut oman luomiseen.
Kirjailija: Mór Kapronczay