导读:

分类问题是机器学习应用中的常见问题,而二分类问题是其中的典型,例如垃圾邮件的识别。本文基于UCI机器学习数据库中的银行营销数据集,从对数据集进行探索,数据预处理和特征工程,到学习模型的评估与选择,较为完整的展示了解决分类问题的大致流程。文中包含了一些常见问题的处理方式,例如缺失值的处理、非数值属性如何编码、如何使用过抽样和欠抽样的方法解决分类问题中正负样本不均衡的问题等等。

作者:llhthinker

欢迎转载,请保留原文链接:http://www.cnblogs.com/llhthinker/p/7101572.html

1. 数据集选取与问题定义

本次实验选取UCI机器学习库中的银行营销数据集(Bank Marketing Data Set: http://archive.ics.uci.edu/ml/datasets/Bank+Marketing )[Moro et al., 2014]. 。这些数据与葡萄牙银行机构的直接营销活动有关。这些直接营销活动是以电话为基础的。通常来说,银行机构的客服人员至少需要联系一次客户来得知客户是否将认购银行的产品(定期存款)。因此,与该数据集对应的任务是分类任务,而分类目标是预测客户是(yes)否(no)认购定期存款(变量y)。

数据集包含四个csv文件:

1) bank-additional-full.csv: 包含所有的样例(41188个)和所有的特征输入(20个),根据时间排序(从2008年5月到2010年9月);

2) bank-additional.csv: 从1)中随机选出10%的样例(4119个);

3) bank-full.csv: 包含所有的样例(41188个)和17个特征输入,根据时间排序。(该数据集是更老的版本,特征输入较少);

4) bank.csv: 从3)中随机选出10%的样例4119个)。

提供小的数据集(bank-additional.csv和bank.csv)是为了能够快速测试一些计算代价较大的机器学习算法(例如SVM)。本次实验将选取较新的数据集,即包含20个特征量的1)和2)。

2. 认识数据

2.1 数据集输入变量与输出变量

数据集的输入变量是20个特征量,分为数值变量(numeric)和分类(categorical)变量。具体描述见数据集网站http://archive.ics.uci.edu/ml/datasets/Bank+Marketing

输出变量为y,即客户是否已经认购定期存款(binary: "yes", "no")。

2.2 原始数据分析

首先载入数据,

然后使用info()函数和describe()函数查看数据集的基本信息。

3. 数据预处理与特征工程

3.1 缺失值处理

从2.2节给出的数据集基本信息可以看出,数值型变量(int64和float64)没有缺失。非数值型变量可能存在unknown值。使用如下代码查看字符型变量unknown值的个数。

缺失值处理通常有如下的方法:

  • 对于unknown值数量较少的变量,包括job和marital,删除这些变量是缺失值(unknown)的行;
  • 如果预计该变量对于学习模型效果影响不大,可以对unknown值赋众数,这里认为变量都对学习模型有较大影响,不采取此法;
  • 可以使用数据完整的行作为训练集,以此来预测缺失值,变量housing,loan,education和default的缺失值采取此法。由于sklearn的模型只能处理数值变量,需要先将分类变量数值化,然后进行预测。本次实验使用随机森林预测缺失值,代码如下:
def fill_unknown(data, bin_attrs, cate_attrs, numeric_attrs):
# fill_attrs = ['education', 'default', 'housing', 'loan']
fill_attrs = []
for i in bin_attrs+cate_attrs:
if data[data[i] == 'unknown']['y'].count() < 500:
# delete col containing unknown
data = data[data[i] != 'unknown']
else:
fill_attrs.append(i) data = encode_cate_attrs(data, cate_attrs)
data = encode_bin_attrs(data, bin_attrs)
data = trans_num_attrs(data, numeric_attrs)
data['y'] = data['y'].map({'no': 0, 'yes': 1}).astype(int)
for i in fill_attrs:
test_data = data[data[i] == 'unknown']
testX = test_data.drop(fill_attrs, axis=1)
train_data = data[data[i] != 'unknown']
trainY = train_data[i]
trainX = train_data.drop(fill_attrs, axis=1)
test_data[i] = train_predict_unknown(trainX, trainY, testX)
data = pd.concat([train_data, test_data]) return data

