Система рекомендаций фильмов, использующая совместную фильтрацию и алгоритм рекомендаций на основе графиков Pinterest-Pixie

С ростом конкуренции между ОТТ-платформами появляются новые схемы и стратегии увеличения удержания. Хотя могут использоваться большие привлекательные предложения, именно сам контент в конечном итоге отвечает за удержание (или потерю) клиентов.

Итак, вот вам и «Системы рекомендаций (RS)», система фильтрации информации, предлагающая пользователю наиболее релевантный контент. Идея может показаться очень простой, но это лучший способ привлечь клиента к продукту. Если пользователь не увидит соответствующий контент на вашей платформе, он, скорее всего, обратится к вашему конкуренту.

Теперь эту релевантность элемента для пользователя можно определить несколькими способами, что приводит нас к трем основным типам рекомендательных систем:

  1. Контентно-рекомендательные системы: он рассматривает проблему как типичную задачу классификации или регрессии машинного обучения, которая использует метаинформацию как о пользователях, так и об элементах в качестве функций для набора данных; метка класса — это оценка, которую пользователь дает этому фильму.

  1. Системы рекомендаций на основе сходства. Они основаны либо на сходстве пользователей, либо на сходстве элементов, где сходство вычисляется с использованием любой метрики сходства, например косинусного сходства. Таким образом, чтобы порекомендовать фильмы пользователю «u», мы можем либо просмотреть матрицу сходства между пользователями и порекомендовать фильмы, которые смотрели пользователи, похожие на пользователя «u», либо мы можем посмотреть на матрицу сходства элементов и порекомендовать фильмы, похожие на эти фильмы. который пользователь 'u' уже смотрел.

  1. Системы рекомендаций по совместной фильтрации. Интуитивно это очень похоже на RS, основанное на сходстве, и часто рассматривается как одно и то же. Однако здесь я различаю их из-за математического подхода, стоящего за этим. Математически он решает задачу завершения матрицы для матрицы элементов пользователя (A), элементами которой (Aᵤᵢ) являются оценки, данные пользователем «u» элементу «i». Эта задача достигается с помощью методов матричной факторизации (MF), таких как SVD, NNMF и т. д., из-за чего ее также называют «рекомендательными системами на основе матричной факторизации».

Фу! Это было много информации, чтобы упаковать в короткое вступление. Однако из них мы собираемся внедрить только Collaborative Filtering RS. Так что, если это еще не ясно, не волнуйтесь, мы столкнемся с этим позже.

Но перед этим, вот еще одно дополнение к трем вышеупомянутым методам, Система рекомендаций на основе графика:

Итак, когда я прочитал о Пикси (нет, не о той стрижке) в этой статье, я решил попробовать ее для рекомендаций фильмов. Если вы не слышали о Pixie, это алгоритм Pinterest на основе графа, созданный для предложения Похожих пинов в режиме реального времени с низкой задержкой. В отличие от Netflix, например, Pinterest должен предоставлять рекомендации для более чем 100 миллиардов идей, сохраненных 150 миллионами человек, и это тоже в режиме реального времени, господи! Как работает Pixie (кстати, какое милое имя (^-^)) выглядит следующим образом:

Он создает двудольный граф из пинов и досок. Когда пользователь сохраняет пин «q» на доске, Pixie запускает предвзятое случайное блуждание от «q» на 100 000 шагов по пути пины-доски-пины. Исходя из этого, он определяет 1000 пинов, которые были затронуты больше всего во время прогулки, и рекомендует их пользователю. И да, прогулка «смещена», потому что мы установили вероятность вернуться к исходному узлу «q» как 0,5. Это гарантирует, что мы не отклонимся слишком далеко от вывода запроса «q».

Для наших рекомендаций по фильмам мы создадим двухчастный график: фильмыс одной стороны и жанр + ключевые слова сюжета с другой. другой.

Теперь, когда теория позади, давайте начнем…

Как и любая проблема машинного обучения, мы следуем шагам жизненного цикла науки о данных.

Постановка проблемы:

