python - forest - sklearn adaboost
Scikit-learn subsampling equilibrado (10)
Estoy intentando crear N subgrupos aleatorios equilibrados de mi gran conjunto de datos desequilibrado. ¿Hay alguna forma de hacerlo simplemente con scikit-learn / pandas o tengo que implementarlo yo mismo? ¿Algún puntero a código que haga esto?
Estas submuestras deben ser aleatorias y pueden superponerse a medida que alimente cada una de ellas para separar el clasificador en un conjunto muy grande de clasificadores.
En Weka hay una herramienta llamada spreadsubsample, ¿hay equivalente en sklearn? http://wiki.pentaho.com/display/DATAMINING/SpreadSubsample
(Sé sobre el peso, pero eso no es lo que estoy buscando).
A continuación se muestra mi implementación de Python para crear una copia de datos equilibrada. Supuestos: 1. la variable objetivo (y) es una clase binaria (0 vs. 1) 2. 1 es la minoría.
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]
Ahora existe un paquete Python completo para tratar los datos desequilibrados. Está disponible como un paquete sklearn-contrib en github.com/scikit-learn-contrib/imbalanced-learn
Aquí está mi primera versión que parece estar funcionando bien, siéntase libre de copiar o hacer sugerencias sobre cómo podría ser más eficiente (tengo una experiencia bastante larga con la programación en general, pero no tanto con python o numpy)
Esta función crea una única submuestra aleatoria equilibrada.
edición: el tamaño de la submuestra ahora muestra muestras de clases minoritarias, esto probablemente debería cambiarse.
def balanced_subsample(x,y,subsample_size=1.0):
class_xs = []
min_elems = None
for yi in np.unique(y):
elems = x[(y == yi)]
class_xs.append((yi, elems))
if min_elems == None or elems.shape[0] < min_elems:
min_elems = elems.shape[0]
use_elems = min_elems
if subsample_size < 1:
use_elems = int(min_elems*subsample_size)
xs = []
ys = []
for ci,this_xs in class_xs:
if len(this_xs) > use_elems:
np.random.shuffle(this_xs)
x_ = this_xs[:use_elems]
y_ = np.empty(use_elems)
y_.fill(ci)
xs.append(x_)
ys.append(y_)
xs = np.concatenate(xs)
ys = np.concatenate(ys)
return xs,ys
Para cualquiera que intente hacer que el trabajo anterior funcione con un DataFrame de Pandas, necesita hacer un par de cambios:
Reemplace la línea
np.random.shuffle
conthis_xs = this_xs.reindex(np.random.permutation(this_xs.index))
Reemplace las líneas
np.concatenate
conxs = pd.concat(xs) ys = pd.Series(data=np.concatenate(ys),name=''target'')
Aquí hay una versión del código anterior que funciona para grupos multiclase (en mi grupo de casos comprobados 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)
Esto también devuelve los índices para que se puedan usar para otros conjuntos de datos y para realizar un seguimiento de la frecuencia con la que se usó cada conjunto de datos (útil para la capacitación)
Aunque ya está respondida, me topé con tu pregunta buscando algo similar. Después de un poco más de investigación, creo que sklearn.model_selection.StratifiedKFold
se puede usar para este propósito:
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])
Es importante que agregue _
porque dado que skf.split()
se usa para crear pliegues estratificados para la validación cruzada K-fold, devuelve dos listas de índices: train
( n - 1 / n
elementos) y prueba ( 1 / n
elementos).
Tenga en cuenta que esto es a partir de 0.18 sklearn . En sklearn 0.17, la misma función se puede encontrar en el módulo cross_validation
.
Este tipo de división de datos no se proporciona entre las técnicas de división de datos incorporadas expuestas en sklearn.cross_validation
.
Lo que parece similar a sus necesidades es sklearn.cross_validation.StratifiedShuffleSplit
, que puede generar submuestras de cualquier tamaño al tiempo que conserva la estructura de todo el conjunto de datos, es decir, aplica meticulosamente el mismo desequilibrio que se encuentra en su conjunto de datos principal. Si bien esto no es lo que está buscando, es posible que pueda usar el código y cambiar la proporción impuesta a 50/50 siempre.
(Esto probablemente sería una muy buena contribución a scikit-learn si te sientes capaz de hacerlo).
Mi versión subsampler espero que esto ayude
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]]
Una ligera modificación a la respuesta superior por parte de mikkom.
Si desea conservar el orden de los datos de clase más grandes, es decir. no quieres barajar
En lugar de
if len(this_xs) > use_elems:
np.random.shuffle(this_xs)
hacer esto
if len(this_xs) > use_elems:
ratio = len(this_xs) / use_elems
this_xs = this_xs[::ratio]
Una solución corta y pythonic para equilibrar un marco de datos de pandas mediante un submuestreo ( uspl=True
) o un uspl=False
( uspl=False
), balanceado por una columna específica en ese marco de datos que tiene dos o más valores.
Para uspl=True
, este código tomará una muestra aleatoria sin reemplazo de tamaño igual al estrato más pequeño de todos los estratos. Para uspl=False
, este código tomará una muestra aleatoria con reemplazo de tamaño igual al estrato más grande de todos los estratos.
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)
Esto solo funcionará con Pandas DataFrame, pero esa parece ser una aplicación común, y restringirla a Pandas DataFrames acorta significativamente el código hasta donde puedo decir.
Una versión para 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