【Pytorch学习笔记】8.训练类别不均衡数据时,如何使用WeightedRandomSampler(权重采样器)

当我们平时处理类别不平衡数据(Imbalanced Data)时,比如当你有一个二分类数据集,其中90%的样本标为阳性,10%的样本为阴性,如果直接拿给模型训练,然后你会发现模型的预测准确率Accuracy永远在某个不到90%的值左右浮动。
当然会这样,因为首先模型学习到的基本是阳性的特征,也就只会判断阳性准一点;其次,再给个还是90%阳性的验证集,模型全预测成阳性都会有九成的准确率。

关于类别不平衡数据的训练

对于类别不平衡数据,在训练模型评估模型时都会有自己的方法。在评估模型时,我们常用混淆矩阵、F1-score、PR曲线等,而不用准确率、ROC曲线(关于ROC、PR曲线的区别可以看看我的这篇文章)。

本文则讨论训练不平衡数据时采用的方法。
为了让模型学习达到更好的效果,通常我们在会让模型在学习时将样本类别变成1:1(二分类的情况)。

这就涉及到样本的重采样,主要分过采样和欠采样:
过采样(over-sampling)对小类的数据样本进行采样来增加小类的数据样本个数;
欠采样(under-sampling)对大类的数据样本进行采样来减少该类数据样本的个数。

给一张,很形象地解释了oversampling和undersampling:
在这里插入图片描述

Pytorch的权重采样器 WeightedRandomSampler

如何实现重采样呢?Pytorch提供了权重采样器 torch.utils.data.WeightedRandomSampler。

粗略看一下文档:
在这里插入图片描述

非常需要解释一下这个权重采样器:
①首先传入一个权重信息weights,这个weights是所有样本(整个待采样数据集)中每个样本的权重。
所以传入的这个向量的长度可以等于数据集的长度。
每个权重值则是你抽选该样本的可能性。这里的权重值大小没有要求所有加和为1。可以预见的是,同一个类别的样本的权重值应当都设为 该类别占比的倒数。比如阳性占比20%,其权重应设为 5 5 5;对应的阴性类样本的权重应为 1 / 80 % = 1.25 1 / 80% = 1.25 1/80=1.25
②第二个参数num_samples表示抽选多少个。
③replacement表示是否放回抽样,一般都要放回,不仅保证数据分布没变,还能让少的那一类被重复抽到,以保证数量与多数类平衡。
④注意这个采样器到时候需要传入Dataloader(),它指导了Dataloader()怎么先对dataset进行采样,然后读取。且一旦传入了这个sampler,Dataloader不能再传入shuffle参数了。

但是它下面给的Example实在是太烂了,很难理解发生了什么。
我自己画了一张图,阐释了WeightedRandomSampler是怎么工作的。

WeightedRandomSampler工作流图

在这里插入图片描述
可以看到sampler和dataset最终都为Dataloader服务的,从原始数据的标签一一标上对应权重传入采样器(还需要设定采样数和是否放回),原始数据则转化成dataset。
sampler和dataset传入Dataloader后,Dataloader就会以dataset为蓝本按sampler指定的规则采样,采好样作为生成器按batch_size输出。

代码复现

我自己根据流图写了个演示代码,可以观察最后输出的一个批次数据中,0、1比例都是平衡的。

(下面代码也可直接看我的GitHub

①生成人工数据

import pandas as pd
import numpy as np

df_y_neg = pd.DataFrame(np.random.randint(0,1,size=900))  # 生成900个0
df_y_pos = pd.DataFrame(np.random.randint(1,2,size=100))  # 生成100个1

df_y = pd.concat([df_y_neg, df_y_pos], ignore_index=True).sample(frac=1).reset_index(drop=True)  # 拼起来打乱,生成标签列y
df_X = pd.DataFrame(np.random.normal(1, 0.1, size=(1000, 10)))  # 生成1000个数据,10个维度
df = pd.concat([df_X, df_y], axis=1)  # 拼起来组成数据集
df.columns = ['x'+str(n) for n in range(10)] + ['y']  # 标上列名
df

在这里插入图片描述

②计算权重

num_pos = df.loc[df['y'] == 1].shape[0]
num_neg = df.loc[df['y'] == 0].shape[0]
pos_weight = (num_pos + num_neg) / num_pos
neg_weight = (num_pos + num_neg) / num_neg

print(num_pos, num_neg)
print(pos_weight, neg_weight)  # 算出权重

在这里插入图片描述
③添加权重列

df['y_weight'] = df['y'].apply(lambda x : pos_weight if x==1 else neg_weight)  # 添加权重列
df

在这里插入图片描述

④定义WeightedRandomSampler抽样器

import torch

data_y_w = torch.tensor(df['y_weight'].to_numpy(), dtype=torch.float)   # 注意一下,DataFrame要to_numpy()转成numpy再传入tensor,不然会报错
num_samples = df.shape[0]   # 一共抽多少样本。可设置为与数据集同一数量,也可以自己设。

# 定义抽样器,传入准备好的权重数组,抽样总数,并选择放回抽样
sampler = torch.utils.data.sampler.WeightedRandomSampler(data_y_w, num_samples, replacement=True)

⑤构建dataset,建立Dataloader

data_features = torch.tensor(df.iloc[:, :-1].values, dtype=torch.float)   # 数据弃掉权重列,传入tensor

data_features_X = data_features[:,:-1]    # 取X
data_features_y = data_features[:, -1].long()  # 取y,标签转成long型

# TensorDataset 可以用来对 tensor 进行打包,类似zip。形式是数据特征+标签。
dataset = torch.utils.data.TensorDataset(data_features_X, data_features_y)

# 建立Dataloader
batch_size = 64
data_iter = torch.utils.data.DataLoader(
    dataset = dataset, sampler = sampler, batch_size = batch_size)

⑥查看数据

# 查看一个批次的数据
X, y = next(iter(data_iter))
y

在这里插入图片描述

训练时的重采样

我们在训练模型时,一般在一个epoch下使用一次Dataloader:

for epoch in range(num_epochs):
  for X, y in data_iter:
    ......

所以每轮epoch的采样到的结果都不同,保证多个epoch下等可能地采到所有数据。

  • 23
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值