ADD: projet PifMoIA

master
Mathilde JEAN 9 months ago
parent 4a4b672506
commit a839d0684b

BIN
.DS_Store vendored

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
Sources/.DS_Store vendored

Binary file not shown.

@ -0,0 +1,347 @@
from ast import literal_eval
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.metrics.pairwise import linear_kernel, cosine_similarity
from surprise import Dataset, Reader, SVD, accuracy
from nltk.stem.snowball import SnowballStemmer
from sklearn.decomposition import PCA
from surprise.model_selection import cross_validate, train_test_split
# Recommandation des films les mieux notés
# On récupère les data des films
md = pd.read_csv('../Databases/movies_metadata.csv')
# Nettoyage des données pour le genre du film
md['genres'] = md['genres'].fillna('[]').apply(literal_eval).apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])
# On récupère le nombre de votes et la note moyenne donnée par les votes
vote_counts = md[md['vote_count'].notnull()]['vote_count'].astype('int')
vote_averages = md[md['vote_average'].notnull()]['vote_average'].astype('int')
C = vote_averages.mean()
# On récupère le 95ème quantile, pour n'avoir que les films qui ont une meilleure note que 95% des films
m = vote_counts.quantile(0.95)
# On nettoie les données de year
md['year'] = pd.to_datetime(md['release_date'], errors='coerce').apply(lambda x: str(x).split('-')[0] if x != np.nan else np.nan)
# On récupère les films qui sont qualifiés par rapport aux exigences précédentes
qualified = md[(md['vote_count'] >= m) & (md['vote_count'].notnull()) & (md['vote_average'].notnull())][['title', 'year', 'vote_count', 'vote_average', 'popularity', 'genres']]
qualified['vote_count'] = qualified['vote_count'].astype('int')
qualified['vote_average'] = qualified['vote_average'].astype('int')
# Méthode pour définir le poids de chaque critère dans le rating, utilisation de la weighted rating formula
def weighted_rating(x):
v = x['vote_count']
R = x['vote_average']
return (v/(v+m) * R) + (m/(m+v) * C)
# On applique le weighted rating aux films
qualified['wr'] = qualified.apply(weighted_rating, axis=1)
# On récupère les 250 films les mieux notés
qualified = qualified.sort_values('wr', ascending=False).head(250)
# On affiche les 15 films les mieux notés
# print(qualified.head(15))
# Recommandation des films les mieux notés d'un certain genre
# Nettoyage des données de genre
s = md.apply(lambda x: pd.Series(x['genres']),axis=1).stack().reset_index(level=1, drop=True)
s.name = 'genre'
gen_md = md.drop('genres', axis=1).join(s)
# On fait la même chose qu'au-dessus, mais en sélectionnant uniquement les films dont le genre est passé en paramètres
def build_chart(genre, percentile=0.85):
df = gen_md[gen_md['genre'] == genre]
vote_counts = df[df['vote_count'].notnull()]['vote_count'].astype('int')
vote_averages = df[df['vote_average'].notnull()]['vote_average'].astype('int')
C = vote_averages.mean()
m = vote_counts.quantile(percentile)
qualified = df[(df['vote_count'] >= m) & (df['vote_count'].notnull()) & (df['vote_average'].notnull())][['title', 'year', 'vote_count', 'vote_average', 'popularity']]
qualified['vote_count'] = qualified['vote_count'].astype('int')
qualified['vote_average'] = qualified['vote_average'].astype('int')
qualified['wr'] = qualified.apply(lambda x: (x['vote_count']/(x['vote_count']+m) * x['vote_average']) + (m/(m+x['vote_count']) * C), axis=1)
qualified = qualified.sort_values('wr', ascending=False).head(250)
return qualified
# Test sur le genre romance, affiche les 15 films de genre 'Romance' les plus aimés
print(build_chart('Romance').head(15))
# Content based recommanders
# On récupère les données et on les nettoie
links_small = pd.read_csv('../Databases/links_small.csv')
links_small = links_small[links_small['tmdbId'].notnull()]['tmdbId'].astype('int')
md = md.drop([19730, 29503, 35587])
# On cast les id en int
md['id'] = md['id'].astype('int')
# On récupère les données des films dont les ids sont dans links
smd = md[md['id'].isin(links_small)]
# Movie description based recommander
# On récupère les taglines et les descriptions des films
smd['tagline'] = smd['tagline'].fillna('')
smd['description'] = smd['overview'] + smd['tagline']
smd['description'] = smd['description'].fillna('')
# Term frequency Inverse document frequency (TFIDF) is a statistical formula to convert text documents
# into vectors based on the relevancy of the word. It is based on the bag of the words model to create
# a matrix containing the information about less relevant and most relevant words in the document.
# On utilise cette formule pour avoir une matrice des mots les plus intéressants pour notre IA
tf = TfidfVectorizer(analyzer='word',ngram_range=(1, 2),min_df=0.0, stop_words='english')
tfidf_matrix = tf.fit_transform(smd['description'])
# On utilise la Cosine Similarity (similarité cosinus), qui sert à déterminer la similarité de 2 vecteurs
# de dimension n en déterminant le cosinus de leur angle. On l'utilise ici avec notre matrice de vecteurs
# de pertinence des mots pour avoir les films les plus similaires en fonction des taglines et descriptions.
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
# On récupère les films les plus similaires grâce à la matrice de similarité calculée précédemment
smd = smd.reset_index()
titles = smd['title']
indices = pd.Series(smd.index, index=smd['title'])
# Méthode pour recommandation en fonction des contents
def get_recommendations(title):
idx = indices[title]
sim_scores = list(enumerate(cosine_sim[idx]))
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
sim_scores = sim_scores[1:31]
movie_indices = [i[0] for i in sim_scores]
return titles.iloc[movie_indices]
#print(get_recommendations('The Dark Knight').head(10))
# Metadata Based Recommander (genre, keywords, cast, crew)
# On récupère les credits (pour cast et crew) et les keywords
credits = pd.read_csv('../Databases/credits.csv')
keywords = pd.read_csv('../Databases/keywords.csv')
# On cast les ids en int
keywords['id'] = keywords['id'].astype('int')
credits['id'] = credits['id'].astype('int')
md['id'] = md['id'].astype('int')
# On merge les databases avec les metadata des films, pour avoir toutes nos données dans un seul dataframe
md = md.merge(credits, on='id')
md = md.merge(keywords, on='id')
# Après avoir merge les tables, on récupère les data qui sont aussi dans links
smd = md[md['id'].isin(links_small)]
# On récupère les données et on les transforme pour avoir ce dont on a besoin ensuite
smd['cast'] = smd['cast'].apply(literal_eval)
smd['crew'] = smd['crew'].apply(literal_eval)
smd['keywords'] = smd['keywords'].apply(literal_eval)
smd['cast_size'] = smd['cast'].apply(lambda x: len(x))
smd['crew_size'] = smd['crew'].apply(lambda x: len(x))
# Méthode pour récupérer le réalisateur, puisque c'est la seule donnée qui nous intéresse dans le crew (c'est
# la seule qui peut avoir une influence notable sur les préférences des utilisateurs).
def get_director(x):
for i in x:
if i['job'] == 'Director':
return i['name']
return np.nan
# On applique la méthode précédente sur nos données pour ne récupérer que les réalisateurs
smd['director'] = smd['crew'].apply(get_director)
# On nettoie les données du cast, et on ne récupère que les 3 acteurs principaux, puisque les autres ne sont pas
# forcément assez important pour changer les préférences et avis des utilisateurs.
smd['cast'] = smd['cast'].apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])
smd['cast'] = smd['cast'].apply(lambda x: x[:3] if len(x) >=3 else x)
# On nettoie les données des keywords pour pouvoir les utiliser
smd['keywords'] = smd['keywords'].apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])
# On enlève les espaces entre les noms et on met tout en lowercase pour pouvoir comparer les données
smd['cast'] = smd['cast'].apply(lambda x: [str.lower(i.replace(" ", "")) for i in x])
# On fait pareil pour le réalisateur, et on le compte 3 fois pour qu'il ait autant de poids que les acteurs principaux
smd['director'] = smd['director'].astype('str').apply(lambda x: str.lower(x.replace(" ", "")))
smd['director'] = smd['director'].apply(lambda x: [x,x, x])
# On s'occupe des keywords, on calcule le nombre d'utilisation des keywords dans notre dataset pour choisir les plus pertinents
s = smd.apply(lambda x: pd.Series(x['keywords']),axis=1).stack().reset_index(level=1, drop=True)
s.name = 'keyword'
# On compte pour chaque value le nombre de fois où elle apparaît
s = s.value_counts()
# On récupère les keywords qui apparaissent plus d'une fois, puisque les autres ne nous intéressent pas dans le cadre d'une comparaison
s = s[s > 1]
# Cet objet sert à uniformiser les données (ex: chiens et chien équivaudront au même mot)
stemmer = SnowballStemmer('english')
# Méthode pour filtrer les keywords (on récupère ceux qui sont dans s)
def filter_keywords(x):
words = []
for i in x:
if i in s:
words.append(i)
return words
# On filtre les keywords, on uniformise les données avec le stemmer, et on met tous les keywords en lowercase et sans espace
smd['keywords'] = smd['keywords'].apply(filter_keywords)
smd['keywords'] = smd['keywords'].apply(lambda x: [stemmer.stem(i) for i in x])
smd['keywords'] = smd['keywords'].apply(lambda x: [str.lower(i.replace(" ", "")) for i in x])
# On mélange toutes les données récupérées pour chaque film
smd['soup'] = smd['keywords'] + smd['cast'] + smd['director'] + smd['genres']
smd['soup'] = smd['soup'].apply(lambda x: ' '.join(x))
# CountVectorizer is a text preprocessing technique commonly used in natural language processing (NLP)
# tasks for converting a collection of text documents into a numerical representation.
# Comme tout à l'heure, on créé une matrice de la fréquence d'apparition dans 'soup' pour pouvoir comparer les films.
count = CountVectorizer(analyzer='word',ngram_range=(1, 2),min_df=0.0, stop_words='english')
count_matrix = count.fit_transform(smd['soup'])
# On créé maintenant la matrice de similarité de la précédente en utilisant la similarité cosinus
cosine_sim = cosine_similarity(count_matrix, count_matrix)
# On récupère les titres des films pour pouvoir afficher le résultat
smd = smd.reset_index()
titles = smd['title']
indices = pd.Series(smd.index, index=smd['title'])
# On réutilise la même méthode, et on attend de nouveaux résultats avec notre nouvelle matrice de similarité
#print(get_recommendations('Shrek').head(10))
# Ratings + Content Recommender
# Dans cette méthode, on utilise à la fois le code pour les recommandations en fonction de la popularité et en fonction du content
def improved_recommendations(title):
idx = indices[title]
sim_scores = list(enumerate(cosine_sim[idx]))
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
sim_scores = sim_scores[1:26]
movie_indices = [i[0] for i in sim_scores]
movies = smd.iloc[movie_indices][['title', 'vote_count', 'vote_average', 'year']]
vote_counts = movies[movies['vote_count'].notnull()]['vote_count'].astype('int')
vote_averages = movies[movies['vote_average'].notnull()]['vote_average'].astype('int')
C = vote_averages.mean()
m = vote_counts.quantile(0.60)
qualified = movies[(movies['vote_count'] >= m) & (movies['vote_count'].notnull()) & (movies['vote_average'].notnull())]
qualified['vote_count'] = qualified['vote_count'].astype('int')
qualified['vote_average'] = qualified['vote_average'].astype('int')
qualified['wr'] = qualified.apply(weighted_rating, axis=1)
qualified = qualified.sort_values('wr', ascending=False).head(10)
return qualified
# On affiche les films recommandés avec l'algorithme prenant en compte tous les paramètres
#print(improved_recommendations('The Dark Knight'))
# Recommendations for users
# On créé un reader pour parser les ratings
reader = Reader()
# On récupère les ratings
ratings = pd.read_csv('../Databases/ratings_small.csv')
# On utilise cette méthode pour classer les movies en fonction des ratings donnés par les utilisateurs
data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)
# On utilise SVD (Singular Value Decomposition)
# Singular value decomposition (SVD) is a method of representing a matrix as a series of linear approximations
# that expose the underlying meaning-structure of the matrix. The goal of SVD is to find the optimal set of
# factors that best predict the outcome.
svd = SVD()
# On train notre modèle pour qu'il arrive à définir la note qu'un utilisateur pourrait donner à un film en fonction des notes qu'il
# a données aux autres films et des notes que les utilisateurs ayant les mêmes goûts que lui ont donné au film en question.
trainset, testset = train_test_split(data, test_size=0.25)
predictions = svd.fit(trainset).test(testset)
#print(predictions)
# Root Mean Square Error
accuracy.rmse(predictions)
# Mean Absolute Error
accuracy.mae(predictions)
# On essaie de predire la note que l'utilisateur 1 va donner au film 302
#print(svd.predict(1, 302, 3))
# Hybrid recommender
# Méthode pour convert les data en int (gestion des data null)
def convert_int(x):
try:
return int(x)
except:
return np.nan
# On récupère les links, avec les données qui nous intéressent
id_map = pd.read_csv('../Databases/links_small.csv')[['movieId', 'tmdbId']]
id_map['tmdbId'] = id_map['tmdbId'].apply(convert_int)
id_map.columns = ['movieId', 'id']
id_map = id_map.merge(smd[['title', 'id']], on='id').set_index('title')
indices_map = id_map.set_index('id')
# Méthode qui utilise plusieurs critères (préférences des utilisateurs et content des films)
def hybrid(userId, title):
idx = indices[title]
tmdbId = id_map.loc[title]['id']
movie_id = id_map.loc[title]['movieId']
sim_scores = list(enumerate(cosine_sim[int(idx)]))
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
sim_scores = sim_scores[1:26]
movie_indices = [i[0] for i in sim_scores]
movies = smd.iloc[movie_indices][['title', 'vote_count', 'vote_average', 'year', 'id']]
movies['est'] = movies['id'].apply(lambda x: svd.predict(userId, indices_map.loc[x]['movieId']).est)
movies = movies.sort_values('est', ascending=False)
return movies.head(10)
# On a des recommandations différentes pour des utilisateurs différents, notre algorithme permet donc une recommandation
# en fonction du user et de ses préférences
# print(hybrid(1, 'Avatar'))
# print(hybrid(500, 'Avatar'))
# L'utilisateur peut rentrer un utilisateur et un titre de film en ligne de commande
print("Rentrer 1 et Avatar pour avoir les recommandations pour l'utilisateur 1 et le film Avatar par exemple")
print("User Id : ")
userId = input()
print("Titre du film : ")
titreFilm = input()
print(hybrid(userId, titreFilm))
Loading…
Cancel
Save