2016-02-18 35 views
9

Sto utilizzando sklearn per attività di classificazione multipla. Devo dividere tutti i dati in train_set e test_set. Voglio prendere a caso lo stesso numero di campione di ogni classe. In realtà, io divertire questa funzioneCome dividere i dati sul set di allenamento bilanciato e sul set di test su sklearn

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

ma dà dataset sbilanciato! Qualche suggerimento.

+0

se si vuole ancora utilizzare 'cross_validation.train_test_split' e sei su sklearn '0.17' puoi bilanciare allenamento e test, controlla la mia risposta –

+0

Su una nota a margine, per un allenamento non bilanciato impostato con [sklearn.ensemble.RandomForestClassifier] (http://scikit-learn.org/stable /modules/generated/sklearn.ensemble.RandomForestClassifier.html#sklearn.ensemble.RandomForestClassifier) ​​ad esempio, 'class_weight =" balanced "' può essere usato. – shadi

risposta

9

È possibile utilizzare StratifiedShuffleSplit per creare insiemi di dati che caratterizzano la stessa percentuale di classi come quella originale:

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] 
+0

Nota dalla documentazione: StratifiedShuffleSplit è obsoleto dalla versione 0.18: questo modulo verrà rimosso in 0.20. Utilizza invece [sklearn.model_selection.StratifiedShuffleSplit] (http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedShuffleSplit.html#sklearn.model_selection.StratifiedShuffleSplit). – mc2

+0

"* per creare set di dati con la stessa percentuale di classi di quella originale:" * in base a https://github.com/scikit-learn/scikit-learn/issues/8913, questo non è sempre il caso. – gented

6

Anche se il suggerimento di Christian è corretto, tecnicamente train_test_split dovrebbe dare risultati stratificate utilizzando la stratify param.

Così si potrebbe fare:

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

Il trucco è che inizia dalla versione0.17 in sklearn.

Dalla documentazione sul parametro stratify:

stratificare: array-like o Nessuno (di default è None) caso contrario No, i dati è diviso in maniera stratificata, usando questo come la matrice etichette. Nuovo nella versione 0.17: stratificare splitting

+3

ma se le classi non sono bilanciate in Dati (classe1 = 200 campioni, classe2 = 250 campioni, ..) e ho bisogno di prendere (100, 100) per l'allenamento e (50, 50) per il test. Come posso farlo – Jeanne

+1

ci sono altri due parametri in 'train_test_split':' train_size', 'test_size' (e quelli, oltre a rappresentare una proporzione se' float', possono anche essere 'int'). Non l'ho mai provato, ma penso che 'train_size = 100',' test_size = 50' combinato con il parametro 'stratify' dovrebbe funzionare. –

+0

Non l'ho provato, ma se lo fai, dovresti 100 campioni di allenamento che seguono la distribuzione originale e 50 che seguono anche la distribuzione originale. (Cambierò un po 'l'esempio per chiarire, supponiamo class1 = 200 campioni, class2 = 400 campioni), quindi il tuo set di treni avrà 33 esempi da class1 e 67 di class2, e il tuo set di test avrà 18 esempi da class1 e 32 da class2. Per quanto ho capito, la domanda originale sta cercando di ottenere un treno con 50 esempi da class1 e 50 da class2, ma un set di test con 18 esempi da class1 e 32 da class2. –

2

Se le classi non sono equilibrati, ma si desidera la scissione di essere equilibrata, poi la stratificazione non sta per aiutare. Non sembra essere un metodo per fare il campionamento equilibrato sklearn, ma è una specie di facile utilizzo NumPy di ​​base, ad esempio una funzione come questa potrebbe aiutare:

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 

Si noti che se si utilizza questo e assaggiare più punti per classe rispetto ai dati di input, quindi quelli verranno sovracampionati (campione con sostituzione). Di conseguenza, alcuni punti dati appariranno più volte e questo potrebbe avere un effetto sulle misure di accuratezza, ecc. E se alcune classi hanno solo un punto dati, ci sarà un errore. Si può facilmente verificare il numero di punti per classe, per esempio con np.unique(target, return_counts=True)

+0

Mi piace il principio, tuttavia penso che ci sia un problema con l'implementazione corrente che il campionamento casuale possa assegnare campioni identici per addestrare e testare i set. Il campionamento dovrebbe probabilmente raccogliere gli indici del treno e del test da pool separati. – DonSteep

+1

Hai assolutamente ragione e ho cercato di dirlo dicendo "potresti avere punti replicati nei tuoi dati di allenamento e test, che possono rendere le prestazioni del tuo modello eccessivamente ottimistiche", ma ora capisco che il testo potrebbe non essere perfetto, mi dispiace a tale proposito. Modificherò il codice in modo che non ci siano più punti dati condivisi. – antike

0

Questa è la mia implementazione che uso per ottenere gli indici di dati treno/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