Учитывая метаданные фильма и пользовательские оценки разных фильмов, мы должны рекомендовать пользователю новые фильмы.

Обзор данных:

Набор данных скачивается отсюда. Это распространяемая форма исходного набора данных MovieLens¹ ml-latest-small. Скачать и прочитать о нем подробнее можно по указанной ссылке. Вот файлы, которые нас интересуют:

  • movie_metadata.csv
  • ключевые слова.csv
  • links_small.csv
  • ratings_small.csv

Если на вашем компьютере достаточно оперативной памяти, вы можете загрузить файлы большего размера для оценок и ссылок.

import pandas as pd
movies = pd.read_csv(path_+'movies_metadata.csv')
ratings = pd.read_csv(path_+'ratings_small.csv')
keywords = pd.read_csv(path_+'keywords.csv')
links = pd.read_csv(path_+'links_small.csv')

Исследовательский анализ данных:

На этом этапе мы выполняем очистку данных, предварительную обработку, анализ данных и т. д., о которых я не буду подробно рассказывать.

  • Фрейм данных рейтингов состоит из столбцов: movieId, userId, рейтинг и метка времени.
  • связывает кадр данных, состоящий из столбцов: id (movieId), tmdbId, imdbId
  • кадр данных фильмов состоит из слишком большого количества ненужных столбцов, и мы избавляемся от них, за исключением нескольких.
movies.columns
Index(['adult', 'belongs_to_collection', 'budget', 'genres', 'homepage', 'id',
       'imdb_id', 'original_language', 'original_title', 'overview',
       'popularity', 'poster_path', 'production_companies',
       'production_countries', 'release_date', 'revenue', 'runtime',
       'spoken_languages', 'status', 'tagline', 'title', 'video',
       'vote_average', 'vote_count'],
      dtype='object')

Мы объединяем эти три фрейма данных вместе с фреймом данных keywords, чтобы получитьmovie_ratings со столбцами: movieId, название, жанры, ключевые слова, tmdbId, userId, рейтинг, отметка времени.

Примечание. movieId находится в сериях 1,2,3,… и т. д., а tmdbId нужен для получения сведений о фильме из API TMDB во время развертывания модели.

Затем мы выполняем разделение тестов поезда следующим образом:

if not (os.path.isfile(path_+’xtrain.csv’) and os.path.isfile(path_+’xtest.csv’)): 
 movie_ratings[:int(len(movie_ratings)*0.8)].to_csv(path_+’xtrain.csv’, index=False)
 movie_ratings[int(len(movie_ratings)*0.8):].to_csv(path_+’xtest.csv’, index=False)
X_train = pd.read_csv(path_+'xtrain.csv')
X_test = pd.read_csv(path_+'xtest.csv')

Самое время познакомить вас с проблемой рекомендательных систем — проблемой холодного запуска! По сути, это когда вы сталкиваетесь с пользователем, который не смотрел ни одного фильма, новым пользователем. Без каких-либо знаний о том, что нравится и не нравится пользователю, как мы можем рекомендовать ему что-то? То же самое с новым товаром, фильмом, которому еще не присвоен рейтинг. Кому мы рекомендуем такой фильм?

train_movies = set(X_train.movieId.values)
test_movies = set(X_test.movieId.values)
total_movies = train_movies.union(test_movies)
print('No. of movies in train set:', len(train_movies))
print('No. of movies in test set:', len(test_movies))
print('No. of movies present in test set but not in train set:', len(test_movies-train_movies))
print('Percentage of movies present in test but not in train set of all the movies: {}%'.format(len(test_movies-train_movies)/len(total_movies)*100))
No. of movies in train set: 7328
No. of movies in test set: 4732
No. of movies present in test set but not in train set: 1697
Percentage of movies present in test but not in train set of all the movies: 18.803324099722992%
train_users = set(X_train.userId.values)
test_users = set(X_test.userId.values)
total_users = train_users.union(test_users)
print('No. of users in train set:', len(train_users))
print('No. of users in test set:', len(test_users))
print('No. of users present in test set but not in train set:', len(test_users-train_users))
print('Percentage of users present in test but not in train set of all the users: {}%'.format(len(test_users-train_users)/len(total_users)*100))
No. of users in train set: 546
No. of users in test set: 148
No. of users present in test set but not in train set: 125
Percentage of users present in test but not in train set of all the users: 18.628912071535023%

