【问题标题】:How to get balanced sample of classes from an imbalanced dataset in sklearn?如何从sklearn中的不平衡数据集中获得平衡的类样本?
【发布时间】:2017-07-27 13:10:58
【问题描述】:

我有一个带有二进制类标签的数据集。我想从我的数据集中提取具有平衡类的样本。我在下面编写的代码给了我不平衡的数据集。

sss = StratifiedShuffleSplit(train_size=5000, n_splits=1, test_size=50000, random_state=0)
for train_index, test_index in sss.split(X, y):
        X_train, X_test = X[train_index], X[test_index]
        y_train, y_test = y[train_index], y[test_index]
        print(itemfreq(y_train))

如您所见,0 类有 2438 个样本,1 类有 2562 个。

[[  0.00000000e+00   2.43800000e+03]
 [  1.00000000e+00   2.56200000e+03]]

我应该如何继续在我的训练集中获得类 1 和类 0 的 2500 个样本。 (测试集也有25000)

【问题讨论】:

  • 你的X的实际大小是多少?

标签: scikit-learn


【解决方案1】:

这是一个围绕pandas.DataFrame.sample 的包装器,它使用weights 参数来执行平衡。它适用于 2 个以上的类和多个功能。

def pd_sample_balanced(X, y, n_times):
  """
  Resample X and y with equal classes in y
  """
  assert y.shape[0] == X.shape[0]
  assert (y.index == X.index).all()
  c = y.value_counts()
  n_samples = c.max() * c.shape[0] * n_times
  weights = (1 / (c / y.shape[0])).reset_index().rename(columns={"index": "y", 0: "w"})
  weights = pd.DataFrame({"y": y}).merge(weights, on="y", how="left").w
  X = X.sample(n=n_samples, weights=weights, random_state=random_state, replace=True)
  y = y[X.index]
  X = X.reset_index(drop=True)
  y = y.reset_index(drop=True)
  return X, y

示例用法

y1 = pd.Series([0, 0, 1, 1, 1, 1, 1, 1, 1, 2])
X1 = pd.DataFrame({"f1": np.arange(len(y1)), "f2": np.arange(len(y1))})

X2, y2 = pd_sample_balanced(X1, y1, 100)

print("before, y:")
print(y1.value_counts())
print("")

print("before, X:")
print(X1.value_counts())
print("")

print("after, y:")
print(y2.value_counts())
print("")

print("after, X:")
print(X2.value_counts())

示例输出

before, y:
1    7
0    2
2    1
dtype: int64

before, X:
f1  f2
9   9     1
8   8     1
7   7     1
6   6     1
5   5     1
4   4     1
3   3     1
2   2     1
1   1     1
0   0     1
dtype: int64

after, y:
2    720
0    691
1    689
Name: 0, dtype: int64

after, X:
f1  f2
9   9     720
1   1     361
0   0     330
7   7     110
6   6     104
4   4      98
3   3      98
8   8      97
5   5      94
2   2      88
dtype: int64

【讨论】:

    【解决方案2】:

    实现数据平衡的方法有很多。

    这是一种不需要 sklearn 的简单方法。

    positives = []
    negatives = []
    for text, label in training_data:
        if label == 1:
            positives.append(text, label)
        else:
            negatives.append(text, label)
    min_rows = min(len(positives), len(negatives))
    
    # Finally, create a balanced data set using an equal number of positive and negative samples.
    balanced_data = positives[0:min_rows]
    balanced_data.extend(negatives[0:min_rows])
    

    如需更高级的技术,请考虑查看imbalanced-learn。它是一个在许多方面都与 sklearn 非常相似的库,但特别专注于处理不平衡的数据。例如,它们提供了一堆用于对数据进行欠采样或过采样的代码。

    【讨论】:

      【解决方案3】:

      由于您没有向我们提供数据集,我正在使用通过make_blobs 生成的模拟数据。您的问题尚不清楚应该有多少测试样本。我已经定义了test_samples = 50000,但您可以更改此值以满足您的需要。

      from sklearn import datasets
      
      train_samples = 5000
      test_samples = 50000
      total_samples = train_samples + train_samples
      X, y = datasets.make_blobs(n_samples=total_samples, centers=2, random_state=0)
      

      以下 sn-p 将数据拆分为训练和测试与平衡类:

      from sklearn.model_selection import StratifiedShuffleSplit    
      
      sss = StratifiedShuffleSplit(train_size=train_samples, n_splits=1, 
                                   test_size=test_samples, random_state=0)  
      
      for train_index, test_index in sss.split(X, y):
          X_train, X_test = X[train_index], X[test_index]
          y_train, y_test = y[train_index], y[test_index]
      

      演示

      In [54]: from scipy import stats
      
      In [55]: stats.itemfreq(y_train)
      Out[55]: 
      array([[   0, 2500],
             [   1, 2500]], dtype=int64)
      
      In [56]: stats.itemfreq(y_test)
      Out[56]: 
      array([[    0, 25000],
             [    1, 25000]], dtype=int64)
      

      编辑

      正如@geompalik 正确指出的那样,如果您的数据集不平衡,StratifiedShuffleSplit 将不会产生平衡拆分。在这种情况下,您可能会发现此功能很有用:

      
      def stratified_split(y, train_ratio):
          
          def split_class(y, label, train_ratio):
              indices = np.flatnonzero(y == label)
              n_train = int(indices.size*train_ratio)
              train_index = indices[:n_train]
              test_index = indices[n_train:]
              return (train_index, test_index)
              
          idx = [split_class(y, label, train_ratio) for label in np.unique(y)]
          train_index = np.concatenate([train for train, _ in idx])
          test_index = np.concatenate([test for _, test in idx])
          return train_index, test_index
      

      演示

      我之前已经生成了模拟数据,其中包含您指定的每个类别的样本数(此处未显示代码)。

      In [153]: y
      Out[153]: array([1, 0, 1, ..., 0, 0, 1])
      
      In [154]: y.size
      Out[154]: 55000
      
      In [155]: train_ratio = float(train_samples)/(train_samples + test_samples)  
      
      In [156]: train_ratio
      Out[156]: 0.09090909090909091
      
      In [157]: train_index, test_index = stratified_split(y, train_ratio)
      
      In [158]: y_train = y[train_index]
      
      In [159]: y_test = y[test_index]
      
      In [160]: y_train.size
      Out[160]: 5000
      
      In [161]: y_test.size
      Out[161]: 50000
      
      In [162]: stats.itemfreq(y_train)
      Out[162]: 
      array([[   0, 2438],
             [   1, 2562]], dtype=int64)
      
      In [163]: stats.itemfreq(y_test)
      Out[163]: 
      array([[    0, 24380],
             [    1, 25620]], dtype=int64)
      

      【讨论】:

      • 我的数据集不平衡。如何从不平衡的数据集中获得平衡的类?
      • 问题略有不同。根据定义,这种策略不会在不平衡数据集中实现平衡分割。
      【解决方案4】:

      问题在于您使用的StratifiedShuffleSplit 方法通过按定义(分层)保留类的百分比来拆分。

      在使用StratifiedShuffleSplit 时实现您想要的一个直接方法是首先对主导类进行二次采样,以便初始数据集平衡然后继续。使用 numpy 这很容易实现。虽然你描述的分裂几乎是平衡的。

      【讨论】:

        猜你喜欢
        • 2017-10-28
        • 1970-01-01
        • 2019-06-22
        • 2018-05-17
        • 2016-12-31
        • 2020-06-07
        • 1970-01-01
        • 2022-11-15
        • 1970-01-01
        相关资源
        最近更新 更多