imblearn 使用笔记

在做机器学习相关项目时,通常会出现样本数据量不均衡操作,这时可以使用 imblearn 包进行重采样操作,可通过 pip install imbalanced-learn 命令进行安装。

imblearn 包使用过程中,通常输入项 x 多为 2D 的结构。否则会包 ``

不均衡分析

在数据化运营过程中,以下场景会经常产生样本分布不均衡的问题:

  • 异常检测场景 大多数企业中的异常个案都是少量的,比如恶意刷单、黄牛订单、信用卡欺诈、电力窃电、设备故障等,这些数据样本所占的比例通常是整体样本中很少的一部分,以信用卡欺诈为例,刷实体信用卡的欺诈比例一般都在0.1%以内。
  • 客户流失场景 大型企业的流失客户相对于整体客户通常是少量的,尤其对于具有垄断地位的行业巨擘,例如电信、石油、网络运营商等更是如此。
  • 罕见事件的分析 罕见事件与异常检测类似,都属于发生个案较少;但不同点在于异常检测通常都有是预先定义好的规则和逻辑,并且大多数异常事件都对会企业运营造成负面影响,因此针对异常事件的检测和预防非常重要;但罕见事件则无法预判,并且也没有明显的积极和消极影响倾向。例如由于某网络大V无意中转发了企业的一条趣味广告导致用户流量明显提升便属于此类。
  • 发生频率低的事件 这种事件是预期或计划性事件,但是发生频率非常低。例如每年1次的双11盛会一般都会产生较高的销售额,但放到全年来看这一天的销售额占比很可能只有1%不到,尤其对于很少参与活动的公司而言,这种情况更加明显。这种属于典型的低频事件。

抽样类别

抽样是解决样本分布不均衡相对简单且常用的方法,包括过抽样和欠抽样两种。

过抽样

过抽样(也叫上采样、over-sampling)方法通过增加分类中少数类样本的数量来实现样本均衡,最直接的方法是简单复制少数类样本形成多条记录,这种方法的缺点是如果样本特征少而可能导致过拟合的问题;经过改进的过抽样方法通过在少数类中加入随机噪声、干扰数据或通过一定规则产生新的合成样本,例如SMOTE算法。

欠抽样

欠抽样(也叫下采样、under-sampling)方法通过减少分类中多数类样本的样本数量来实现样本均衡,最直接的方法是随机地去掉一些多数类样本来减小多数类的规模,缺点是会丢失多数类样本中的一些重要信息。

总体上,过抽样和欠抽样更适合大数据分布不均衡的情况,尤其是第一种(过抽样)方法应用更加广泛。

实现方式

本文中使用开放的微博4种情绪数据集 simplifyweibo_4_modes.csv 作为样本数据进行数据处理操作,其中所有的预操作如下:

import sys
import os
import pandas as pd

import jieba

from keras_preprocessing.sequence import pad_sequences
# keras 里类似
from preprocessing.text import Tokenizer
# 模型评价工具
from sklearn import metrics

# xgboost / lightgbm 模型
import xgboost as xgb
import lightgbm as lbm

# 多模型投票
from sklearn.ensemble import VotingClassifier
from collections import Counter
# 重采样
from imblearn.under_sampling import RandomUnderSampler
sys.path.extend([os.path.dirname(os.getcwd())])

dataset = pd.read_csv("data/samples/simplifyweibo_4_moods.csv", header=0)
moods = {0: '喜悦', 1: '愤怒', 2: '厌恶', 3: '低落'}
x = dataset['review'].values

def transform(a):
    return moods[a]

# 将数字标签转为文字标签
y = dataset['label'].apply(transform).values

# 进行结巴分词并添加 padding
data_tokenizer = Tokenizer(split=jieba.cut)
data_tokenizer.fit_on_texts(x)
data_seq = data_tokenizer.texts_to_sequences(x)
data_seq = pad_sequences(data_seq, maxlen=200)

1. SMOTE 抽样

print('Original dataset shape %s' % Counter(y))
# 建立 SMOTE模型
smote = SMOTE()
# 对x和y过抽样处理后的数据集,将两份数据集转换为数据框然后合并为一个整体数据框
data_seq, y = smote.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

# 建立 SMOTE + ENN
smote = SMOTEENN()
# 对x和y过抽样处理后的数据集,将两份数据集转换为数据框然后合并为一个整体数据框
data_seq, y = smote.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

# 建立 SMOTE + Tomek
smote = SMOTETomek()
# 对x和y过抽样处理后的数据集,将两份数据集转换为数据框然后合并为一个整体数据框
data_seq, y = smote.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

过抽样方法通过在少数类中加入随机噪声、干扰数据或通过一定规则产生新的合成样本。

2. 随机抽样

print('Original dataset shape %s' % Counter(y))
# 数据样本会以最小样本数在多样本中进行随机采样
rus = RandomUnderSampler()
data_seq, y = rus.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

# 随机选取数据的子集
ros = RandomOverSampler(random_state=0)
data_seq, y = ros.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

经过 RandomUnderSampler 重采样之后,。

3. ADASYN 抽样

from imblearn.over_sampling import ADASYN

print('Original dataset shape %s' % Counter(y))
adasyn = ADASYN()
data_seq, y = adasyn.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

SMOTE 算法与 ADASYN 都是基于同样的算法来合成新的少数类样本

4. 原型生成

from imblearn.under_sampling import ClusterCentroids
 
print('Original dataset shape %s' % Counter(y))
cc = ClusterCentroids(random_state=0)
data_seq, y = cc.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