В нашем случае у нас есть примерно такой же процент фильмов и пользователей, которые встречаются в тестовом наборе, но не в наборе поездов. Один из способов решить эту проблему — использовать метаданные пользователя/элемента, такие как возраст или местоположение пользователя, категория или жанр элемента, актеры, режиссер и т. д., чтобы рекомендовать его соответствующим пользователям. Однако, если у нас нет этой информации, как в нашем случае, мы можем просто порекомендовать самые популярные фильмы на платформе новому пользователю, используя систему рекомендаций на основе популярности,также известную как демографическая фильтрация. , обобщенная система. Давайте построим один!

Но держись! На этом этапе я хочу, чтобы вы визуализировали рабочий процесс конечного продукта, и поэтому теперь, оставляя в стороне традиционные этапы цикла данных, я также расскажу об этапе развертывания.

Итак, мы разработаем веб-сайт/приложение таким образом, чтобы оно представляло домашнюю страницу случайного пользователя из набора данных. Для этого мы используем метод случайной выборки numpy numpy.random.choice() для случайной выборки userId из набора данных. Затем мы получаем список фильмов, которые смотрел этот пользователь. Если этот список пуст, мы знаем, что пользователь является новым пользователем, и в этом случае мы отображаем 20 самых популярных фильмов. Если список не пуст, мы можем отобразить 10 лучших рекомендаций, полученных с использованием подхода Collaborative Filtering. Для старого пользователя мы также отображаем 5 лучших фильмов из этого списка, чтобы мы могли понять, похожи ли они на 20 лучших рекомендаций совместной фильтрации.

Мы используем Streamlit, чтобы упростить создание нашего приложения. Мы устанавливаем конфигурацию страницы, как показано ниже.

import streamlit as st

st.set_page_config(
    page_title="Movie App",
    page_icon=":film_projector:",
    layout="centered",
    initial_sidebar_state="expanded")

user_no = np.random.choice(range(0,671))

1. Система рекомендаций на основе популярности

По популярности мы выбираем только те фильмы, которые оценили не менее 100 пользователей. top20_ids содержит 20 самых популярных фильмов в нашем наборе данных.

top_bool = X_train.groupby('movieId').count()['rating']
top_ind = top_bool[top_bool>100].index
top_movies = X_train[X_train.movieId.isin(top_ind)]
top20_ids = top_movies.groupby('movieId').rating.mean().sort_values(ascending=False)[:20].index        #average rating

2. Система рекомендаций по совместной фильтрации

И мы вернулись к этому! Как упоминалось ранее, в своей основе он использует метод матричной факторизации. Это помогает зафиксировать неявное взаимодействие пользователя с фильмом. Теперь, если я перейду к работе алгоритма, это будет совершенно новое обсуждение, которое здесь не находится в центре внимания. Также, хотя мы можем написать код всего алгоритма с нуля, мы прибегнем к его более мощной реализации с помощью Surprise Library. Он также содержит больше вариантов реального алгоритма, который я опробовал.

Библиотека поездов для неожиданностей — это объект класса surprise.trainset.Trainset, который должен быть построен, как показано ниже. С другой стороны, набор тестов — это просто список списков (или кортежей).

import surprise
from surprise import Reader,Dataset

train_ratings = X_train[['userId','movieId','rating']]
test_ratings = X_test[['userId','movieId','rating']]

#Trainset
reader = Reader(rating_scale=(1,5))
data = Dataset.load_from_df(train_ratings,reader)
trainset = data.build_full_trainset()

#Testset
#list of list (or tuples)==> (user, id, rating)
testset = test_ratings.values