3.2 分类变量数值化

为了能使分类变量参与模型计算,我们需要将分类变量数值化,也就是编码。分类变量又可以分为二项分类变量、有序分类变量和无序分类变量。不同种类的分类变量编码方式也有区别。

3.2.1 二分类变量编码

根据上文的输入变量描述,可以认为变量default 、housing 和loan 为二分类变量,对其进行0,1编码。代码如下:

def encode_bin_attrs(data, bin_attrs):
for i in bin_attrs:
data.loc[data[i] == 'no', i] = 0
data.loc[data[i] == 'yes', i] = 1
return data

3.2.2 有序分类变量编码

根据上文的输入变量描述,可以认为变量education是有序分类变量,影响大小排序为"illiterate", "basic.4y", "basic.6y", "basic.9y", "high.school", "professional.course", "university.degree", 变量影响由小到大的顺序编码为1、2、3、...,。代码如下:

def encode_edu_attrs(data):
values = ["illiterate", "basic.4y", "basic.6y", "basic.9y",
"high.school", "professional.course", "university.degree"]
levels = range(1,len(values)+1)
dict_levels = dict(zip(values, levels))
for v in values:
data.loc[data['education'] == v, 'education'] = dict_levels[v]
return data

3.2.3 无序分类变量编码

根据上文的输入变量描述,可以认为变量job,marital,contact,month,day_of_week为无序分类变量。需要说明的是,虽然变量month和day_of_week从时间角度是有序的,但是对于目标变量而言是无序的。对于无序分类变量,可以利用哑变量(dummy variables)进行编码。一般的,n个分类需要设置n-1个哑变量。例如,变量marital分为divorced、married、single,使用两个哑变量V1和V2来编码。

marital

V1

V2

divorced

0

0

married

1

0

single

0

1

Python的pandas包提供生成哑变量的函数,故代码如下:

def encode_cate_attrs(data, cate_attrs):
data = encode_edu_attrs(data)
cate_attrs.remove('education')
for i in cate_attrs:
dummies_df = pd.get_dummies(data[i])
dummies_df = dummies_df.rename(columns=lambda x: i+'_'+str(x))
data = pd.concat([data,dummies_df],axis=1)
data = data.drop(i, axis=1)
return data

3.3 数值特征预处理

3.3.1 连续型特征离散化

将连续型特征离散化的一个好处是可以有效地克服数据中隐藏的缺陷: 使模型结果更加稳定。例如,数据中的极端值是影响模型效果的一个重要因素。极端值导致模型参数过高或过低,或导致模型被虚假现象"迷惑",把原来不存在的关系作为重要模式来学习。而离散化,尤其是等距离散,可以有效地减弱极端值和异常值的影响。

通过观察2.2节的原始数据集的统计信息,可以看出变量duration的最大值为4918,而75%分位数为319,远小于最大值,而且该变量的标准差为259,相对也比较大。因此对变量duration进行离散化。具体地,使用pandas.qcut()函数来离散化连续数据,它使用分位数对数据进行划分(分箱: bining),可以得到大小基本相等的箱子(bin),以区间形式表示。然后使用pandas.factorize()函数将区间转为数值。

    data[bining_attr] = pd.qcut(data[bining_attr], bining_num)
data[bining_attr] = pd.factorize(data[bining_attr])[0]+1

3.3.3 规范化

由于不同变量常常使用不同的度量单位,从数值上看它们相差很大,容易使基于距离度量的学习模型更容易受数值较大的变量影响。数据规范化就是将数据压缩到一个范围内,从而使得所有变量的单位影响一致。

    for i in numeric_attrs:
