Scikit-learn сбалансированная подвыборка
Я пытаюсь создать N сбалансированных случайных подвыборки моего большого несбалансированного набора данных. Есть ли способ сделать это просто с помощью scikit-learn/ pandas или я должен сам его реализовать? Любые указатели на код, который делает это?
Эти подвыборки должны быть случайными и могут перекрываться, когда я подаю каждый отдельный классификатор в очень большой ансамбль классификаторов.
В Weka есть инструмент под названием spreadsubsample, есть ли эквивалент в sklearn?
http://wiki.pentaho.com/display/DATAMINING/SpreadSubsample
(Я знаю о взвешивании, но это не то, что я ищу.)
Ответы
Ответ 1
Вот моя первая версия, которая, кажется, работает нормально, не стесняйтесь копировать или делать предложения о том, как она может быть более эффективной (у меня довольно много опыта программирования в целом, но не так долго с python или numpy)
Эта функция создает единую случайную сбалансированную подвыборку.
edit: размер подвыборки теперь отображает классы меньших классов, это, вероятно, должно быть изменено.
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
Для тех, кто пытается сделать вышеописанную работу с Pandas DataFrame, вам нужно сделать пару изменений:
-
Замените строку np.random.shuffle
на
this_xs = this_xs.reindex(np.random.permutation(this_xs.index))
-
Замените строки np.concatenate
на
xs = pd.concat(xs)
ys = pd.Series(data=np.concatenate(ys),name='target')
Ответ 2
В настоящее время существует полноценный пакет Python для устранения несбалансированных данных. Он доступен в виде пакета sklearn-contrib на https://github.com/scikit-learn-contrib/imbalanced-learn
Ответ 3
Версия для pandas Серия:
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
Ответ 4
Этот тип разделения данных не предоставляется среди встроенных методов разделения данных, открытых в sklearn.cross_validation
.
Что похоже на ваши потребности, это sklearn.cross_validation.StratifiedShuffleSplit
, который может генерировать подвыборки любого размера, сохраняя при этом структуру всего набора данных, т.е. тщательно применяя тот же дисбаланс, который находится в вашем основном наборе данных. В то время как это не то, что вы ищете, вы можете использовать код в нем и постоянно менять установленное соотношение до 50/50.
(Это, вероятно, будет очень хорошим вкладом в scikit-learn, если вы с этим позаботитесь.)
Ответ 5
Ниже приведена моя реализация python для создания сбалансированной копии данных.
Предположения:
1. target variable (y) - двоичный класс (0 против 1)
2. 1 - меньшинство.
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]
Ответ 6
Вот версия вышеуказанного кода, которая работает для многоклассовых групп (в моей проверенной группе случаев 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)
Это также возвращает индексы, чтобы они могли использоваться для других наборов данных и отслеживать, как часто использовался каждый набор данных (полезно для обучения)
Ответ 7
Небольшая модификация верхнего ответа mikkom.
Если вы хотите сохранить порядок больших данных класса, т.е. вы не хотите перетасовывать.
Вместо
if len(this_xs) > use_elems:
np.random.shuffle(this_xs)
сделайте это
if len(this_xs) > use_elems:
ratio = len(this_xs) / use_elems
this_xs = this_xs[::ratio]
Ответ 8
Моя подсимплексорная версия, надеюсь, что это поможет
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]]
Ответ 9
Хотя на это уже ответил, я наткнулся на ваш вопрос, ища что-то подобное. После нескольких исследований я считаю, что sklearn.model_selection.StratifiedKFold
можно использовать для этой цели:
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])
Важно, чтобы вы добавили _
, так как skf.split()
используется для создания стратифицированных сгибов для кросс-валидации K-fold, он возвращает два списка индексов: train
(n - 1 / n
elements) и test (1 / n
).
Обратите внимание, что это значение sklearn 0.18. В sklearn 0.17 эта же функция может быть найдена в модуле cross_validation
.
Ответ 10
Краткое, pythonic решение для баланса pandas DataFrame либо путем подвыборки (uspl=True
), либо передискретизацией (uspl=False
), сбалансированной заданной колонкой в этом фрейме данных, который имеет два или более значений.
Для uspl=True
этот код будет принимать произвольный образец без замены размера, равного наименьшему слою из всех слоев. Для uspl=False
этот код возьмет случайную выборку с заменой размера, равной наибольшей страте из всех слоев.
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)
Это будет работать только с pandas DataFrame, но это похоже на общее приложение, и ограничение его на pandas DataFrames значительно сокращает код, насколько я могу судить.
Ответ 11
Просто выберите 100 строк в каждом классе с дубликатами, используя следующий код. activity
- мои занятия (метки набора данных)
balanced_df=Pdf_train.groupby('activity',as_index = False,group_keys=False).apply(lambda s: s.sample(100,replace=True))
Ответ 12
Вот мое решение, которое может быть тесно интегрировано в существующий конвейер sklearn:
from sklearn.model_selection import RepeatedKFold
import numpy as np
class DownsampledRepeatedKFold(RepeatedKFold):
def split(self, X, y=None, groups=None):
for i in range(self.n_repeats):
np.random.seed()
# get index of major class (negative)
idxs_class0 = np.argwhere(y == 0).ravel()
# get index of minor class (positive)
idxs_class1 = np.argwhere(y == 1).ravel()
# get length of minor class
len_minor = len(idxs_class1)
# subsample of major class of size minor class
idxs_class0_downsampled = np.random.choice(idxs_class0, size=len_minor)
original_indx_downsampled = np.hstack((idxs_class0_downsampled, idxs_class1))
np.random.shuffle(original_indx_downsampled)
splits = list(self.cv(n_splits=self.n_splits, shuffle=True).split(original_indx_downsampled))
for train_index, test_index in splits:
yield original_indx_downsampled[train_index], original_indx_downsampled[test_index]
def __init__(self, n_splits=5, n_repeats=10, random_state=None):
self.n_splits = n_splits
super(DownsampledRepeatedKFold, self).__init__(
n_splits=n_splits, n_repeats=n_repeats, random_state=random_state
)
Используйте это как обычно:
for train_index, test_index in DownsampledRepeatedKFold(n_splits=5, n_repeats=10).split(X, y):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
Ответ 13
Я нашел лучшие решения здесь
И это тот, который я считаю самым простым.
dataset = pd.read_csv("data.csv")
X = dataset.iloc[:, 1:12].values
y = dataset.iloc[:, 12].values
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(return_indices=True)
X_rus, y_rus, id_rus = rus.fit_sample(X, y)
тогда вы можете использовать данные X_rus, y_rus