Existe-t-il un moyen d'utiliser sklearn.model_selection.train_test_split
pour conserver toutes les valeurs uniques d'une ou de plusieurs colonnes spécifiques du jeu d'apprentissage?
Laissez-moi vous donner un exemple. Je suis au courant du problème de factorisation matricielle le plus courant, qui consiste à prévoir les classifications de films pour les utilisateurs dans les ensembles de données Netflix Challenge ou Movielens . Maintenant, cette question n'est pas vraiment centrée sur une approche de factorisation matricielle unique, mais parmi l'éventail des possibilités, il existe un groupe qui ne fera des prédictions que pour des combinaisons connues d'utilisateurs et d'éléments.
Ainsi, dans Movielens 100k, par exemple, nous avons 943 utilisateurs uniques et 1682 films uniques. Si nous utilisions train_test_split
même avec un ratio train_size
élevé (disons 0,9), le nombre d'utilisateurs uniques et de films ne serait pas le même. Cela pose un problème car le groupe de méthodes que j'ai mentionné ne pourrait prédire rien d'autre que 0 pour les films ou les utilisateurs pour lesquels il n'avait pas été formé. Voici un exemple de ce que je veux dire.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
ml = pd.read_csv('ml-100k/u.data', sep='\t', names=['User_id', 'Item_id', 'Rating', 'ts'])
ml.head()
User_id Item_id Rating ts
0 196 242 3 881250949
1 186 302 3 891717742
2 22 377 1 878887116
3 244 51 2 880606923
4 166 346 1 886397596
ml.User_id.unique().size
943
ml.Item_id.unique().size
1682
utrain, utest, itrain, itest, rtrain, rtest = train_test_split(ml, train_size=0.9)
np.unique(utrain).size
943
np.unique(itrain).size
1644
Essayez ceci autant de fois que vous le pouvez et vous ne vous retrouverez pas avec 1682 films uniques dans le décor du train. Cela est dû au fait qu'un certain nombre de films n'ont qu'un seul classement dans l'ensemble de données. Heureusement, il n'en va pas de même pour les utilisateurs (le plus petit nombre d'évaluations par un utilisateur est de 20), donc ce n'est pas un problème là-bas. Mais pour avoir un ensemble d’entraînement fonctionnel, nous avons besoin que tous les films uniques figurent dans cet ensemble au moins une fois. De plus, je ne peux pas utiliser le stratify=
kwarg pour train_test_split
car il n'y a pas plus d'une entrée pour tous les utilisateurs ou pour tous les films.
Ma question est la suivante.
Ma solution rudimentaire au problème est la suivante.
train_test_split
sur les données en excluant ces éléments/utilisateurs rarement notés (en veillant à ce que la taille de fractionnement + la taille d'exclusion soient égales à la taille de fractionnement souhaitée).Exemple:
item_counts = ml.groupby(['Item_id']).size()
user_counts = ml.groupby(['User_id']).size()
rare_items = item_counts.loc[item_counts <= 5].index.values
rare_users = user_counts.loc[user_counts <= 5].index.values
rare_items.size
384
rare_users.size
0
# We can ignore users in this example
rare_ratings = ml.loc[ml.Item_id.isin(rare_items)]
rare_ratings.shape[0]
968
ml_less_rare = ml.loc[~ml.Item_id.isin(rare_items)]
items = ml_less_rare.Item_id.values
users = ml_less_rare.User_id.values
ratings = ml_less_rare.Rating.values
# Establish number of items desired from train_test_split
desired_ratio = 0.9
train_size = desired_ratio * ml.shape[0] - rare_ratings.shape[0]
train_ratio = train_size / ml_less_rare.shape[0]
itrain, itest, utrain, utest, rtrain, rtest = train_test_split(items, users, ratings, train_size=train_ratio)
itrain = np.concatenate((itrain, rare_ratings.Item_id.values))
np.unique(itrain).size
1682
utrain = np.concatenate((utrain, rare_ratings.User_id.values))
np.unique(utrain).size
943
rtrain = np.concatenate((rtrain, rare_ratings.Rating.values))
Cette approche fonctionne, mais je dois juste avoir le sentiment qu’il existe un moyen d’obtenir la même chose avec train_test_split
ou une autre méthode de scission de sklearn.
Tandis que l'approche proposée par @serv-inc fonctionnerait pour des données où chaque classe est représentée plus d'une fois. Ce n'est pas le cas avec ces données, ni avec la plupart des ensembles de données de recommandation/classement.
Ce que vous recherchez s'appelle stratification. Heureusement, sklearn
a justement cela. Il suffit de changer la ligne pour
itrain, itest, utrain, utest, rtrain, rtest = train_test_split(
items, users, ratings, train_size=train_ratio, stratify=users)
Si stratify
n'est pas défini, les données sont mélangées de manière aléatoire. Voir http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html
Si [
stratify
n'est pas]None
, les données sont fractionnées de manière stratifiée, en utilisant ceci comme libellé de classe.
Mise à jour de la question mise à jour: il semble que l'insertion d'instances uniques dans l'ensemble de formations ne soit non intégrée à scikit-learn. Vous pourriez abuser de PredefinedSplit
, ou extendStratifiedShuffleSplit
, mais cela pourrait être plus compliqué que de simplement lancer le vôtre.
Vous pouvez peut-être grouper vos données d'entrée sur un film, puis prélever un échantillon, puis combiner tous les échantillons dans un ensemble de données volumineux.
# initialize lists
utrain_all =[]
utest_all =[]
itrain_all = []
itest_all = []
rtrain_all = []
rtest__all = []
grp_ml = ml.groupby('Item_id')
for name, group in grp_ml:
utrain, utest, itrain, itest, rtrain, rtest = train_test_split(group, train_size=0.9)
utrain_all.append(utrain)
utest_all.append(utest)
itrain_all.append(itrain)
.
.
.