web-dev-qa-db-fra.com

Scikit-learn sous-échantillonnage équilibré

J'essaie de créer N sous-échantillons aléatoires équilibrés de mon grand ensemble de données non équilibré. Existe-t-il un moyen de faire cela simplement avec scikit-learn/pandas ou dois-je le mettre en œuvre moi-même? Des pointeurs sur le code qui fait cela?

Ces sous-échantillons doivent être aléatoires et se chevaucher car je charge chacun d’entre eux de séparer un classificateur dans un très grand ensemble de classificateurs.

Dans Weka, il existe un outil appelé spreadsubsample, existe-t-il un équivalent dans sklearn? http://wiki.pentaho.com/display/DATAMINING/SpreadSubsample

(Je connais la pondération, mais ce n'est pas ce que je recherche.)

34
mikkom

Il existe maintenant un package python complet pour traiter les données déséquilibrées. Il est disponible sous forme de package sklearn-contrib à https://github.com/scikit-learn-contrib/imbalanced-learn

20
eickenberg

Une version pour pandas Series :

import numpy as np

def balanced_subsample(y, size=None):

    subsample = []

    if size is None:
        n_smp = y.value_counts().min()
    else:
        n_smp = int(size / len(y.value_counts().index))

    for label in y.value_counts().index:
        samples = y[y == label].index.values
        index_range = range(samples.shape[0])
        indexes = np.random.choice(index_range, size=n_smp, replace=False)
        subsample += samples[indexes].tolist()

    return subsample
7
gc5

Ce type de fractionnement de données est non fourni par les techniques de fractionnement de données intégrées exposées dans sklearn.cross_validation.

sklearn.cross_validation.StratifiedShuffleSplit semble similaire à vos besoins. Il peut générer des sous-échantillons de toute taille tout en conservant la structure de l'ensemble de données, c'est-à-dire méticuleusement en appliquant le même déséquilibre que celui de votre ensemble de données principal. Bien que ce ne soit pas ce que vous recherchez, vous pourrez peut-être utiliser le code et modifier le ratio imposé à 50/50 toujours.

(Ce serait probablement une très bonne contribution à scikit-learn si vous vous sentez à la hauteur.)

3
eickenberg

Voici une version du code ci-dessus qui fonctionne pour les groupes multiclass (dans mon groupe de cas testé 0, 1, 2, 3, 4)

import numpy as np
def balanced_sample_maker(X, y, sample_size, random_seed=None):
    """ return a balanced data set by sampling all classes with sample_size 
        current version is developed on assumption that the positive
        class is the minority.

    Parameters:
    ===========
    X: {numpy.ndarrray}
    y: {numpy.ndarray}
    """
    uniq_levels = np.unique(y)
    uniq_counts = {level: sum(y == level) for level in uniq_levels}

    if not random_seed is None:
        np.random.seed(random_seed)

    # find observation index of each class levels
    groupby_levels = {}
    for ii, level in enumerate(uniq_levels):
        obs_idx = [idx for idx, val in enumerate(y) if val == level]
        groupby_levels[level] = obs_idx
    # oversampling on observations of each label
    balanced_copy_idx = []
    for gb_level, gb_idx in groupby_levels.iteritems():
        over_sample_idx = np.random.choice(gb_idx, size=sample_size, replace=True).tolist()
        balanced_copy_idx+=over_sample_idx
    np.random.shuffle(balanced_copy_idx)

    return (X[balanced_copy_idx, :], y[balanced_copy_idx], balanced_copy_idx)

Cela renvoie également les index pour qu'ils puissent être utilisés avec d'autres jeux de données et pour garder une trace de la fréquence d'utilisation de chaque jeu de données (utile pour la formation).

2
Kevin Mader

Ci-dessous, mon implémentation en python pour créer une copie de données équilibrée. Hypothèses: 1. la variable cible (y) est une classe binaire (0 vs. 1) 2. 1 est la minorité.

from numpy import unique
from numpy import random 

def balanced_sample_maker(X, y, random_seed=None):
    """ return a balanced data set by oversampling minority class 
        current version is developed on assumption that the positive
        class is the minority.

    Parameters:
    ===========
    X: {numpy.ndarrray}
    y: {numpy.ndarray}
    """
    uniq_levels = unique(y)
    uniq_counts = {level: sum(y == level) for level in uniq_levels}

    if not random_seed is None:
        random.seed(random_seed)

    # find observation index of each class levels
    groupby_levels = {}
    for ii, level in enumerate(uniq_levels):
        obs_idx = [idx for idx, val in enumerate(y) if val == level]
        groupby_levels[level] = obs_idx

    # oversampling on observations of positive label
    sample_size = uniq_counts[0]
    over_sample_idx = random.choice(groupby_levels[1], size=sample_size, replace=True).tolist()
    balanced_copy_idx = groupby_levels[0] + over_sample_idx
    random.shuffle(balanced_copy_idx)

    return X[balanced_copy_idx, :], y[balanced_copy_idx]
2
beingzy

Une légère modification de la réponse de mikkom.

Si vous souhaitez conserver l'ordre des données de classe plus importantes, par exemple. vous ne voulez pas mélanger.

Au lieu de

    if len(this_xs) > use_elems:
        np.random.shuffle(this_xs)

fais ça

        if len(this_xs) > use_elems:
            ratio = len(this_xs) / use_elems
            this_xs = this_xs[::ratio]
1
Bert Kellerman

Sélectionnez simplement 100 lignes dans chaque classe avec des doublons en utilisant le code suivant. activity est mes classes (étiquettes de l'ensemble de données) 

balanced_df=Pdf_train.groupby('activity',as_index = False,group_keys=False).apply(lambda s: s.sample(100,replace=True))
0
javac

Ma version de sous-échantillonnage, espérons que cela aide

def subsample_indices(y, size):
    indices = {}
    target_values = set(y_train)
    for t in target_values:
        indices[t] = [i for i in range(len(y)) if y[i] == t]
    min_len = min(size, min([len(indices[t]) for t in indices]))
    for t in indices:
        if len(indices[t]) > min_len:
            indices[t] = random.sample(indices[t], min_len)
    return indices

x = [1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1]
j = subsample_indices(x, 2)
print j
print [x[t] for t in j[-1]]
print [x[t] for t in j[1]]
0
hernan

Bien qu'il y ait déjà une réponse, je suis tombé sur votre question en cherchant quelque chose de similaire. Après quelques recherches supplémentaires, je crois que sklearn.model_selection.StratifiedKFold peut être utilisé à cette fin:

from sklearn.model_selection import StratifiedKFold

X = samples_array
y = classes_array # subsamples will be stratified according to y
n = desired_number_of_subsamples

skf = StratifiedKFold(n, shuffle = True)

batches = []
for _, batch in skf.split(X, y):
    do_something(X[batch], y[batch])

Il est important que vous ajoutiez le _ car, puisque skf.split() est utilisé pour créer des plis stratifiés pour la validation croisée des plis K, il renvoie deux listes d'index: train (n - 1 / n éléments) et test (1 / n éléments).

Veuillez noter qu’il s’agit du sklearn 0,18 . Dans sklearn 0.17 la même fonction peut être trouvée dans le module cross_validation à la place.

0
kadu

Une solution courte et Pythonic pour équilibrer un DataFrame de pandas par sous-échantillonnage (uspl=True) ou suréchantillonnage (uspl=False), équilibrée par une colonne spécifiée dans cette trame de données ayant deux valeurs ou plus. 

Pour uspl=True, ce code prendra un échantillon aléatoire sans remplacement de taille égale à la plus petite strate de toutes les strates. Pour uspl=False, ce code prendra un échantillon aléatoire avec remplacement de taille égale à la plus grande strate de toutes les strates. 

def balanced_spl_by(df, lblcol, uspl=True):
    datas_l = [ df[df[lblcol]==l].copy() for l in list(set(df[lblcol].values)) ]
    lsz = [f.shape[0] for f in datas_l ]
    return pd.concat([f.sample(n = (min(lsz) if uspl else max(lsz)), replace = (not uspl)).copy() for f in datas_l ], axis=0 ).sample(frac=1) 

Cela ne fonctionnera qu'avec un Pandas DataFrame, mais cela semble être une application courante, et le limiter à Pandas DataFrames raccourcit considérablement le code, à ma connaissance. 

0
Roko Mijic