scaler = preprocessing.StandardScaler()
data[i] = scaler.fit_transform(data[i])

3.3.4 持久化预处理后的数据

由于需要训练模型预测unknown值,预处理过程的时间代价比较大。因此将预处理后的数据持久化,保存到文件中,之后的学习模型直接读取文件数据进行训练预测,无须再预处理。

def preprocess_data():
input_data_path = "../data/bank-additional/bank-additional-full.csv"
processed_data_path = '../processed_data/bank-additional-full.csv'
print("Loading data...")
data = pd.read_csv(input_data_path, sep=';')
print("Preprocessing data...")
numeric_attrs = ['age', 'duration', 'campaign', 'pdays', 'previous',
'emp.var.rate', 'cons.price.idx', 'cons.conf.idx',
'euribor3m', 'nr.employed',]
bin_attrs = ['default', 'housing', 'loan']
cate_attrs = ['poutcome', 'education', 'job', 'marital',
'contact', 'month','day_of_week'] data = shuffle(data)
data = fill_unknown(data, bin_attrs, cate_attrs, numeric_attrs)
data.to_csv(processed_data_path, index=False)

需要注意的是,由于原始数据是有序的(以时间为序),读取原始数据后,需要将其随机打乱,变成无序数据集。这里使用sklearn.utils包中的shuffle()函数进行打乱。

一些情况下原始数据维度非常高,维度越高,数据在每个特征维度上的分布就越稀疏,这对机器学习算法基本都是灾难性(维度灾难)。当我们又没有办法挑选出有效的特征时,需要使用PCA等算法来降低数据维度,使得数据可以用于统计学习的算法。但是,如果能够挑选出少而精的特征了,那么PCA等降维算法没有很大必要。在本次实验中,数据集中的特征已经比较有代表性而且并不过多,所以应该不需要降维(实验证明降维确实没有帮助)。关于降维的介绍可以参考之前写的这个博客

总之,数据预处理对于训练机器学习算法非常重要,正所谓“garbage in, garbage out”。

4. 模型的训练与评估

4.1 划分数据集

首先,需要将处理好的数据集划分为3部分,分别是训练集(train set)、交叉检验集(Cross validation set)和测试集(test set)。(另见博客学习模型的评估和选择)。训练集是用于训练模型。交叉检验集用来进行模型的选择,包括选择不同的模型或者同一模型的不同参数,即选择在交叉检验集上的测试结果最优的模型。测试集用于检测最终选择的最优模型的质量。通常,可以按照6:2:2的比例划分,代码如下:

def split_data(data):
data_len = data['y'].count()
split1 = int(data_len*0.6)
split2 = int(data_len*0.8)
train_data = data[:split1]
cv_data = data[split1:split2]
test_data = data[split2:] return train_data, cv_data, test_data

4.2 训练集重采样

对导入的数据集按如下方式进行简单统计可以发现,正样本(y=1)的数量远小于负样本(y=0)的数量,近似等于负样本数量的1/8。

在分类模型中,这种数据不平衡问题会使得学习模型倾向于把样本分为多数类,但是,我们常常更关心少数类的预测情况。在本次分类问题中,分类目标是预测客户是(yes:1)否(no:0)认购定期存款(变量y)。显然,我们更关心有哪些客户认购定期存款。为减弱数据不均衡问题带来的不利影响,在数据层面有两种较简单的方法:过抽样和欠抽样。

  • 过抽样: 抽样处理不平衡数据的最常用方法,基本思想就是通过改变训练数据的分布来消除或减小数据的不平衡。过抽样方法通过增加少数类样本来提高少数类的分类性能 ,最简单的办法是简单复制少数类样本,缺点是可能导致过拟合,没有给少数类增加任何新的信息,泛化能力弱。改进的过抽样方法通过在少数类中加入随机高斯噪声或产生新的合成样本等方法。 
  • 欠抽样: 欠抽样方法通过减少多数类样本来提高少数类的分类性能,最简单的方法是通过随机地去掉一些多数类样本来减小多数类的规模,缺点是会丢失多数类的一些重要信息,不能够充分利用已有的信息。

