web-dev-qa-db-fra.com

Sklearn train_test_split; conserver les valeurs uniques des colonnes du jeu d'apprentissage

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. 

Existe-t-il un moyen, dans sklearn, de scinder un jeu de données pour s'assurer que l'ensemble de valeurs uniques d'une colonne spécifique est conservé dans l'ensemble de formation?

Ma solution rudimentaire au problème est la suivante.

  1. Séparez les éléments que/les utilisateurs ont un nombre faible d’évaluations totales.
  2. créer un 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).
  3. combinez les deux pour obtenir un ensemble de formation représentant final

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.

Caveat - Les données contiennent des entrées uniques pour les utilisateurs et les films

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. 

19
Grr

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.

1
serv-inc

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)
 .
 .
 .
0
Mikhail Venkov