许多二分类任务并不是每个类别都有相同数量的数据,存在着数据分布不平衡的情况。
一个常用的例子是成人收入数据集,它涉及到社交关系、教育水平等个人数据,以此来预测成人的收入水平,判断其是否拥有5万美元/年的个人收入。数据集中个人收入低于5万美元的数据比高于5万美元的数据要明显多一些,存在着一定程度的分布不平衡。
针对这一数据集,可以使用很多不平衡分类的相关算法完成分类任务。
在本教程中,您将了解如何为数据分布不平衡的成人收入数据集开发分类模型并对其进行评估。
学习本教程后,您将知道:
- 如何加载和分析数据集,并对如何进行数据预处理和模型选择有一定启发。
- 如何使用一个稳健的测试工具系统地评估机器学习模型的效能。
- 如何拟合最终模型并使用它预测特定情况所对应的类标签。
针对成人收入不平衡分类的具体内容如下:
教程大纲
本教程主要分为了以下五个部分:
- 成人收入数据集介绍
- 数据集分析
- 基础模型和性能评价
- 模型评价
- 对新输入数据进行预测
成人收入数据集介绍
在这个教程中,我们将使用一个数据分布不平衡的机器学习常用数据集,称为“成人收入”或简称“成人”数据集。
该数据集归Ronny Kohavi和Barry Becker所有,取自1994年美国人口普查局的数据,包含有教育水平等个人详细数据,用于预测个人年收入是否超过或低于50000美元。
数据集提供14个输入变量,这些变量数据的类型有标签数据、序列数据、连续数据。变量的完整列表如下:
- 年龄。
- 阶级。
- 最终重量。
- 教育程度。
- 教育年限。
- 婚姻状况。
- 职业。
- 社交。
- 种族。
- 性别。
- 资本收益。
- 资本损失。
- 每周工作小时数。
- 国籍。
总共有48842行数据,3620行含有缺失数据,45222行具有完整的数据,其中缺失值用?
标记。
有'>50K'和'<=50K'两类标签数据,也就是说它是一个二分类任务。同时这些标签数据分布不平衡,'<=50K'类标签比重更大。
考虑到标签数据分布不平衡的情况并不严重,并且两个标签同等重要,本教程采用常见的分类准确度或分类误差来反映此数据集上的相关模型性能。
分析数据集
成人数据集是一个广泛使用的标准机器学习数据集,用于探索和演示许多一般性的或专门为不平衡分类设计的机器学习算法。
首先,下载数据集并将其保存在当前工作目录中,命名为“adult-all.csv”.
接下来让我们考察一下该数据集。文件的前几行如下:
39,State-gov,77516,Bachelors,13,Never-married,Adm-clerical,Not-in-family,White,Male,2174,0,40,United-States,<=50K 50,Self-emp-not-inc,83311,Bachelors,13,Married-civ-spouse,Exec-managerial,Husband,White,Male,0,0,13,United-States,<=50K 38,Private,215646,HS-grad,9,Divorced,Handlers-cleaners,Not-in-family,White,Male,0,0,40,United-States,<=50K 53,Private,234721,11th,7,Married-civ-spouse,Handlers-cleaners,Husband,Black,Male,0,0,40,United-States,<=50K 28,Private,338409,Bachelors,13,Married-civ-spouse,Prof-specialty,Wife,Black,Female,0,0,40,Cuba,<=50K ...
我们可以看到,输入变量包含有连续数据、标签数据以及序号数据,对于标签数据需要进行二进制或者独热编码。同时也需要注意到,目标变量是用字符串表示的,而对于二分类问题,需要用0/1进行标签编码,因此对于占比多的多数标签编码为0,而占比较少的少数标签则编码为1。缺失的数据用?
表示,通常可以估算这些值,也可以直接从数据集中删除这些行。
具体的载入数据集方法可使用read_csv()
这一Pandas包的内置函数,只需要指定文件名、是否读入标题行以及缺失值的对应符号(本数据为?
,缺失值会被处理为NaN
数据):
# define the dataset location filename = 'adult-all.csv' # load the csv file as a data frame dataframe = read_csv(filename, header=None, na_values='?')
成功加载数据集后,我们需要移除缺失数据所在的行,并统计数据大小:
# drop rows with missing dataframe = dataframe.dropna() # summarize the shape of the dataset print(dataframe.shape)
通过Counter
函数我们可以统计数据集分布情况:
# summarize the class distribution target = dataframe.values[:,-1] counter = Counter(target) for k,v in counter.items(): per = v / len(target) * 100 print('Class=%s, Count=%d, Percentage=%.3f%%' % (k, v, per))
上述函数集合到一起,就实现了数据加载和相关统计工作。完整代码如下:
# load and summarize the dataset from pandas import read_csv from collections import Counter # define the dataset location filename = 'adult-all.csv' # load the csv file as a data frame dataframe = read_csv(filename, header=None, na_values='?') # drop rows with missing dataframe = dataframe.dropna() # summarize the shape of the dataset print(dataframe.shape) # summarize the class distribution target = dataframe.values[:,-1] counter = Counter(target) for k,v in counter.items(): per = v / len(target) * 100 print('Class=%s, Count=%d, Percentage=%.3f%%' % (k, v, per))
运行结果如下:
(45222, 15) Class= <=50K, Count=34014, Percentage=75.216% Class= >50K, Count=11208, Percentage=24.784%
在上述代码中,首先我们加载了数据集,并确认了行和列的数量,即45222行,15列(14个输入变量和一个目标变量)。然后分析了数据分布情况,发现数据分布是不平衡的,大约75%的数据都是(<=50K),而只有大约25%的数据是(>50K)。
通过创建直方图,我们可以更直观地看到数据分布情况。具体做法如下:
首先,调用select_dtypes
函数选取数值型数据。
... # select columns with numerical data types num_ix = df.select_dtypes(include=['int64', 'float64']).columns # select a subset of the dataframe with the chosen columns subset = df[num_ix]
然后通过matplotlib
绘图包进行显示。
# create histograms of numeric input variables from pandas import read_csv from matplotlib import pyplot # define the dataset location filename = 'adult-all.csv' # load the csv file as a data frame df = read_csv(filename, header=None, na_values='?') # drop rows with missing df = df.dropna() # select columns with numerical data types num_ix = df.select_dtypes(include=['int64', 'float64']).columns # select a subset of the dataframe with the chosen columns subset = df[num_ix] # create a histogram plot of each numeric variable subset.hist() pyplot.show()
运行上述代码,将为数据集中的六个输入变量分别创建一个直方图。
我们可以看到它们有着不同的分布情况,有些是高斯分布,有些是指数分布或离散分布。同样可以看出,他们的变化范围差异较大。而为了得到较好的算法效果,我们通常需要将数据分布缩放到相同的范围,因此需要进行相应的幂变换。
基础模型和性能评价
k-fold交叉验证方法能够较好估计模型的性能。在这里我们将使用k=10的重复分层k-fold交叉验证方法来评估相关模型,这意味着每个折叠将包含约45222/10=4522
个数据。而分层表示每一个折叠将包含相同的混合比例(即每个折叠中指标数据都具有75%-25%的分布特征)。重复表示评估过程将被多次执行,以避免偶然结果和更好地捕获所选模型的方差,本教程中,我们将重复三次。这意味着将对单个模型进行10×3=30
次拟合和评估,并记录每次运行结果的平均值和标准差。
上述方法可以通过scikit-learn包里面的RepeatedStratifiedKFold
函数实现。
具体代码如下:
# evaluate a model def evaluate_model(X, y, model): # define evaluation procedure cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # evaluate model scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1) return scores
通过evaluate_model()
函数我们实现了获取加载的数据集和定义的模型,使用重复分层k-fold交叉验证对其进行评估,然后返回一个准确度列表。
而如何生成X、Y数据呢?我们可以定义一个函数来加载数据集并对目标列进行编码,然后返回所需数据。具体代码如下:
# load the dataset def load_dataset(full_path): # load the dataset as a numpy array dataframe = read_csv(full_path, header=None, na_values='?') # drop rows with missing dataframe = dataframe.dropna() # split into inputs and outputs last_ix = len(dataframe.columns) - 1 X, y = dataframe.drop(last_ix, axis=1), dataframe[last_ix] # select categorical and numerical features cat_ix = X.select_dtypes(include=['object', 'bool']).columns num_ix = X.select_dtypes(include=['int64', 'float64']).columns # label encode the target variable to have the classes 0 and 1 y = LabelEncoder().fit_transform(y) return X.values, y, cat_ix, num_ix
通过以上步骤,我们就可以使用这个测试工具评估数据集的相关模型了。
为了更好地评估若干模型之间的差距,我们可以通过scikit库里面的DummyClassifier
类建立一个基准模型。相关代码如下:
# define the reference model model = DummyClassifier(strategy='most_frequent') # evaluate the model scores = evaluate_model(X, y, model) # summarize performance print('Mean Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))
上述函数集合到一起,就实现了一个基准算法对于数据集的预测分类和评价。完整代码如下:
# test harness and baseline model evaluation for the adult dataset from collections import Counter from numpy import mean from numpy import std from numpy import hstack from pandas import read_csv from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import cross_val_score from sklearn.model_selection import RepeatedStratifiedKFold from sklearn.dummy import DummyClassifier # load the dataset def load_dataset(full_path): # load the dataset as a numpy array dataframe = read_csv(full_path, header=None, na_values='?') # drop rows with missing dataframe = dataframe.dropna() # split into inputs and outputs last_ix = len(dataframe.columns) - 1 X, y = dataframe.drop(last_ix, axis=1), dataframe[last_ix] # select categorical and numerical features cat_ix = X.select_dtypes(include=['object', 'bool']).columns num_ix = X.select_dtypes(include=['int64', 'float64']).columns # label encode the target variable to have the classes 0 and 1 y = LabelEncoder().fit_transform(y) return X.values, y, cat_ix, num_ix # evaluate a model def evaluate_model(X, y, model): # define evaluation procedure cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # evaluate model scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1) return scores # define the location of the dataset full_path = 'adult-all.csv' # load the dataset X, y, cat_ix, num_ix = load_dataset(full_path) # summarize the loaded dataset print(X.shape, y.shape, Counter(y)) # define the reference model model = DummyClassifier(strategy='most_frequent') # evaluate the model scores = evaluate_model(X, y, model) # summarize performance print('Mean Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))
运行结果如下:
(45222, 14) (45222,) Counter({0: 34014, 1: 11208}) Mean Accuracy: 0.752 (0.000)
通过上述代码,我们首先加载数据并进行预处理。然后通过DummyClassifier()
进行分类,并通过RepeatedStratifiedKFold()
进行评价。可以看到,基准算法达到了约75.2%的准确度。这一结果指出了相关模型的准确度下限;任何平均准确度高于75.2%的模型都可被视为有效模型,而低于75.2%则通常被认为是无效的。