在本次实验中,采用Smote算法[Chawla et al., 2002]增加新的样本进行过抽样;采用随机地去掉一些多数类样本的方法进行欠抽样。Smote算法的基本思想是对于少数类中每一个样本x,以欧氏距离为标准计算它到少数类样本集中所有样本的距离,得到其k近邻。然后根据样本不平衡比例设置一个采样比例以确定采样倍率N,对于每一个少数类样本x,从其k近邻中随机选择若干个样本,构建新的样本。针对本实验的数据,为防止新生成的数据噪声过大,新的样本只有数值型变量真正是新生成的,其他变量和原样本一致。重采样的代码如下:

def resample_train_data(train_data, n, frac):
numeric_attrs = ['age', 'duration', 'campaign', 'pdays', 'previous',
'emp.var.rate', 'cons.price.idx', 'cons.conf.idx',
'euribor3m', 'nr.employed',]
#numeric_attrs = train_data.drop('y',axis=1).columns
pos_train_data_original = train_data[train_data['y'] == 1]
pos_train_data = train_data[train_data['y'] == 1]
new_count = n * pos_train_data['y'].count()
neg_train_data = train_data[train_data['y'] == 0].sample(frac=frac)
train_list = []
if n != 0:
pos_train_X = pos_train_data[numeric_attrs]
pos_train_X2 = pd.concat([pos_train_data.drop(numeric_attrs, axis=1)] * n)
pos_train_X2.index = range(new_count) s = smote.Smote(pos_train_X.values, N=n, k=3)
pos_train_X = s.over_sampling()
pos_train_X = pd.DataFrame(pos_train_X, columns=numeric_attrs,
index=range(new_count))
pos_train_data = pd.concat([pos_train_X, pos_train_X2], axis=1)
pos_train_data = pd.DataFrame(pos_train_data, columns=pos_train_data_original.columns)
train_list = [pos_train_data, neg_train_data, pos_train_data_original]
else:
train_list = [neg_train_data, pos_train_data_original]
print("Size of positive train data: {} * {}".format(pos_train_data_original['y'].count(), n+1))
print("Size of negative train data: {} * {}".format(neg_train_data['y'].count(), frac))
train_data = pd.concat(train_list, axis=0)
return shuffle(train_data)

4.3 模型的训练与评估

常用的分类模型包括感知机SVM,朴素贝叶斯,决策树,logistic回归,随机森林等等。本次实验选择logistic回归和随机森林在训练集上进行训练,在交叉检验集上进行评估,随机森林的表现更优,所以最终选择随机森林模型在测试集上进行测试。

对于不同的任务,评价一个模型的优劣可能不同。正如4.2节中所言,实验选取的数据集是不平衡的,数据集中负样本0值占数据集总比例高达88.7%,如果我们的模型"预测"所有的目标变量值都为0,那么准确度(Accuracy)应该在88.7%左右。但是,显然,这种"预测"没有意义。所以,我们更倾向于能够预测出正样本(y=1)的模型。因此,实验中将正样本的f1-score作为评价模型优劣的标准(也可以用其他类似的评价指标如AUC)。训练与评估的代码如下:

