machine-learning - prueba - datos train y test
Cómo dividir los datos en un conjunto de entrenamiento equilibrado y un conjunto de pruebas en sklearn (4)
Estoy usando sklearn para la tarea de clasificación múltiple. Necesito dividir todos los datos en train_set y test_set. Quiero tomar al azar el mismo número de muestra de cada clase. En realidad, estoy divirtiendo esta función.
X_train, X_test, y_train, y_test = cross_validation.train_test_split(Data, Target, test_size=0.3, random_state=0)
Pero da datos desbalanceados! Cualquier sugerencia.
Aunque la sugerencia de Christian es correcta, técnicamente train_test_split
debería proporcionarle resultados estratificados utilizando el stratify
stratify.
Para que pudieras hacer:
X_train, X_test, y_train, y_test = cross_validation.train_test_split(Data, Target, test_size=0.3, random_state=0, stratify=Target)
El truco aquí es que comienza a partir de la versión 0.17
en sklearn
.
De la documentación sobre el parámetro stratify
:
stratify: array-like o None (el valor predeterminado es None) Si no es None, los datos se dividen de forma estratificada, utilizando esto como la matriz de etiquetas. Nuevo en la versión 0.17: estratificar la división
Esta es mi implementación que uso para obtener índices de datos de tren / prueba
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
Puede usar StratifiedShuffleSplit para crear conjuntos de datos con el mismo porcentaje de clases que el 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]
Si las clases no están equilibradas pero desea que la división sea equilibrada, entonces la estratificación no ayudará. Parece que no hay un método para realizar un muestreo equilibrado en sklearn, pero es bastante fácil usar un número básico, por ejemplo, una función como esta podría ayudarlo a:
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
Tenga en cuenta que si usa esto y muestra más puntos por clase que en los datos de entrada, entonces se muestrearán (muestreando con reemplazo). Como resultado, algunos puntos de datos aparecerán varias veces y esto puede tener un efecto en las medidas de precisión, etc. Y si alguna clase tiene solo un punto de datos, habrá un error. Puede verificar fácilmente el número de puntos por clase, por ejemplo, con np.unique(target, return_counts=True)