Подтвердить что ты не робот

Scikit-learn сбалансированная подвыборка

Я пытаюсь создать N сбалансированных случайных подвыборки моего большого несбалансированного набора данных. Есть ли способ сделать это просто с помощью scikit-learn/ pandas или я должен сам его реализовать? Любые указатели на код, который делает это?

Эти подвыборки должны быть случайными и могут перекрываться, когда я подаю каждый отдельный классификатор в очень большой ансамбль классификаторов.

В Weka есть инструмент под названием spreadsubsample, есть ли эквивалент в sklearn? http://wiki.pentaho.com/display/DATAMINING/SpreadSubsample

(Я знаю о взвешивании, но это не то, что я ищу.)

4b9b3361

Ответ 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