def train_evaluate(train_data, test_data, classifier, n=1, frac=1.0, threshold = 0.5):
train_data = resample_train_data(train_data, n, frac)
train_X = train_data.drop('y',axis=1)
train_y = train_data['y']
test_X = test_data.drop('y', axis=1)
test_y = test_data['y'] classifier = classifier.fit(train_X, train_y)
prodict_prob_y = classifier.predict_proba(test_X)[:,1]
report = classification_report(test_y, prodict_prob_y > threshold,
target_names = ['no', 'yes'])
prodict_y = (prodict_prob_y > threshold).astype(int)
accuracy = np.mean(test_y.values == prodict_y)
print("Accuracy: {}".format(accuracy))
print(report)
fpr, tpr, thresholds = metrics.roc_curve(test_y, prodict_prob_y)
precision, recall, thresholds = metrics.precision_recall_curve(test_y, prodict_prob_y)
test_auc = metrics.auc(fpr, tpr)
plot_pr(test_auc, precision, recall, "yes") return prodict_y

利用训练评估函数可以进行模型的选择,分别选择Logistic回归模型和随机森林模型,并对其分别调整各自参数的取值,最终选择f1-score最高的随机森林模型。具体地,当将n_estimators设置为400,对正样本进行7倍的过抽样(n=7),不对负样本进行负抽样(frac=1.0),正样本分类的阈值为0.40(threshold),即当预测某样本属于正样本的概率大于0.4时,就将该样本分类为正样本。

forest = RandomForestClassifier(n_estimators=400, oob_score=True)
prodict_y = train_evaluate(train_data, test_data, forest, n=7, frac=1, threshold=0.40)

该模型在交叉检验集上的评估结果如下:

precision-recall曲线如下:

最后,将该模型应用于测试集,测试结果如下:

precision-recall曲线如下:

5. 展望

还可以考虑以下几个方面以提高F1得分:

  • 更细致的特征选择,如派生属性;
  • 采用更好的方法解决数据不平衡问题,如代价敏感学习方法;
  • 更细致的调参;
  • 尝试其他分类模型如神经网络;

完整代码:见github

参考资料:

[Moro et al., 2014] S. Moro, P. Cortez and P. Rita. A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems, Elsevier, 62:22-31, June 2014

[Chawla et al., 2002] N. V. Chawla, L. O. Hall, K. W. Bowyer, and W. P. Kegelmeyer. SMOTE: SMOTE: Synthetic Minority Over-sampling Technique. Journal of Artificial Intelligence Research, 16:321–357, 2002.

http://blog.csdn.net/dream2009gd/article/details/35569343

http://www.cnblogs.com/north-north/p/4360121.html

http://alexkong.net/2013/06/introduction-to-auc-and-roc/

机器学习之分类问题实战(基于UCI Bank Marketing Dataset)的更多相关文章

  1. Python机器学习实践与Kaggle实战(转)

    https://mlnote.wordpress.com/2015/12/16/python%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5 ...

  2. 关于《Selenium3自动化测试实战--基于python语言》

    2016年1月,机缘巧合下我出版了<Selenium2自动化测试实战--基于python语言>这本书,当时写书的原因是,大部分讲Selenium的书并不讲编程语言和单元测试框,如果想在项目 ...

  3. selenium2自动化测试实战--基于Python语言

    自动化测试基础 一. 软件测试分类 1.1 根据项目流程阶段划分软件测试 1.1.1 单元测试 单元测试(或模块测试)是对程序中的单个子程序或具有独立功能的代码段进行测试的过程. 1.1.2 集成测试 ...

  4. 秒懂机器学习---k-近邻算法实战

    秒懂机器学习---k-近邻算法实战 一.总结 一句话总结: k临近算法的核心就是:将训练数据映射成k维空间中的点 1.k临近算法怎么解决实际问题? 构建多维空间:每个特征是一维,合起来组成了一个多维空 ...

  5. 《Selenium2自动化测试实战--基于Python语言》 --即将面市

    发展历程: <selenium_webdriver(python)第一版>   将本博客中的这个系列整理为pdf文档,免费. <selenium_webdriver(python)第 ...

  6. 【阿里云产品公测】云引擎ACE新手实战基于Wordpress

    [阿里云产品公测]云引擎ACE新手实战基于Wordpress 作者:阿里云用户imnpc ACE(Aliyun Cloud Engine) 是一款弹性.分布式的应用托管环境,支持Java.php多种语 ...

  7. Swift项目开发实战-基于分层架构的多版本iPhone计算器-直播公开课

    Swift项目开发实战-基于分层架构的多版本iPhone计算器-直播公开课 本课程采用Q Q群直播方式进行直播,价值99元视频课程免费直播.完整的基于Swift项目实战,手把手教你做一个Swift版i ...

  8. 《Selenium 2自动化测试实战 基于Python语言》中发送最新邮件无内容问题的解决方法

    虫师的<Selenium 2自动化测试实战 基于Python语言>是我自动化测试的启蒙书 也是我推荐的自动化测试入门必备书,但是书中有一处明显的错误,会误导很多读者,这处错误就是第8章自动 ...

  9. 《数据分析实战:基于EXCEL和SPSS系列工具的实践》一1.4 数据分析的流程

    本节书摘来华章计算机<数据分析实战:基于EXCEL和SPSS系列工具的实践>一书中的第1章 ,第1.4节,纪贺元 著 更多章节内容可以访问云栖社区"华章计算机"公众号查 ...

