前言
数据不平衡问题在机器学习分类问题中很常见,尤其是涉及到“异常检测"类型的分类。因为异常一般指的相对不常见的现象,因此发生的机率必然要小很多。因此正常类的样本量会远远高于异常类的样本量,一般高达几个数量级。
比如: 疾病相关的样本,正常的样本会远高于疾病的样本,即便是当下流行的COVID-19。
比如kaggle 竞赛的信用卡交易欺诈(credit card fraud),正常交易与欺诈类交易比例大于10000:1。
再比如工业中常见的故障诊断数据,正常运行的时间段会远远高于停机(故障)时间。
开题
首先我们提出一个问题: 为什么数据不平衡会对机器模型产生影响? 原因很直观,因为训练集中的数据如果不平衡,“机器” 会集中解决大多数的数据的问题,而会忽视了少数类的数据。就像少数民族会不占优势。既然是基于大样本训练的机器模型,无法避免地被主要样本带偏。
关键问题来了: 那我们如何让少数类获得同等的地位,然后被模型同等对待呢? 今天我们可以通过一个实战样本来看看有哪些技巧能降低数据不平衡带来的影响。
数据源准备
数据源是NSL-KDD 数据包。数据源来自: https://www.unb.ca/cic/datasets/nsl.html。 简单介绍一下数据源,NSL-KDD是为解决在中KDD'99数据集的某些固有问题而推荐的数据集。尽管该数据集可能无法完美地代表现有的现实网络世界,但是很多论文依然可以用它作有效的基准数据集,以帮助研究人员比较不同的入侵检测方法。
本文数据集来源于github的整理半成品。https://github.com/arjbah/nsl-kdd.git (include the most attack types) 和https://github.com/defcom17/NSL_KDD.git。
数据集比较分散,train_file 和test_file 只包含样本特征和标签值,但是没有表头(header),表头的信息包含在field_name_file 中,另外关于网络攻击类型,分为5个大类,40多个小类,但是我们该测试中只预测5个大类。
数据源略点凌乱,所以我们需要在代码中稍作归类。 代码入场:
# import packages
import pandas as pd
"""
DATASET SOURCE is from https://github.com/arjbah/nsl-kdd.git (include the most attack types)
https://github.com/defcom17/NSL_KDD.git
"""
train_file = 'https://raw.githubusercontent.com/arjbah/nsl-kdd/master/nsl-kdd/KDDTrain%2B.txt'
test_file = 'https://raw.githubusercontent.com/arjbah/nsl-kdd/master/nsl-kdd/KDDTest%2B.txt'
field_name_file = 'https://raw.githubusercontent.com/defcom17/NSL_KDD/master/Field%20Names.csv'
attack_type_file = 'https://raw.githubusercontent.com/arjbah/nsl-kdd/master/training_attack_types.txt'
这里就是常规的pandas 读csv 或txt 操作,仅仅注意一下列表头/列名称的处理。
field_names_df = pd.read_csv(
field_name_file, header=None, names=[
'name', 'data_type']) # 定义dataframe ,并给个column name,方便索引
field_names = field_names_df['name'].tolist()
field_names += ['label', 'label_code'] # 源文件中没有标签名称,以及等级信息
df = pd.read_csv(train_file, header=None, names=field_names)
df_test = pd.read_csv(test_file, header=None, names=field_names)
attack_type_df = pd.read_csv(
attack_type_file, sep=' ', header=None, names=[
'name', 'attack_type'])
attack_type_dict = dict(
zip(attack_type_df['name'].tolist(), attack_type_df['attack_type'].tolist())) # 定义5大类和小类的映射字典,方便替代
df.drop('label_code', axis=1, inplace=True) # 最后一列 既无法作为feature,也不是我们的label,删掉
df_test.drop('label_code', axis=1, inplace=True)
df['label'].replace(attack_type_dict, inplace=True) # 替换label 为5 大类
df_test['label'].replace(attack_type_dict, inplace=True)
数据一览(不平衡分布)
数据已经准备好,我们可以初步浏览一下数据结构。
print(df.info())
结果如下:
Data columns (total 42 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 duration 125973 non-null int64
1 protocol_type 125973 non-null object
2 service 125973 non-null object
3 flag