Мы протестируем три модели (BaselineOnly, SVD, SVD++), доступные в Surprise Library, и выберем ту, которая работает лучше. Показателем производительности будет RMSE. Результатом, который мы получим от алгоритма (метод тестирования/прогнозирования), будет список прогнозов, пример показан ниже.

[Prediction(uid=383, iid=47, r_ui=5.0, est=3.614847468612193, details={‘was_impossible’: False}),
 Prediction(uid=383, iid=1079, r_ui=3.0, est=3.5756706411528865, details={'was_impossible': False}),
...]

где uid = userId, iid=movieId, r_ui=рейтинг, est=прогнозируемый рейтинг. Чтобы извлечь соответствующие данные, мы определяем следующие две функции:

from sklearn.metrics import mean_squared_error

def get_predictions(results):
    true = np.array([i.r_ui for i in results])
    pred = np.array([i.est for i in results])
    return true,pred

def rmse(true,pred):
    return np.sqrt(np.mean((true-pred)**2))

Теперь мы можем обучать наши модели. Вы можете прочитать документацию для каждого из них. Вот, например, SVD — это знаменитый алгоритм SVD, который популяризировал Саймон Фанк во время Приза Netflix. ТУДУМ!

Он пытается минимизировать квадрат ошибки между истинным и прогнозируемым рейтингом, используя SGD (стохастический градиентный спуск); что означают эти переменные, можно понять из приведенной выше ссылки на Netflix Prize.

from surprise import BaselineOnly, SVD, SVDpp

if not os.path.isfile(path_+'svdalgo.pkl'):
    algo = SVD(n_factors=100, biased=True, random_state=42)
    algo.fit(trainset)
    surprise.dump.dump(path_+'svdalgo.pkl',algo=algo)
    print('Done Dumping...!')
algo = surprise.dump.load(path_+'svdalgo.pkl')[1]  #tuple (prediction,algo)

Объект trainset, который мы создали ранее, требуется только для подгонки данных (метод fit), для тестирования нам нужен тот же формат, что и у нашего набора тестов. Итак, мы делаем это,

train_test = trainset.build_testset()
train_results = algo.test(train_test)#, verbose=True)
test_results = algo.test(testset)#, verbose=True)

true,pred = get_predictions(train_results)
print('Train RMSE:', rmse(true,pred))
true,pred = get_predictions(test_results)
print('Test RMSE:', rmse(true,pred))

Вот результаты трех испробованных мной алгоритмов. Алгоритм SVD++ выбран потому, что он работает немного лучше, чем SVD.

Здесь в функции recommend_collab() мы определяем процесс, описанный ранее на блок-схеме. movies_watched — это список фильмов, оцененных пользователем, если список пуст, мы рекомендуем top20_ids (популярные фильмы), в противном случае мы предсказываем оценки пользователя на все непросмотренные фильмы с помощью svdpp_algo. Из этого мы выбираем top10_id, которые, по прогнозам, будут иметь высокие оценки, и рекомендуем их пользователю. liked_id содержит 5 фильмов, получивших высокую оценку пользователя.

svdpp_algo = surprise.dump.load(path_+'svdpp_algo.pkl')[1]     #tuple (prediction,algo)

def recommend_collab(user_id):
    movies_watched = set(train_ratings[train_ratings.userId==user_id].movieId.values)

    if len(movies_watched)==0:
        return [],[]

    movies_unwatched = set(train_movies.movieId) - movies_watched

    results = []
    for mid in movies_unwatched:
        results.append(svdpp_algo.predict(user_id, mid))

    df =  pd.DataFrame([(i.iid,i.est) for i in results], columns=['movieId', 'rating']).sort_values('rating', ascending=False)
    top10_ids = df.movieId[:10]

    liked_ids = train_ratings[train_ratings.userId==user_id].sort_values('rating', ascending=False).movieId.values[:5]
    return (top10_ids, liked_ids)

top10_ids, liked_ids = recommend_collab(user_no)

3. Рекомендательные системы на основе графов

Окончательно!! Барабанная дробь, пожалуйста :) В этой системе мы рекомендуем 5 фильмов, похожих на фильм, который пользователь ищет с помощью панели поиска (или выбора), как показано ниже.