随机推荐

  1. dedecms学习笔记

    终于弄懂了dedecms的架构和原理,然后搭建了人生中的第一个网站.网站名就不说了. dede的后台在dede中,这是后台代码 templets/default中放的是模板 article 里是文章内 ...

  2. 【企业级框架整合】Springmvc+mybatis+restful+bootstrap框架整合

    1. 使用阿里巴巴Druid连接池(高效.功能强大.可扩展性好的数据库连接池.监控数据库访问性能.支持Common-Logging.Log4j和JdkLog,监控数据库访问)2. 提供高并发JMS消息 ...

  3. ACCESS数据库增强器需求及介绍

    目前版本:ver1.0.0.2 现已支持cs文件浏览,高亮显示 针对如下图所示的access数据库,我想导出access数据库的所有或者部分表的表结构,还想对表进行封装,封装如下所示. using S ...

  4. (one) 条件判断的总结

    第一:if语句的一般形式: if(expression)   statement1; statement2; 对于条件判断,我觉得要点在于“条件”(expression),它是一个结果为false或t ...

  5. 用SIM900上传云端数据(原创KEKE)

    依次发送如下几个命令到SIM900A模块:   AT+CGCLASS="B" AT+CGDCONT=1,"IP","CMNET" AT+CG ...

  6. Java反射机制剖析(一)-定义和API

    1.     什么是Java反射机制 Java的反射机制是在程序运行时,能够完全知道任何一个类,及其它的属性和方法,并且能够任意调用一个对象的属性和方法.这种运行时的动态获取就是Java的反射机制.其 ...

  7. 实现分布式队列ZooKeeper的实现

    一.背景 有一些时候,多个团队需要共同完成一个任务,比如,A团队将Hadoop集群计算的结果交给B团队继续计算,B完成了自己任务再交给C团队继续做.这就有点像业务系统的工作流一样,一环一环地传下去,直 ...

  8. JS组件系列——自己动手扩展BootstrapTable的treegrid功能

    前言:上篇  JS组件系列——自己动手封装bootstrap-treegrid组件 博主自己动手封装了下treegrid的功能,但毕竟那个组件只是一个单独针对树形表格做的,适用性还比较有限.关注博主的 ...

  9. asp.net MVC 网站图片防盗链的几种方法

    目录 1. 通过 URL Rewrite Module 组件 2. 通过 nginx 图片防盗链 3.自定义 HttpHandler 处理 4. 通过 MVC 自定义路由规则防盗链 5. 通过 MVC ...

  10. vscode中使用markdown

    vscode中使用markdown vscode 是微软推出一款轻量级的文本编辑工具,类似于sublime,由于其拥有丰富的插件,安装使用也非常简单,所以深受广大程序员的喜爱. markdown 是一 ...