Chaque jour, les utilisateurs du monde entier reçoivent un grand nombre de publipostages différents - uniquement via le service MailChimp envoie quotidiennement un milliard de lettres . Parmi ceux-ci, 20,81% sont découverts.
Chaque mois, les utilisateurs de nos sites reçoivent des newsletters contenant des documents sélectionnés par l'éditeur. Environ 21% des lecteurs ouvrent ces lettres.
Afin d'augmenter ce nombre, vous pouvez les personnaliser. Une façon consiste à ajouter un système de recommandation qui suscitera des documents intéressants pour un lecteur particulier.
Dans cet article, je parlerai de la façon de mettre en œuvre un système de recommandation à partir de zéro basé sur le filtrage collaboratif.
La première partie de l'article contient les bases théoriques de la mise en œuvre du système de recommandation. Les mathématiques à l'école suffisent pour comprendre le matériel.
La deuxième partie décrit une implémentation Python pour nos données de site.
Un peu de théorie du filtrage collaboratif
Le filtrage collaboratif est probablement l'approche la plus simple dans les systèmes de recommandation. Il est basé sur l'idée que des utilisateurs similaires aiment des objets similaires, tels que des articles.
Que signifie «utilisateurs similaires»?

Comment déterminer combien Vasily est comme Ivan ou un article sur SQL Server pour un article sur PostgreSQL?
Regardons un exemple. Disons que nous avons quatre utilisateurs: Vasily, Ivan, Inna et Anna. Le site comporte cinq articles: article 1, article 2, article 3, article 4 et article 5. Dans le tableau ci-dessous, le nombre à l'intersection de l'utilisateur et de l'article est la note de l'utilisateur de l'article sur une échelle de cinq points. Zéro dans le tableau sont des articles qui n'ont pas été évalués par l'utilisateur. Par exemple, Vasily a aimé les articles 1, 3 et 4.
Tableau 1
Intuitivement, nous pouvons supposer que si les utilisateurs aiment les mêmes articles, leurs goûts coïncident. Que pensez-vous, dont les intérêts sont similaires à ceux de Vasily?
Les intérêts de Vasily sont plus similaires aux intérêts d'Ivan et d'Inna et moins semblables aux intérêts d'Anna. Pourquoi - on le dira plus loin.
Pour poursuivre les travaux, il est nécessaire de formaliser et de mesurer la «similitude» de Vasily et Ivan ou Inna et Anna.
La façon la plus simple de procéder consiste à considérer les évaluations des utilisateurs comme une description de leur profil. Dans l'exemple, chaque ligne du tableau est une description d'un utilisateur. La première ligne - la description de Basil - est un vecteur de cinq nombres: [4, 0, 5, 5, 0]; le second - Ivan - [0, 0, 4, 5, 0]; le troisième est Inna - [4, 2, 4, 0, 0]; le quatrième est Anna - [5, 5, 0, 0, 5].
Vous pouvez maintenant introduire le concept de descriptions d'utilisateurs "mesure de similitude".
Une façon de mesurer la «similitude» des utilisateurs est de calculer la distance cosinusoïdale entre les vecteurs qui les décrivent.