Как я упоминал ранее, сначала мы создаем двудольный график фильмовв зависимости от жанров и ключевых слов сюжета.

Еще один важный момент: во время EDA было обнаружено, что некоторые общие ключевые слова, такие как человек, корабль, любовь, ручка и т. д. встречаются чаще, чем другие, такие как человек против машины, искусственный интеллект и т. д. То же самое с такими жанрами, как драма, комедия, триллер, мелодрама и т. д. которые более популярны, чем другие. Они, хотя и очень важны, во время случайного обхода могут посещаться чаще, чем другие, даже если запрашиваемый фильм, скажем, анимация, очень редкий жанр в наборе данных.

Таким образом, чтобы избежать этого, мы придаем вес каждой ссылке/реберу, чтобы наиболее частые ключевые слова и жанры имели меньшее значение. Простой прием состоит в том, чтобы вычислить распределение фильмов по каждому уникальному жанру и ключевому слову и взять обратное значение этих значений. в качестве веса для фильма, принадлежащего этому жанру/ключевому слову.

Мы также придаем в 20 раз больше веса жанру, так как это довольно важный фактор для получения похожих фильмов.

import networkx as nx
from networkx.algorithms import bipartite
import pickle

dist = []
den = len(train_movies)
for i in genres:           #genres is a set of all the 20 unique genres
    v = len(train_movies[train_movies.genres.str.match('.*'+i+'.*')==True])/den*100
    dist.append((v,i))
dist = pd.DataFrame(dist).sort_values(0)
dist[0] = dist[0].apply(lambda x: x**(-1))
weights_genre = dict(zip(list(dist[1].values),list(dist[0].values)))


dist = []
den = len(train_movies)
for i in tqdm(keywords):   #keywords is a set of all the 11611 unique genres
    if i=='':   continue            #empty keyword 
    v = len(train_movies[train_movies.keywords.str.match('.*'+i+'.*')==True])/den*100
    dist.append((v,i))
dist = pd.DataFrame(dist).sort_values(0)
dist[0] = dist[0].apply(lambda x: (x+1)**(-1))
weights_keywords = dict(zip(list(dist[1].values),list(dist[0].values)))

#create edges
edges = []
for row in train_movies.iterrows():
    mid = row[1].movieId
    edges.extend([(mid, k, weights_keywords.get(k,0)*10) for k in ast.literal_eval(row[1].keywords)]) 
    edges.extend([(mid, g, weights_genre[g]*200) for g in ast.literal_eval(row[1].genres)])
edges[:10]
data = pd.DataFrame(edges, columns=['movieId','keywords','weights'])

#create graph
if not os.path.isfile(path_+'graph.pkl'):
    B = nx.Graph()
    B.add_nodes_from(data.movieId.unique(), bipartite=0, label='movie')
    B.add_nodes_from(data.keywords.unique(), bipartite=1, label='keygen')
    B.add_weighted_edges_from(edges)
    pickle.dump(B,open(path_+'graph.pkl','wb'))

Вот узел для фильма «История игрушек», показывающий ссылки на его жанры (семейный, анимация, комедия) и ключевые слова. Обратите внимание, как высоко взвешенные жанры и ключевые слова довольно близки к узлу.

sub=nx.ego_graph(B,1)          #Toy Story
nx.draw_networkx(sub)

Без весов случайное блуждание посещало бы фильмы, основанные на дружбе, соперничестве, ревности, комедиях, друзьях, мальчиках и т. д.,рекомендуемые ромкомы, любовные трейнглы, криминал и т. д. О Боже!

Мы выбираем длину пути 10 000, два других важных параметра — p и q.

  • 1/pопределяет вероятность, с которой мы вернемся к исходному узлу
  • 1/q определяет вероятность, с которой мы удаляемся от исходного узла.

