web-dev-qa-db-fra.com

Comment fractionner des données sur un ensemble d’entraînement équilibré et un ensemble d’essai sur sklearn

J'utilise sklearn pour des tâches de multi-classification. Je dois diviser toutes les données en train_set et test_set. Je veux prendre au hasard le même numéro d’échantillon de chaque classe ..__ En fait, j’amuse cette fonction

X_train, X_test, y_train, y_test = cross_validation.train_test_split(Data, Target, test_size=0.3, random_state=0)

mais cela donne un ensemble de données non équilibré! Toute suggestion.

16
Jeanne

Vous pouvez utiliser StratifiedShuffleSplit pour créer des jeux de données comportant le même pourcentage de classes que l’original:

import numpy as np
from sklearn.cross_validation import StratifiedShuffleSplit
X = np.array([[1, 3], [3, 7], [2, 4], [4, 8]])
y = np.array([0, 1, 0, 1])
stratSplit = StratifiedShuffleSplit(y, 1, test_size=0.5,random_state=42)
StratifiedShuffleSplit(y, n_iter=1, test_size=0.5)
for train_idx,test_idx in stratSplit:
    X_train=X[train_idx]
    y_train=y[train_idx]
print(X_train)
print(y_train)
//[[3 7]
// [2 4]]
//[1 0]
13
Christian Hirsch

Bien que la suggestion de Christian soit correcte, techniquement train_test_split devrait vous donner des résultats stratifiés en utilisant le paramètre stratify.

Alors tu pourrais faire:

X_train, X_test, y_train, y_test = cross_validation.train_test_split(Data, Target, test_size=0.3, random_state=0, stratify=Target)

Le truc ici est que il commence à partir de la version 0.17 dans sklearn.

Dans la documentation sur le paramètre stratify:

stratify: semblable à un tableau ou None (la valeur par défaut est None) Sinon, les données sont fractionnées de manière stratifiée, en utilisant ceci comme tableau d'étiquettes . Nouveau dans la version 0.17: fractionnement par stratification

13
Guiem Bosch

Si les classes ne sont pas équilibrées mais que vous voulez que la division soit équilibrée, alors la stratification ne va pas aider. Il ne semble pas exister de méthode pour effectuer un échantillonnage équilibré dans sklearn, mais il est assez facile d'utiliser numpy de base. Par exemple, une fonction comme celle-ci pourrait vous aider:

def split_balanced(data, target, test_size=0.2):

    classes = np.unique(target)
    # can give test_size as fraction of input data size of number of samples
    if test_size<1:
        n_test = np.round(len(target)*test_size)
    else:
        n_test = test_size
    n_train = max(0,len(target)-n_test)
    n_train_per_class = max(1,int(np.floor(n_train/len(classes))))
    n_test_per_class = max(1,int(np.floor(n_test/len(classes))))

    ixs = []
    for cl in classes:
        if (n_train_per_class+n_test_per_class) > np.sum(target==cl):
            # if data has too few samples for this class, do upsampling
            # split the data to training and testing before sampling so data points won't be
            #  shared among training and test data
            splitix = int(np.ceil(n_train_per_class/(n_train_per_class+n_test_per_class)*np.sum(target==cl)))
            ixs.append(np.r_[np.random.choice(np.nonzero(target==cl)[0][:splitix], n_train_per_class),
                np.random.choice(np.nonzero(target==cl)[0][splitix:], n_test_per_class)])
        else:
            ixs.append(np.random.choice(np.nonzero(target==cl)[0], n_train_per_class+n_test_per_class,
                replace=False))

    # take same num of samples from all classes
    ix_train = np.concatenate([x[:n_train_per_class] for x in ixs])
    ix_test = np.concatenate([x[n_train_per_class:(n_train_per_class+n_test_per_class)] for x in ixs])

    X_train = data[ix_train,:]
    X_test = data[ix_test,:]
    y_train = target[ix_train]
    y_test = target[ix_test]

    return X_train, X_test, y_train, y_test

Notez que si vous utilisez ceci et échantillonnez plus de points par classe que dans les données d'entrée, ceux-ci seront suréchantillonnés (échantillon avec remplacement). En conséquence, certains points de données apparaîtront plusieurs fois, ce qui peut avoir une incidence sur les mesures de précision, etc. Et si une classe ne comporte qu'un seul point de données, il y aura une erreur. Vous pouvez facilement vérifier le nombre de points par classe, par exemple avec np.unique(target, return_counts=True)

2
antike

Ceci est mon implémentation que j'utilise pour obtenir des index de données de train/test

def get_safe_balanced_split(target, trainSize=0.8, getTestIndexes=True, shuffle=False, seed=None):
    classes, counts = np.unique(target, return_counts=True)
    nPerClass = float(len(target))*float(trainSize)/float(len(classes))
    if nPerClass > np.min(counts):
        print("Insufficient data to produce a balanced training data split.")
        print("Classes found %s"%classes)
        print("Classes count %s"%counts)
        ts = float(trainSize*np.min(counts)*len(classes)) / float(len(target))
        print("trainSize is reset from %s to %s"%(trainSize, ts))
        trainSize = ts
        nPerClass = float(len(target))*float(trainSize)/float(len(classes))
    # get number of classes
    nPerClass = int(nPerClass)
    print("Data splitting on %i classes and returning %i per class"%(len(classes),nPerClass ))
    # get indexes
    trainIndexes = []
    for c in classes:
        if seed is not None:
            np.random.seed(seed)
        cIdxs = np.where(target==c)[0]
        cIdxs = np.random.choice(cIdxs, nPerClass, replace=False)
        trainIndexes.extend(cIdxs)
    # get test indexes
    testIndexes = None
    if getTestIndexes:
        testIndexes = list(set(range(len(target))) - set(trainIndexes))
    # shuffle
    if shuffle:
        trainIndexes = random.shuffle(trainIndexes)
        if testIndexes is not None:
            testIndexes = random.shuffle(testIndexes)
    # return indexes
    return trainIndexes, testIndexes
0
Cobry