算法学习
算法理解
- 决策树的本质就是从数据集中归纳出一组分类规则,也称‘树归纳’,对于给定数据集,存在许多对他无错编码的树,我们感兴趣的是从中选出最小的树(树的结点数和决策结点的复杂性度量)。(一个if-then规则的集合)
- 从另一个角度看,决策树学习是根据训练数据集估计条件概率模型。基于特征空间划分的类的条件概率模型有无数个,我们选择的数据应该是不仅能对训练数据有很好的拟合,而且对未知数据也有很好的预测。
- 从所有可能的树中选取最优的树是NP完全问题,所以我们必须使用基于启发式的局部搜索过程,在合理的时间内找到合理的树。
- 树的学习是‘贪婪算法’,从包含全部训练数据的根开始,每一步的选择都是最佳划分。
NP完全问题
决策树生成步骤
- 特征选择
- 决策树的生成(考虑局部最优)
- 决策树的剪枝(考虑全局最优)
分类树手写代码实现
如何分支(不纯度的度量指标)
- 分类树:基尼系数;信息熵
- 回归树:MSE均方误差
构造数据集的最佳切分函数并返回结果(特征选择)
# 定义熵函数(Entropy function熵函数)
def calEnt(dataset):
n=dataset.shape[0]
i=dataset.iloc[:,-1].value_counts()
p=i/n
ent=(-p*np.log2(p)).sum()
return ent
# 手写数据
row_data = {'accompany':[0,0,0,1,1],
'game':[1,1,0,1,1],
'bad boy':['yes','yes','no','no','no']}
data = pd.DataFrame(row_data)
data
calEnt(data)
# 第0列的信息增益
a = 3/5*(-2/3*np.log2(2/3)-1/3*np.log2(1/3))
calEnt(data)-a
# 第1列的信息增益
b = 4/5*(-2/4*np.log2(2/4)-2/4*np.log2(2/4))
calEnt(data)-b
- 第0列的信息增益大于第1列的信息增益,所以应该选择第0列切分数据集
"""
函数功能:根据信息增益选择出最佳数据集切分的列
参数说明:
dataSet:原始数据集
返回:
axis:数据集最佳切分列的索引
"""
def bestSplit(dataSet):
baseEnt = calEnt(dataSet) # 计算原始熵
bestGain = 0 # 初始化信息增益
axis = -1 # 初始化最佳切分列,标签列
for i in range(dataSet.shape[1]-1): # 对特征的每一列进行循环
levels= dataSet.iloc[:,i].value_counts().index # 提取出当前列的所有取值
ents = 0 # 初始化子节点的信息熵
for j in levels: # 对当前列的每一个取值进行循环
childSet = dataSet[dataSet.iloc[:,i]==j] # 某一个子节点的dataframe
ent = calEnt(childSet) # 计算某一个子节点的信息熵
ents += (childSet.shape[0]/dataSet.shape[0])*ent # 计算当前列的信息熵
print(f'第{i}列的信息熵为{ents}')
infoGain = baseEnt-ents # 计算当前列的信息增益
print(f'第{i}列的信息增益为{infoGain}')
if (infoGain > bestGain):
bestGain = infoGain # 选择最大信息增益
axis = i # 最大信息增益所在列的索引
return axis
# 输出结果:0
bestSplit(data)
col = data.columns[0]
col
data.loc[data['accompany']==1,:].drop('accompany',axis=1)
'''
函数功能:按照给定的列划分数据集
参数说明:
dataSet:原始数据集
axis:指定的列索引
value:指定的属性值
返回:
redateset:按照指定索引和属性值切分后的数据集
'''
def mySplit(dataSet,axis,value):
col = dataSet.columns[axis]
redateset = dataSet.loc[dataSet[col]==value,:].drop(col,axis=1)
return redateset
# 例子
mySplit(data,0,1)
data.iloc[:,-1].value_counts()
决策树生成
递归结束的条件:
- 程序遍历完所有划分数据集的属性
- 每个分支下的所有实例都具有相同的分类
- 当前结点包含的样本集和为空,不能划分
ID3算法
ID3算法:在决策树的各个结点应用信息增益准则选择特征,递归的构建决策树。
具体方法:
- 从根结点开始,对结点计算所有可能的特征的信息增益
- 选择信息增益最大的特征作为结点特征,由该特征的不同取值建立子结点
- 再对子结点调用以上方法构建决策树
- 直到所有的信息增益均很小或没有特征可以选择为止,最后得到一个决策树
'''
函数说明:基于最大信息增益切分数据集,递归构建决策树
参数说明:
dataSet:原始数据集(最后一列是标签)
返回:
myTree:字典形式的树
'''
def createTree(dataSet):
featlist = list(dataSet.columns) # 提取出数据集所有的列
classlist = dataSet.iloc[:,-1].value_counts() # 获取最后一列类标签
# 判断最多标签数目是否等于数据集行数,或者数据集是否只有一列
if classlist[0]==dataSet.shape[0] or dataSet.shape[1] == 1:
return classlist.index[0] # 如果是,返回类标签
axis = bestSplit(dataSet) # 确定出当前最佳切分列的索引
bestfeat = featlist[axis] # 获取该索引对应的特征
myTree = {bestfeat:{}} # 采用字典嵌套的方式存储树信息
del featlist[axis] # 删除当前特征
valuelist = set(dataSet.iloc[:,axis]) # 提取最佳切分列所有属性值
for value in valuelist: # 对每一个属性值递归建树
myTree[bestfeat][value] = createTree(mySplit(dataSet,axis,value))
return myTree
# 生成字典形式的树
mytree = createTree(data)
mytree
ID3算法的局限性:
- 分支度越高离散变量子节点的总信息熵越小,容易出现极端情况
- 不能直接处理连续性变量,先要对连续性变量离散化
- 对缺失值敏感,使用ID3之前先要处理缺失值
- 没有剪枝的设置,很容易导致过拟合
C4.5
- 修改局部最优化条件:用信息增益比准则来选择特征(信息增益比:信息增益/训练数据关于某一特征值的熵)
- 增加连续变量处理手段:为连续变量分箱提供了模型方法,这也是决策树的最常见用途之一
决策树剪枝
- 预剪枝:降低了过拟合风险,由于贪婪算法的本质是禁止后续分支展开,带来了欠拟合风险;训练开销和测试开销小
- 后剪枝:先生成决策树,自上而下逐一考察,欠拟合风险小,泛化能力更强;训练开销大
CART算法
CART:分类回归树
- 分裂过程是一个二叉递归划分过程
- 预测变量x的类型既可以是连续性变量,也可以是分类型变量
- 数据应以其原始形式处理,不需要离散化
- 用于数值型预测时,并没有使用回归,而是基于到达叶结点的案例的平均值做出预测
属性不同的预测变量y分裂准则不同:
- 分类树:Gini准则,与信息增益类似,Gini系数度量一个结点的不纯度。
- 回归树:常见分割标准是标准偏差减少(SDR),类似于最小均方误差(LS准则)。
sklearn实现
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['Simhei'] #显示中文
plt.rcParams['axes.unicode_minus']=False #显示负号
data=load_wine()
X=data.data
y=data.target
Xtrain,Xtest,Ytrain,Ytest=train_test_split(X,y,test_size=0.2,random_state=0)
from sklearn.tree import DecisionTreeClassifier
clf=DecisionTreeClassifier()
clf.fit(Xtrain,Ytrain)
clf.score(Xtest,Ytest)
DecisionTreeClassifier参数:
- criterion: 属性选择算法,默认:“gini"表示基尼不纯度,可用"entropy"表示信息熵,两种算法类似,信息熵运算效率略低
- splitter:结点选择属性的策略,默认"best”:选择最优分割属性;"random"从排名靠前的属性中随机选择
- max_depth:树的最大深度,即层数,默认为None,也可以给整数
- min_samples_split:内部结点包含的观测数最小值(即能创建分支的节点包含的最小观测数),默认为2,可以是整数表示最少包含几行数据,也可以是浮点数表示占比
- min_samples_leaf:叶节点包含的最小观测数(即分支后的子节点包含的最小观测数),默认为1,可以是整数和浮点数,表示含义同上
- max_leaf_node:节点个数的最大值
- min_impurity_decrease:不纯度(信息熵)下降的阈值,只有当下降值大于阈值的时候才分支,用于前剪枝策略.版本0.19的新参数,对应的旧参数名为:min_impurity_split
- presort:是否对数据排序以提高寻找最优分割点的效率,默认为False,大数据集设置为True会降低训练速度,小数据集设置为True会提速
- 更多其他参数查看帮助文件(主要是样本加权相关参数)
函数返回结果包含的属性:
- classes_:y的类别标签
- n_classes_:类别标签的个数
- feature_importances:属性重要性的列表
- n_features_:属性个数
- n_outputs_:输出个数
- tree_:存储整个树结构,可以抽出如下属性,详情参考,其中-2(或-1)表示叶节点
- node_count:树的结点个数
- children_left:所在位置节点对应左节点的id值,所有节点从0开始计数,每个节点对应一个id,计数规则从左分支到右分支,-1表示叶节点
- children_right:所在位置节点对应右节点的id值,-1表示叶节点
- feature:每个结点对应的属性索引值,-2表示叶节点
- threshold:每个结点对应的分割值(-2表示叶节点)
feature_name = ['酒精','苹果酸','灰','灰的碱性','镁','总酚','类黄酮','非黄烷类酚类','花青素','颜色强度','色调','od280/od315稀释葡萄酒','脯氨酸']
决策树的可视化
import graphviz
from sklearn import tree
dot_data = tree.export_graphviz(clf,feature_names=feature_name,
class_names=['琴酒','雪莉','贝尔摩德'],
filled=True,rounded=True)
graph = graphviz.Source(dot_data)
#graph.render('wine')
graph
重要参数:certerion
# 默认criterion='gini'
clf = tree.DecisionTreeClassifier(criterion="entropy")
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score
重要参数:random_state & splitter
# 默认splitter='best'
clf = tree.DecisionTreeClassifier(criterion="entropy",splitter="random",random_state=420)
clf = clf.fit(Xtrain, Ytrain)
score = clf.score(Xtest, Ytest)
score
网格搜索
from sklearn.model_selection import GridSearchCV
Xtrain,Xtest,Ytrain,Ytest=train_test_split(X,y,test_size=0.2,random_state=0)
# 设置参数可取值
param_grid = {'criterion':['entropy','gini'],
'max_depth':range(2,11),
'min_samples_split':range(2,20,2),
'min_samples_leaf':range(1,10)}
# 设置参数网格
reg = GridSearchCV(DecisionTreeClassifier(),param_grid,cv=5)
# 建模
reg.fit(Xtrain,Ytrain)
# 最优参数
reg.best_params_
# 最优分数
reg.best_score_
clf = DecisionTreeClassifier(criterion='entropy',max_depth=10,
min_samples_split=2,min_samples_leaf=4)
clf.fit(Xtrain,Ytrain)
clf.score(Xtest,Ytest)
剪枝参数
# 模型对训练集的拟合程度,这是一课没有修建的树
train_score=clf.score(Xtrain,Ytrain)
train_score
- max_depth:限制树的最大深度
- min_samples_leaf:限制子结点的最小训练样本数
- min_samples_split:限制每个内部结点包含的最小训练样本数
- max_features:方法暴力,直接限制可以拟合的特征数量;建议使用PCA,ICA或特征选择模块中的降维算法
- min_impurity_decrease:限制信息增益的大小
# 学习曲线确定最优参数
score=[]
for i in range(10):
clf = DecisionTreeClassifier(max_depth=i+1,
criterion='gini',
random_state=30)
clf = clf.fit(Xtrain,Ytrain)
score.append(clf.score(Xtest,Ytest))
plt.plot(range(1,11),score,c='red',label='max depth')
plt.legend()
plt.show()
# 返回max(score)对应的值
score.index(max(score))+1
模型的其他属性接口
# 特征重要性clf.faeture_importances_
[*zip(feature_name,clf.feature_importances_)]
# 结点个数
clf.tree_.node_count
分类模型的评估
二分类决策树中的模型不均衡问题
from sklearn.datasets import make_blobs
class_1 = 1000 #类别1有1000个样本
class_2 = 100 #类别2只有100个
centers = [[0,0], [2.0, 2.0]] #设定两个类别的中心
clusters_std = [2.5, 0.5] #设定两个类别的方差,通常来说,样本量比较大的类别会更加松散
X, y = make_blobs(n_samples=[class_1, class_2],
centers=centers,
cluster_std=clusters_std,
random_state=420,shuffle=False)
#看看数据集长什么样
plt.scatter(X[:, 0], X[:, 1], c=y, cmap="rainbow",s=10);
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=420)
clf = DecisionTreeClassifier()
clf.fit(Xtrain,Ytrain)
#clf.predict(Xtest)
clf.score(Xtest,Ytest)
wclf = DecisionTreeClassifier(class_weight='balanced')
wclf.fit(Xtrain,Ytrain)
#wclf.predict(Xtest)
wclf.score(Xtest,Ytest)
混淆矩阵
from sklearn import metrics
ypred = wclf.predict(Xtest)
# 混淆矩阵
metrics.confusion_matrix(Ytest,ypred)
# 准确率accuracy
metrics.accuracy_score(Ytest,ypred)
# 精确度precision
metrics.precision_score(Ytest,ypred)
# 召回率recall
metrics.recall_score(Ytest,ypred)
# F1 measure
metrics.f1_score(Ytest,ypred)