Таким образом, pдолжно быть низким, чтобы 1/p было высоким, и наоборот, для q. Для Pinterest Pixie мы увидели, что 1/p равно 0,5, но это не очень хорошо работает с имеющимся у нас набором данных о фильмах. Поэтому я настроил эти два параметра для различных комбинаций значений и протестировал результаты на нескольких фильмах, чтобы окончательно остановиться на p=0,01 и q=100.

Случайное прохождение содержит идентификатор фильма, жанр, ключевые слова. Мы избавляемся от строк и оставляем только movieId. После этого мы используем CountVectorizer sklearn для подсчета вхождений movieId, а затем получаем top5_id, которые встречаются чаще всего.

from stellargraph.data import BiasedRandomWalk
from stellargraph import StellarGraph
from sklearn.feature_extraction.text import CountVectorizer

B = pickle.load(open(path_+'graph.pkl','rb'))

def graph_recommend(q):

    rw = BiasedRandomWalk(StellarGraph(B))
    walk = rw.run(nodes=[q], n=1, length=10000, p=0.01, q=100, weighted=True, seed=42)

    #with 1/p prob, it returns to the source node
    #with 1/q prob, it moves away from the source node
    #Shape of walk: (1,10000)

    walk = list(filter(lambda x:type(x)==int, walk[0])) #getting rid of keywords and genres... left with only movieIds
    walk = list(map(str, walk))                         #for countvectorizer
    walk = ' '.join(walk)                               #['m1','m2','m3'] ====> 'm1 m2 m3'... for tokenzation

    vocab = {str(mov):ind for ind,mov in enumerate(train_movies.movieId.sort_values().unique())}   #movieId:index
    vec = CountVectorizer(vocabulary=vocab)
    embed = vec.fit_transform([walk])

    reverse_vocab = {v:int(k) for k,v in vocab.items()}         #index:movieId
    embed = np.array(embed.todense())[0]

    top5_ids=[]
    for ind in embed.argsort()[::-1]:
        if len(top5_ids)==5: break
        movid = reverse_vocab[ind]
        if movid!=q: top5_ids.append(movid)

    return top5_ids

Теперь переходим к финальному акту!

Мы почти закончили. Теперь нам просто нужно реализовать пользовательский интерфейс (пользовательский интерфейс) нашего приложения.

  • Как для «нового пользователя», так и для «старого пользователя» у нас должна быть панель поиска/выбора для выбора фильма в нашем наборе данных. Кроме этого,
  • Главная страница «нового пользователя» состоит из 20 рекомендаций популярных фильмов.
  • Домашняя страница «старого пользователя» состоит из 5 блоков избранных фильмов и 10 рекомендаций по совместной фильтрации.
  • Когда пользователь ищет фильм, сведения о фильме отображаются под панелью выбора, под которой находится блок для отображения 5 фильмов, похожих на этот фильм, полученных из рекомендаций на основе графика.

Вот как мы это делаем:

if len(top10_ids)==0:
    st.header('Hello stranger!!')

    show_bar()
    st.title('Most Popular movies on the platform')
    show_blocks(top20_ids,[4,8,12,16,20])

else:
    st.header('Welcome user '+str(user_no))
    show_bar()
    st.title('Your Favourites...')
    show_blocks(liked_ids,[1,2,3,4,5])
    st.title('Based on your taste...')
    show_blocks(top10_ids,[2,4,6,8,10])

В приведенном выше коде используются определенные пользователем функции show_bar() для отображения полосы выбора и show_blocks() для отображения фрагментов фильмов. Список, который вы видите в качестве параметра функции show_blocks(), определяет шаги, на которых вы разбиваете список movie_ids для отображения фрагментов. Вы поймете это лучше, когда посмотрите на определение функции.

Еще одна важная вещь: на странице результатов поиска отображаются детали фильма, такие как постер, обзор сюжета, дата выпуска, время выполнения, средний рейтинг и количество голосов. Мы получаем эти данные из API TMBD. Вам нужно будет создать учетную запись, заполнить несколько деталей и запросить ключ API для вашего проекта.

import requests

base_url = 'https://image.tmdb.org/t/p/w500'  #for poster image 