La distance cosinus est calculée par la formule:
où et - vecteurs de description d'utilisateur; - produit scalaire des vecteurs de description; , - longueurs des vecteurs de description.
La signification de la distance cosinus est la suivante: si deux vecteurs où et (vecteurs de description d'utilisateur) sont "similaires", alors l'angle entre eux tendra vers zéro et le cosinus de cet angle tendra vers l'unité. Dans le cas idéal, lorsque les «intérêts» des deux utilisateurs coïncident, la distance cosinus pour eux sera nulle.
Distance cosinus entre Vasily et Ivan:
De même, la distance cosinus entre Vasily et Anna est de 0,715. Autrement dit, les intérêts de Vasily ressemblent davantage aux intérêts d'Ivan qu'à ceux d'Anna.
Comment prédire les notes des utilisateurs?
Cette partie est la plus intéressante. Il existe de nombreuses options différentes. Ci-dessous, nous considérons deux options simples.
Note prévue - note moyenne parmi les utilisateurs «similaires»
L'option la plus simple pour calculer la note prévue est de voir quelles notes les utilisateurs «similaires» attribuent à l'article et de prendre la note moyenne:
Dans cette formule:
- Est-ce l'estimation prévue pour e article et utilisateur ,
- - note des utilisateurs pour e article
- —Beaucoup d'utilisateurs "similaires",
- - le nombre d'utilisateurs «similaires».
Note prévue - note moyenne pondérée parmi les utilisateurs «similaires»
Une option un peu plus compliquée consiste à prendre en compte le degré de similitude: les évaluations d'utilisateurs plus similaires devraient influer davantage sur l'évaluation finale que les évaluations d'utilisateurs moins similaires:
Dans cette formule:
- Est-ce l'estimation prévue pour e article et utilisateur ,
- - note des utilisateurs pour e article
- —Beaucoup d'utilisateurs "similaires",
- - «similitude» (distance cosinus) des utilisateurs et .
Comment mesurer la qualité des recommandations?

Lors de la création d'un système de recommandation, vous devez déterminer la métrique par laquelle vous pouvez évaluer la qualité de notre modèle - dans quelle mesure le système offre à l'utilisateur de nouveaux matériaux. Par exemple, l'erreur quadratique moyenne racine ( ) Est la racine carrée de l'erreur moyenne pour toutes les évaluations des utilisateurs. Formellement, cette mesure est décrite par la formule:
Dans cette formule
- - l'ensemble de toutes les évaluations d'articles par les utilisateurs,
- - note d'utilisateur prévue article ,
- - évaluation réelle des utilisateurs article .
Dans le cas idéal, lorsque les notes prévues coïncident avec les égal à zéro.
Prenons un exemple. Deux systèmes de référence ont fait des prédictions pour Vasily. Le résultat est dans le tableau ci-dessous.
Il est intuitivement clair que le deuxième système de recommandation a prédit des notes mieux que le premier. Compter :
L'erreur pour les évaluations du deuxième système de recommandation devrait être considérablement plus faible.
Implémentation
Nous avons à notre disposition la plupart des données sur les articles et les utilisateurs du site: informations sur les articles, les tags, les likes des utilisateurs, etc.
Pour implémenter le filtrage collaboratif, les évaluations des utilisateurs sont suffisantes.
Clause de non-responsabilitéCi-après, le code est écrit «dans le front» pour démontrer la logique du système de recommandation. Dans la vraie vie, il est préférable d'utiliser toutes les fonctionnalités de numpy
et de pandas
.
import pandas as pd import numpy as np import os ratings_df = pd.read_csv('./input/Ratings.csv') print(' :', ratings_df.shape[0]) print(' :', ratings_df[ratings_df['Rate']].shape[0]) unique_user_ids = ratings_df[ratings_df['Rate']]['UserId'].unique() print(' :', len(unique_user_ids)) ratings_df.head()
Sortie [1]Données totales: 15313
Évaluations positives: 15121
Utilisateurs actifs: 1007
1007 utilisateurs actifs ont donné 15313 "évaluations". De ce nombre, 15121 «aime».
Les données contiennent quatre colonnes: l'identifiant de ligne de la base de données (colonne Id ), l'identifiant d'objet (colonne DocumentId ), un signe que l'utilisateur a aimé l'article (colonne Rate ) et l'identifiant utilisateur (colonne UserId ).
Pour plus de commodité, ajoutez la colonne RateInt . 1 dans cette colonne signifie que l'utilisateur a aimé l'article; -1 - qui n'a pas aimé.
ratings_df['RateInt'] = ratings_df['Rate'].apply(lambda x: 1 if x else -1) ratings_df.head()
Pour les travaux ultérieurs, il est nécessaire de diviser l'ensemble de données en formation et test: la formation sera utilisée pour former le modèle, et le test déterminera la qualité des prédictions.
from sklearn.model_selection import train_test_split train, test = train_test_split(ratings_df, test_size=0.2)
Pour plus de commodité, nous transformons chaque ensemble en un tableau, où dans les lignes sont les identifiants des utilisateurs, et dans les colonnes sont les identifiants des articles par analogie avec l'exemple au début de l'article.
def create_matrix(df): ratings_per_user = [] post_ids = df['DocumentId'].unique() for user_id in tqdm_notebook(all_users_ids, ''): row = {'user_id': user_id} ratings = df[df['UserId'] == user_id]['DocumentId'].values for post_id in post_ids: row[str(post_id)] = 1 if post_id in ratings else 0 ratings_per_user.append(row) return pd.DataFrame(ratings_per_user) train_df = create_matrix(train) test_df = create_matrix(test)
Matrice correspondant aux utilisateurs et articles préférés vous permettra de calculer la distance cosinus entre les utilisateurs:
from scipy import spatial def cos_distance(x1, x2): return spatial.distance.cosine(x1, x2) at_least_one_fav_post_users = list(train_valuable_df['user_id'].values) def calculate_distances(df): columns = df.columns[:-1] cp = at_least_one_fav_post_users.copy() data = [] for user_id_1 in tqdm_notebook(at_least_one_fav_post_users, ''): row = {'user_id': user_id_1} for user_id_2 in cp: x1 = df[df['user_id'] == user_id_1][columns].values[0] x2 = df[df['user_id'] == user_id_2][columns].values[0] row[str(user_id_2)] = cos_distance(x1, x2) data.append(row) return pd.DataFrame(data) train_distances = calculate_distances(train_valuable_df)
Maintenant, tout est prêt afin de proposer aux utilisateurs des articles qui, à notre avis, plairont.
Nous mettons en œuvre les deux stratégies de calcul des recommandations décrites ci-dessus: les notes moyennes et moyennes pondérées chez des utilisateurs similaires.
Première voie
Nous prenons 10 utilisateurs les plus proches du courant et prédisons la note moyenne pour des utilisateurs similaires pour l'article:
from tqdm import tqdm_notebook import heapq def rmse(predicted, actual): return ((predicted - actual) ** 2).mean() ** 0.5 def get_similar(id, n): df = train_distances[train_distances['user_id'] == id] d = df.to_dict('records')[0] top_similar_ids = heapq.nsmallest(n+1, d, key=d.get) top_similar = df[top_similar_ids] return top_similar.to_dict('records')[0] def get_predictions(id, n): top_similar_users = get_similar(id, n) top_similar_users_ids = list([int(x) for x in top_similar_users.keys()]) ratings_for_top_similar = train_df[train_df['user_id'].isin(top_similar_users_ids)] predicted_ratings = {} for article_id in train_df.columns[:-1]: predicted_ratings[article_id] = ratings_for_top_similar[article_id].mean() return predicted_ratings rand_n_users = train_distances.sample(50)['user_id'].values err = 0 for u in tqdm_notebook(rand_n_users): pred = get_predictions(u, 10) err += rmse(test_df[test_df['user_id'] == u][list(pred.keys())].values, pd.DataFrame(pred, index=[0]).values) print(err / len(rand_n_users))
Pour la première approche, nous avons obtenu une erreur égale à 0,855.
Recommandations pour l'utilisateur occasionnel Deuxième voie
La deuxième méthode prend en compte le degré de similitude des utilisateurs. Sa mise en œuvre est presque identique à la première:
def get_predictions(id, n): similar_users = get_similar(u, 10) prediction = {} user_ids = list(similar_users.keys()) user_similarities = [] for user_id in user_ids: user_similarities.append(similar_users[user_id]) predicted_ratings = {} for article_id in train_df.columns[:-1]: prediction_for_article = 0 numerator = 0 denominator = 0 for user_id in user_ids: rating = train_df[train_df['user_id'] == int(user_id)][article_id].values[0] numerator += rating * (1 - similar_users[user_id]) denominator += np.abs(similar_users[user_id]) predicted_ratings[article_id] = numerator / denominator return predicted_ratings err = 0 for u in tqdm_notebook(rand_n_users): pred = get_predictions(u, 10) err += rmse(test_df[test_df['user_id'] == u][list(pred.keys())].values, pd.DataFrame(pred, index=[0]).values) print(err / len(rand_n_users))
Dans ce cas, ils ont obtenu l'erreur 0.866. L'erreur est légèrement plus importante que dans le premier cas.
Recommandations pour le même utilisateur aléatoire Les résultats peuvent être utilisés dans différents scénarios. Par exemple, dans les newsletters de nouveaux articles par mois ou ajoutez sur le site la rubrique «ça pourrait vous intéresser».
Résumé
Dans cet article, j'ai essayé en détail, en utilisant l'exemple d'une tâche réelle, de comprendre comment créer un système de recommandation basé sur le filtrage collaboratif.
L'avantage de cette approche est sa polyvalence - les recommandations ne tiennent pas compte des objets recommandés. Un système peut être utilisé pour les articles de blog et les produits de la boutique en ligne.
Les inconvénients sont les suivants:
- dans le cas d'un grand nombre d'objets pour les recommandations, la matrice utilisateur-objet devient clairsemée et il devient plus difficile de trouver des utilisateurs suffisamment similaires (moins de paires utilisateur-objet correspondent)
- problème de démarrage à froid - il est impossible pour un nouvel utilisateur de trouver des utilisateurs similaires (il existe des stratégies pour contourner cette limitation, mais elles ne sont pas une panacée)
- un système basé sur le filtrage collaboratif a tendance à recommander des objets populaires, car la grande majorité des utilisateurs apprécieront ces objets.
Dans le prochain article, une autre approche sera envisagée - basée sur une analyse des objets eux-mêmes.