每一个类别的样本都会用K-Means算法的中心点来进行合成, 而不是随机从原始样本进行抽取.

5. 最近邻算法下采样

应用最近邻算法来编辑(edit)数据集, 找出那些与邻居不太友好的样本然后移除. 对于每一个要进行下采样的样本, 那些不满足一些准则的样本将会被移除; 他们的绝大多数(kind_sel='mode')或者全部(kind_sel='all')的近邻样本都属于同一个类, 这些样本会被保留在数据集中.

from imblearn.under_sampling import EditedNearestNeighbours
from imblearn.under_sampling import RepeatedEditedNearestNeighbours
from imblearn.under_sampling import AllKNN

print('Original dataset shape %s' % Counter(y))
enn = EditedNearestNeighbours(random_state=0)
data_seq, y = enn.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

# 多次执行 EditedNearestNeighbours
renn = RepeatedEditedNearestNeighbours(random_state=0)
data_seq, y = renn.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

# ALLKNN算法在进行每次迭代的时候, 最近邻的数量都在增加
allknn = AllKNN(random_state=0)
data_seq, y = allknn.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

# KMeans + SMOTE
sm = KMeansSMOTE(random_state=0)
data_seq, y = sm.fit_resample(data_seq, y)
print('resampled dataset shape %s' % Counter(y))

构建不均衡样本

from sklearn.datasets import load_iris
from imblearn.datasets import make_imbalance
iris = load_iris()

# 指定数量生成
ratio = {0: 20, 1: 30, 2: 40}
x_imb, y_imb = make_imbalance(iris.data, iris.target, sampling_strategy=ratio)
# Out[37]: [(0, 20), (1, 30), (2, 40)]
sorted(Counter(y_imb).items()) 
 
# 当类别不指定时, 所有的数据集均导入
ratio = {0: 10}
x_imb, y_imb = make_imbalance(iris.data, iris.target, sampling_strategy=ratio)
# Out[38]: [(0, 10), (1, 50), (2, 50)]
sorted(Counter(y_imb).items())
 
# 同样亦可以传入自定义的比例函数
def ratio_multiplier(y):
    multiplier = {0: 0.5, 1: 0.7, 2: 0.95}
    target_stats = Counter(y)
    for key, value in target_stats.items():
        target_stats[key] = int(value * multiplier[key])
    return target_stats
x_imb, y_imb = make_imbalance(iris.data, iris.target, sampling_strategy=ratio_multiplier)
# Out[39]: [(0, 25), (1, 35), (2, 47)]
sorted(Counter(y_imb).items())

常见问题

  1. 安装 imblearn 包之后,默认会更新 sklearn 包,这时候会导致 sklearn2pmml 报如下错误:
Standard output is empty
Standard error:
Apr 15, 2020 9:21:53 AM org.jpmml.sklearn.Main run
INFO: Parsing PKL..
Apr 15, 2020 9:21:53 AM org.jpmml.sklearn.Main run
INFO: Parsed PKL in 17 ms.
Apr 15, 2020 9:21:53 AM org.jpmml.sklearn.Main run
INFO: Converting..
Apr 15, 2020 9:21:53 AM org.jpmml.sklearn.Main run
SEVERE: Failed to convert
java.lang.IllegalArgumentException: The transformer object (Python class sklearn.ensemble._voting.VotingClassifier) is not a supported Transformer
    at org.jpmml.sklearn.CastFunction.apply(CastFunction.java:43)
    at sklearn.pipeline.Pipeline$1.apply(Pipeline.java:121)
    at sklearn.pipeline.Pipeline$1.apply(Pipeline.java:112)
    at com.google.common.collect.Lists$TransformingRandomAccessList.get(Lists.java:599)
    at sklearn.TransformerUtil.getHead(TransformerUtil.java:35)
    at sklearn2pmml.pipeline.PMMLPipeline.encodePMML(PMMLPipeline.java:189)
    at org.jpmml.sklearn.Main.run(Main.java:145)
    at org.jpmml.sklearn.Main.main(Main.java:94)
Caused by: java.lang.ClassCastException: Cannot cast net.razorvine.pickle.objects.ClassDict to sklearn.Transformer
    at java.lang.Class.cast(Class.java:3369)
    at org.jpmml.sklearn.CastFunction.apply(CastFunction.java:41)
    ... 7 more

Exception in thread "main" java.lang.IllegalArgumentException: The transformer object (Python class sklearn.ensemble._voting.VotingClassifier) is not a supported Transformer
    at org.jpmml.sklearn.CastFunction.apply(CastFunction.java:43)
    at sklearn.pipeline.Pipeline$1.apply(Pipeline.java:121)
    at sklearn.pipeline.Pipeline$1.apply(Pipeline.java:112)
    at com.google.common.collect.Lists$TransformingRandomAccessList.get(Lists.java:599)
    at sklearn.TransformerUtil.getHead(TransformerUtil.java:35)
    at sklearn2pmml.pipeline.PMMLPipeline.encodePMML(PMMLPipeline.java:189)
    at org.jpmml.sklearn.Main.run(Main.java:145)
    at org.jpmml.sklearn.Main.main(Main.java:94)
Caused by: java.lang.ClassCastException: Cannot cast net.razorvine.pickle.objects.ClassDict to sklearn.Transformer
    at java.lang.Class.cast(Class.java:3369)
    at org.jpmml.sklearn.CastFunction.apply(CastFunction.java:41)
    ... 7 more

更新 sklearn 版本即可 pip install --upgrade git+https://github.com/jpmml/sklearn2pmml.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容