def fetch_details(tmdbId):
    response = requests.get('https://api.themoviedb.org/3/movie/{}?api_key=<<your_api_key>>&language=en-US'.format(tmdbId))
    return response.json()

def show_blocks(id_lst,steps):

    lst = []
    for ind in id_lst:
        tid = train_movies[train_movies.movieId==ind].tmdbId.values[0]
        details = fetch_details(tid)
        name = details['title']
        img = base_url+details['poster_path']
        lst.append((name, img))

    col1, col2, col3, col4, col5 = st.columns(5)
    with col1:
        for i in lst[:steps[0]]:
            st.image(i[1])
            st.text(i[0])
    with col2:
        for i in lst[steps[0]:steps[1]]:
            st.image(i[1])
            st.text(i[0])
    with col3:
        for i in lst[steps[1]:steps[2]]:
            st.image(i[1])
            st.text(i[0])
    with col4:
        for i in lst[steps[2]:steps[3]]:
            st.image(i[1])
            st.text(i[0])
    with col5:
        for i in lst[steps[3]:steps[4]]:
            st.image(i[1])
            st.text(i[0])

def show_bar():
    mov_name = st.selectbox('What are you looking for...?', train_movies.title.values)
    mid, tid = train_movies[train_movies.title==mov_name][['movieId','tmdbId']].values[0]
    details = fetch_details(tid)

    if st.button('Search'):

        col1,col2 = st.columns(2)
        with col1:
            st.image(base_url+details['poster_path'])
        with col2:
            st.header(details['title'])
            st.caption(details['tagline'])
            st.write(details['overview'])
            st.markdown("**Released in {}**".format(details['release_date']))
            st.write('Runtime: {} mins'.format(details['runtime']))
            st.write('Avg. Rating: {} :star:            Votes: {} :thumbsup:'.format(details['vote_average'], details['vote_count']))

        st.header("More like this...")
        top5_ids = graph_recommend(mid)
        show_blocks(top5_ids,[1,2,3,4,5])

И вот мы наконец закончили! Сейчас мы просто видим, насколько хорошо мы справляемся с рекомендациями. Что ж, в случае рекомендаций на основе жанра у нас все хорошо, как видно из примера «История игрушек», учитывая, что в нем показано несколько анимационных фильмов. Что касается рекомендаций по совместной фильтрации, мы не можем сказать наверняка, потому что фильмы рекомендуются на основе взаимодействия пользователя с фильмом, сходства пользователя с пользователем и фильма с фильмом, но если мы посмотрим на приведенный ниже пример, похоже, мы делаем достойную работу. Пользователь, похоже, проявляет интерес к криминальным фильмам, также смотрел «Властелина колец». Наша система также рекомендует некоторые криминальные фильмы, и хотя мы не видим никаких фильмов о Бэтмене, мы видим там еще один фильм «Властелин колец». Так что да!

Наконец,

Я очень благодарен, если вы остались здесь! Мы внедрили приложение для рекомендаций фильмов, используя три метода рекомендаций: на основе популярности, на основе совместной фильтрации и на основе графика. Результаты также приличные, и, конечно, всегда есть возможности для улучшения, может быть, мы можем попробовать разные значения для p и q, параметров случайного блуждания, или, может быть, мы можем использовать другой метод взвешивания для ребер. Дайте мне знать, если вы что-то обнаружите.

Кроме того, если вы похожи на меня, вы, возможно, задавались вопросом, можем ли мы использовать метод на основе графа для совместной фильтрации с графом узлов фильм и пользователь! Что ж, если вы это сделали, вы можете проверить этот блог.

Вы можете найти код в моем профиле Github. Если вы нашли эту статью полезной, пожалуйста, похлопайте в ладоши или оставьте отзыв ниже. Спасибо!

[1] Ф. Максвелл Харпер и Джозеф А. Констан. 2015. Наборы данных MovieLens: история и контекст. Транзакции ACM в интерактивных интеллектуальных системах (TiiS) 5, 4: 19: 1–19: 19.

https://dl.acm.org/doi/10.1145/2827872