一、决策树与随机森林
(一)决策树
1.算法概述
决策树(Decision Tree)是一种非参数的有监督学习方法,它能够从一系列有特征和标签的数据中总结出决策规则,并用树状图的结构来呈现这些规则,以解决分类和回归问题。决策树算法容易理解,适用各种数据,在解决各种问题时都有良好表现,尤其是以树模型为核心的各种集成算法,在各个行业和领域都有广泛的应用。
决策树优点:可以对复杂和非线性的数据建模。缺点:结果不易理解。适用数据类型:数值型和标称型数据。
决策树的一般方法:(1) 收集数据:采用任意方法收集数据。(2) 准备数据:需要数值型的数据,标称型数据应该映射成二值型数据。(3) 分析数据:绘出数据的二维可视化显示结果,以字典方式生成树。(4) 训练算法:大部分时间都花费在叶节点树模型的构建上。(5) 测试算法:使用测试数据上的R2值来分析模型的效果。(6) 使用算法:使用训练出的树做预测,预测结果还可以用来做很多事情。
(二)随机森林
1.算法概述
集成学习(ensemble learning)是时下非常流行的机器学习算法,它本身不是一个单独的机器学习算法,而是通过在数据上构建多个模型,集成所有模型的建模结果。基本上所有的机器学习领域都可以看到集成学习的身影,在现实中集成学习也有相当大的作用,它可以用来做市场营销模拟的建模,统计客户来源,保留和流失,也可用来预测疾病的风险和病患者的易感性。在现在的各种算法竞赛中,随机森林,梯度提升树(GBDT),Xgboost等集成算法的身影也随处可见,可见其效果之好,应用之广。
随机森林是非常具有代表性的Bagging集成算法,它的所有基评估器都是决策树,分类树组成的森林就叫做随机森林分类器,回归树所集成的森林就叫做随机森林回归器。
(三)实战:使用决策树和随机森林预测员工离职率
我们的任务是帮助人事部门理解员工为何离职,预测一个员工离职的可能性。数据来源:
https://www.kaggle.com/ludobenistant/hr-analytics
(1)收集、准备数据
# 引入工具包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as matplot
import seaborn as sns
%matplotlib inline
# 读入数据到Pandas Dataframe "df"
df = pd.read_csv('HR_comma_sep.csv', index_col=None)
(2)分析数据
观察特征数据可知,sales和salary特征的数据为String类型,我们将其转换成数值类型。
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report, precision_score, recall_score, confusion_matrix, precision_recall_curve
# 将string类型转换为整数类型
df["department"] = df["department"].astype('category').cat.codes
df["salary"] = df["salary"].astype('category').cat.codes
df.head()
(3)训练、测试算法
# 产生X, y
target_name = 'turnover'
X = df.drop('turnover', axis=1)
y = df[target_name]
# 将数据分为训练和测试数据集
# 注意参数 stratify = y 意味着在产生训练和测试数据中, 离职的员工的百分比等于原来总的数据中的离职的员工的百分比
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.15, random_state=123, stratify=y)
from sklearn.metrics import roc_auc_score
from sklearn.metrics import classification_report
from sklearn.ensemble import RandomForestClassifier
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
# 决策树
dtree = tree.DecisionTreeClassifier(
criterion='entropy',
#max_depth=3, # 定义树的深度, 可以用来防止过拟合
min_weight_fraction_leaf=0.01 # 定义叶子节点最少需要包含多少个样本(使用百分比表达), 防止过拟合
)
dtree = dtree.fit(X_train,y_train)
print ("\n\n ---决策树---")
dt_roc_auc = roc_auc_score(y_test, dtree.predict(X_test))
print ("决策树 AUC = %2.2f" % dt_roc_auc)
print(classification_report(y_test, dtree.predict(X_test)))
# 随机森林
rf = RandomForestClassifier(
criterion='entropy',
n_estimators=1000,
max_depth=None, # 定义树的深度, 可以用来防止过拟合
min_samples_split=10, # 定义至少多少个样本的情况下才继续分叉
#min_weight_fraction_leaf=0.02 # 定义叶子节点最少需要包含多少个样本(使用百分比表达), 防止过拟合
)
rf.fit(X_train, y_train)
print ("\n\n ---随机森林---")
rf_roc_auc = roc_auc_score(y_test, rf.predict(X_test))
print ("随机森林 AUC = %2.2f" % rf_roc_auc)
print(classification_report(y_test, rf.predict(X_test)))
---决策树---
决策树 AUC = 0.93
precision recall f1-score support
0 0.97 0.98 0.97 1714
1 0.93 0.89 0.91 536
accuracy 0.96 2250
macro avg 0.95 0.93 0.94 2250
weighted avg 0.96 0.96 0.96 2250
---随机森林---
随机森林 AUC = 0.97
precision recall f1-score support
0 0.98 1.00 0.99 1714
1 0.99 0.94 0.96 536
accuracy 0.98 2250
macro avg 0.99 0.97 0.98 2250
weighted avg 0.98 0.98 0.98 2250
# ROC 图
from sklearn.metrics import roc_curve
rf_fpr, rf_tpr, rf_thresholds = roc_curve(y_test, rf.predict_proba(X_test)[:,1])
dt_fpr, dt_tpr, dt_thresholds = roc_curve(y_test, dtree.predict_proba(X_test)[:,1])
plt.figure()
# 随机森林 ROC
plt.plot(rf_fpr, rf_tpr, label='Random Forest (area = %0.2f)' % rf_roc_auc)
# 决策树 ROC
plt.plot(dt_fpr, dt_tpr, label='Decision Tree (area = %0.2f)' % dt_roc_auc)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Graph')
plt.legend(loc="lower right")
plt.show()
还可以通过决策树分析不同的特征的重要性。
## 画出决策树特征的重要性 ##
importances = rf.feature_importances_
feat_names = df.drop(['turnover'],axis=1).columns
indices = np.argsort(importances)[::-1]
plt.figure(figsize=(12,6))
plt.title("Feature importances by DecisionTreeClassifier")
plt.bar(range(len(indices)), importances[indices], color='lightblue', align="center")
plt.step(range(len(indices)), np.cumsum(importances[indices]), where='mid', label='Cumulative')
plt.xticks(range(len(indices)), feat_names[indices], rotation='vertical',fontsize=14)
plt.xlim([-1, len(indices)])
plt.show()
## 画出随机森林的特征的重要性 ##
importances = rf.feature_importances_
feat_names = df.drop(['turnover'],axis=1).columns
indices = np.argsort(importances)[::-1]
plt.figure(figsize=(12,6))
plt.title("Feature importances by Random Forest")
plt.bar(range(len(indices)), importances[indices], color='lightblue', align="center")
plt.step(range(len(indices)), np.cumsum(importances[indices]), where='mid', label='Cumulative')
plt.xticks(range(len(indices)), feat_names[indices], rotation='vertical',fontsize=14)
plt.xlim([-1, len(indices)])
plt.show()
(四)作业:使用决策树和随机森林预测糖尿病
作业使用的是皮马印第安人糖尿病数据集,来源:
https://www.kaggle.com/uciml/pima-indians-diabetes-database#diabetes.csv
1. 作业要求:
(1)创建新的决策树,限定树的最大深度,减少过拟合
参数criterion:
为了要将表格转化为一棵树,决策树需要找出最佳节点和最佳的分枝方法,对分类树来说,衡量这个“最佳”的指标叫做“不纯度”。通常来说,不纯度越低,决策树对训练集的拟合越好。现在使用的决策树算法在分枝方法上的核心大多是围绕在对某个不纯度相关指标的最优化上。不纯度基于节点来计算,树中的每个节点都会有一个不纯度,并且子节点的不纯度一定是低于父节点的,也就是说,在同一棵决策树上,叶子节点的不纯度一定是最低的。Criterion这个参数正是用来决定不纯度的计算方法的。sklearn提供了两种选择:
a. 输入”entropy“,使用信息熵(Entropy)
b. 输入”gini“,使用基尼系数(Gini Impurity)
比起基尼系数,信息熵对不纯度更加敏感,对不纯度的惩罚最强。但是在实际使用中,信息熵和基尼系数的效果基本相同。信息熵的计算比基尼系数缓慢一些,因为基尼系数的计算不涉及对数。另外,因为信息熵对不纯度更加敏感,所以信息熵作为指标时,决策树的生长会更加“精细”,因此对于高维数据或者噪音很多的数据,信息熵很容易过拟合,基尼系数在这种情况下效果往往比较好。当模型拟合程度不足的时候,即当模型在训练集和测试集上都表现不太好的时候,使用信息熵。当然,这些不是绝对的。
在不加限制的情况下,一棵决策树会生长到衡量不纯度的指标最优,或者没有更多的特征可用为止。这样的决策树往往会过拟合,这就是说,它会在训练集上表现很好,在测试集上却表现糟糕。我们收集的样本数据不可能和整体的状况完全一致,因此当一棵决策树对训练数据有了过于优秀的解释性,它找出的规则必然包含了训练样本中的噪声,并使它对未知数据的拟合程度不足。为了让决策树有更好的泛化性,我们要对决策树进行剪枝。剪枝策略对决策树的影响巨大,正确的剪枝策略是优化决策树算法的核心。
参数max_depth:
限制树的最大深度,超过设定深度的树枝全部剪掉。这是用得最广泛的剪枝参数,在高维度低样本量时非常有效。决策树多生长一层,对样本量的需求会增加一倍,所以限制树深度能够有效地限制过拟合。在集成算法中也非常实用。实际使用时,建议从=3开始尝试,看看拟合的效果再决定是否增加设定深度。
参数min_samples_leaf & min_samples_split:
min_samples_leaf限定,一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生,或者,分枝会朝着满足每个子节点都包含min_samples_leaf个样本的方向去发生一般搭配max_depth使用,在回归树中有神奇的效果,可以让模型变得更加平滑。这个参数的数量设置得太小会引起过拟合,设置得太大就会阻止模型学习数据。一般来说,建议从=5开始使用。如果叶节点中含有的样本量变化很大,建议输入浮点数作为样本量的百分比来使用。同时,这个参数可以保证每个叶子的最小尺寸,可以在回归问题中避免低方差,过拟合的叶子节点出现。对于类别不多的分类问题,=1通常就是最佳选择。
参数class_weight & min_weight_fraction_leaf:
完成样本标签平衡的参数。样本不平衡是指在一组数据集中,标签的一类天生占有很大的比例。比如说,在银行要判断“一个办了信用卡的人是否会违约”,就是是vs否(1%:99%)的比例。这种分类状况下,即便模型什么也不做,全把结果预测成“否”,正确率也能有99%。因此我们要使用class_weight参数对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重。
有了权重之后,样本量就不再是单纯地记录数目,而是受输入的权重影响了,因此这时候剪枝,就需要搭配min_weight_fraction_leaf这个基于权重的剪枝参数来使用。另请注意,基于权重的剪枝参数(例如min_weight_fraction_leaf)将比不知道样本权重的标准(比如min_samples_leaf)更少偏向主导类。如果样本是加权的,则使用基于权重的预修剪标准来更容易优化树结构,这确保叶节点至少包含样本权重的总和的一小部分。
使用确定超参数的曲线来进行判断max_depth的大小,继续使用我们已经训练好的决策树模型clf。超参数的学习曲线,是一条以超参数的取值为横坐标,模型的度量指标为纵坐标的曲线,它是用来衡量不同超参数取值下模型的表现的线。在我们建好的决策树里,我们的模型度量指标就是score。
test = []
for i in range(10):
clf = DecisionTreeClassifier(max_depth=i+1, criterion="entropy")
clf = clf.fit(X_train, y_train)
score = clf.score(X_test, y_test)
test.append(score)
plt.plot(range(1,11),test,color="red",label="max_depth")
plt.legend()
plt.show()
由图可知,选择4作为max_depth的参数。
# 创建新的决策树, 限定树的最大深度, 减少过拟合
clf = DecisionTreeClassifier(criterion='entropy',
max_depth=4,
)
# 训练模型
clf = clf.fit(X_train, y_train)
# 预测
y_pred = clf.predict(X_test)
# 模型的性能
print("Accuracy:",metrics.accuracy_score(y_test, y_pred))
Accuracy: 0.7835497835497836
(2)随机森林, 通过调整参数来获取更好的结果
n_estimators参数:这是森林中树木的数量,即基评估器的数量。这个参数对随机森林模型的精确性影响是单调的,n_estimators越大,模型的效果往往越好。但是相应的,任何模型都有决策边界,n_estimators达到一定的程度之后,随机森林的精确性往往不在上升或开始波动,并且,n_estimators越大,需要的计算量和内存也越大,训练的时间也会越来越长。对于这个参数,我们是渴望在训练难度和模型效果之间取得平衡。n_estimators的默认值10。
superpa = []
for i in range(200):
rfc = RandomForestClassifier(n_estimators=i+1,criterion='entropy')
rfc = rfc.fit(X_train, y_train)
score = rfc.score(X_test, y_test)
superpa.append(score)
print(max(superpa),superpa.index(max(superpa)))
plt.figure(figsize=[20,5])
plt.plot(range(1,201),superpa)
plt.show()
0.8225108225108225 57
from sklearn.ensemble import RandomForestClassifier
# 随机森林, 通过调整参数来获取更好的结果
rf = RandomForestClassifier(
criterion='entropy',
n_estimators=57,
)
# 训练模型
rf.fit(X_train, y_train)
# 做预测
y_pred = rf.predict(X_test)
# 模型的准确率
print("Accuracy:",metrics.accuracy_score(y_test, y_pred))
二、K-means
(一)算法概述
聚类算法又叫做“无监督分类”,其目的是将数据划分成有意义或有用的组(或簇)。这种划分可以基于我们的业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。比如在商业中,如果我们手头有大量的当前和潜在客户的信息,我们可以使用聚类将客户划分为若干组,以便进一步分析和开展营销活动,最有名的客户价值判断模型RFM,就常常和聚类分析共同使用。再比如,聚类可以用于降维和矢量量化(vectorquantization),可以将高维特征压缩到一列当中,常常用于图像,声音,视频等非结构化数据,可以大幅度压缩数据量。
作为聚类算法的典型代表,KMeans可以说是最简单的聚类算法。KMeans算法将一组N个样本的特征矩阵X划分为K个无交集的簇,直观上来看是簇是一组一组聚集在一起的数据,在一个簇中的数据就认为是同一类。簇就是聚类的结果表现。簇中所有数据的均值通常被称为这个簇的“质心”(centroids)。在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。KMeans的工作流程是这样的。首先,随机确定k个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。
K-means的优点:容易实现。缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。适用数据类型:数值型数据。
K-means聚类的一般流程:(1) 收集数据:使用任意方法。(2) 准备数据:需要数值型数据来计算距离,也可以将标称型数据映射为二值型数据再用于距离计算。(3) 分析数据:使用任意方法。(4) 训练算法:不适用于无监督学习,即无监督学习没有训练过程。(5) 测试算法:应用聚类算法、观察结果。可以使用量化的误差指标如误差平方和(后面会介绍)来评价算法的结果。(6) 使用算法:可以用于所希望的任何应用。通常情况下,簇质心可以代表整个簇的数据来做出决策。
(二)实战:使用K-means图像压缩
在图像压缩问题中,K均值聚类算法会把类似的颜色分别放在K个簇中——也就是说,每个簇的颜色都变成了一种。因此,我们只需要保留每个像素的标签(表明该像素在哪个簇中),以及每个簇的颜色编码即可完成图像的压缩。
from pylab import imread,imshow,figure,show,subplot
from numpy import reshape,uint8,flipud
from sklearn.cluster import KMeans
from copy import deepcopy
%matplotlib inline
img = imread('sample.jpeg') # img: 图片的数据
pixel = reshape(img,(img.shape[0]*img.shape[1],3))
pixel_new = deepcopy(pixel)
print (img.shape)
model = KMeans(n_clusters=3)
labels = model.fit_predict(pixel)
palette = model.cluster_centers_
for i in range(len(pixel)):
pixel_new[i,:] = palette[labels[i]]
imshow(reshape(pixel_new, (img.shape[0], img.shape[1],3)))
(三)实战:使用K-means用户分层
1. 收集、准备、分析数据
该数据集包含了营销快讯/电邮推广(电子邮件发送报价)和来自客户的交易层面数据(提供的数据来自客户期望和最终购买)这两个信息。
%matplotlib inline
import pandas as pd
import sklearn
import matplotlib.pyplot as plt
import seaborn as sns
data_offer = pd.read_excel("./WineKMC.xlsx", sheetname=0)
data_offer.columns = ["offer_id", "campaign", "varietal", "min_qty", "discount", "origin", "past_peak"]
data_offer.head()
data_transactions = pd.read_excel("./WineKMC.xlsx", sheetname=1)
data_transactions.columns = ["customer_name", "offer_id"]
data_transactions['n'] = 1
data_transactions.head()
因为我们试图更多地了解我们客户的行为,我们可以用他们的行为(根据他们是否在收到报价后进行了采购),以此将有类似想法的客户分类在一起。然后,我们可以研究这些群体,来寻找模式和趋势,来帮助我们制定未来的报价。
import numpy as np
# 合并两个dataframe
cust_compare = data_transactions.merge(data_offer, on = 'offer_id')
#Drop unnecessary columns
cust_compare = cust_compare.drop(['campaign', 'varietal', 'min_qty', 'discount', 'origin', 'past_peak'], axis = 1)
#Create pivot table
table = pd.pivot_table(cust_compare, index = 'customer_name', columns = 'offer_id', aggfunc=np.sum, fill_value = 0)
table
2.训练、测试算法
参数n_clusters:KMeans中的k,表示着我们告诉模型我们要分几类。这是KMeans当中唯一一个必填的参数,默认为8类,但通常我们的聚类结果会是一个小于8的结果。
如何衡量聚类算法的效果?K-means的目标是确保“簇内差异小,簇外差异大”,我们就可以通过衡量簇内差异来衡量聚类的效果。Inertia是用距离来衡量簇内差异的指标,因此,是否可以使用Inertia来作为聚类的衡量指标呢?
首先,它不是有界的。我们只知道,Inertia是越小越好,是0最好,但我们不知道,一个较小的Inertia究竟有没有达到模型的极限,能否继续提高。第二,它的计算太容易受到特征数目的影响,数据维度很大的时候,Inertia的计算量会陷入维度诅咒之中,计算量会爆炸,不适合用来一次次评估模型。第三,它会受到超参数K的影响,在我们之前的常识中其实我们已经发现,随着K越大,Inertia注定会越来越小,但这并不代表模型的效果越来越好了第四,Inertia对数据的分布有假设,它假设数据满足凸分布(即数据在二维平面图像上看起来是一个凸函数的样子),并且它假设数据是各向同性的(isotropic),即是说数据的属性在不同方向上代表着相同的含义。但是现实中的数据往往不是这样。所以使用Inertia作为评估指标,会让聚类算法在一些细长簇,环形簇,或者不规则形状的流形时表现不佳。
当真实标签未知的时候可以使用轮廓系数评价K-means的效果。在99%的情况下,我们是对没有真实标签的数据进行探索,也就是对不知道真正答案的数据进行聚类。这样的聚类,是完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果。其中轮廓系数是最常用的聚类算法的评价指标。它是对每个样本来定义的,它能够同时衡量:1)样本与其自身所在的簇中的其他样本的相似度a,等于样本与同一簇中所有其他点之间的平均距离;2)样本与其他簇中的样本的相似度b,等于样本与下一个最近的簇中的所有点之间的平均距离根据聚类的要求”簇内差异小,簇外差异大“,我们希望b永远大于a,并且大得越多越好。
轮廓系数范围是(-1,1),其中值越接近1表示样本与自己所在的簇中的样本很相似,并且与其他簇中的样本不相似,当样本点与簇外的样本更相似的时候,轮廓系数就为负。当轮廓系数为0时,则代表两个簇中的样本相似度一致,两个簇本应该是一个簇。可以总结为轮廓系数越接近于1越好,负数则表示聚类效果非常差。如果一个簇中的大多数样本具有比较高的轮廓系数,则簇会有较高的总轮廓系数,则整个数据集的平均轮廓系数越高,则聚类是合适的。如果许多样本点具有低轮廓系数甚至负值,则聚类是不合适的,聚类的超参数K可能设定得太大或者太小。
from sklearn.metrics import silhouette_score
from sklearn.cluster import KMeans
test = []
for i in range(2,11):
km = KMeans(n_clusters = i, random_state=0).fit(table)
score = silhouette_score(table,km.labels_)
test.append(score)
print(max(test),test.index(max(test)))
plt.figure(figsize=[10,5])
plt.plot(range(2,11),test)
plt.show()
由学习曲线可知,n_clusters=7时,轮廓系数最大。
其他参数:
init:可输入"k-means++","random"或者一个n维数组。这是初始化质心的方法,默认"k-means++"。输入"kmeans++":一种为K均值聚类选择初始聚类中心的聪明的办法,以加速收敛。如果输入了n维数组,数组的形状应该是(n_clusters,n_features)并给出初始质心。
random_state:控制每次质心随机初始化的随机数种子。
n_init:整数,默认10,使用不同的质心随机初始化的种子来运行k-means算法的次数。最终结果会是基于Inertia来计算的n_init次连续运行后的最佳输出。
#Choosing K=7
kmeans_7 = KMeans(n_clusters = 7, random_state=0).fit_predict(table)
points = list(kmeans_5)
d = {x:points.count(x) for x in points}
heights = list(d.values())
plt.bar(range(7),heights)
plt.xlabel('Clusters')
plt.ylabel('# of samples')
from sklearn.decomposition import PCA
pca = PCA(n_components = 2)
data_new = pca.fit_transform(table)
print(table.shape)
print(data_new.shape)
fig, ax = plt.subplots()
for i in range(7):
points = np.array([data_new[j] for j in range(len(data_new)) if kmeans_7[j] == i])
ax.scatter(points[:, 0], points[:, 1], s=7)