机器学习----决策树

决策树思想的来源非常朴素,程序设计中的条件分支结构就是if-else结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法

决策树:

  • 是一种树形结构,本质是一颗由多个判断节点组成的树
  • 其中每个内部节点表示一个属性上的判断,
  • 每个分支代表一个判断结果的输出,
  • 最后每个叶节点代表一种分类结果。

在这里插入图片描述

感知决策树

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

iris = datasets.load_iris()
X = iris.data[:, 2:]
y = iris.target

plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()

from sklearn.tree import DecisionTreeClassifier

dt_clf = DecisionTreeClassifier(max_depth=2, criterion='entropy') # entropy = 熵
dt_clf.fit(X, y)

plot_decision_boundary(dt_clf, axis=[0.5, 7.5, 0, 3])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()

在这里插入图片描述

在这里插入图片描述
当属性是数值特征时,在每一个节点上,选择某一个维度上的数值,以它为域值将样本分成两类。

决策树的特点

  1. 是非参数学习算法

  2. 可以分类问题

  3. 天然可以解决多分类问题

  4. 也可以解决回归问题(用分类算法将样本归到某个叶子上,该叶子上所有样本的平均值即输出标记)

  5. 有非常好的可解释性

怎样创建决策树

  1. 每个结点在哪个维度做划分?

  2. 某个维度的哪个值上做划分?

信息熵

信息熵表示一组变量的不确定度。
熵越大,不确定性越高。
熵越小,不确定性越低。
在这里插入图片描述
假设数据集中有三个类别,样本属于这三个类别的概率分别是{1/3, 1/3, 1/3},此时信息熵H为:
H = -1/3log(1/3) -1/3log(1/3) -1/3log(1/3) = 1.0986
如果三个类别的概率分别是{0.1、0.2、0.7},那么H=0.8018
如果三个类别的概率分别是{1, 0, 0},那么H=0

以二分类为例,画出信息熵的图像

数据只有两个类型,其中一类的概率为x,另一个类的概率为 1- x, 信息熵计算公式如下: ​
在这里插入图片描述

def  entropy(p):
    return -p*np.log(p) - (1-p)*np.log(1-p)

在这里插入图片描述

使用信息熵寻找最优划分

dt_clf = DecisionTreeClassifier(max_depth=2, criterion='entropy', random_state=42)  
dt_clf.fit(X, y)
plot_decision_boundary(dt_clf, [0.5, 7.5, 0, 3])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.scatter(X[y==2, 0], X[y==2, 1])
plt.show()

在这里插入图片描述

模拟使用信息熵进行划分

# 基于维度d上的值value对X,y,进行划分
def split(X, y, d, value):
    index_a = (X[:, d] <= value)
    index_b = (X[:, d] > value)
    return  X[index_a],  X[index_b], y[index_a],  y[index_b]

计算划分后的每一组熵

from collections import Counter
from math import log

def entropy(y):
    counter = Counter(y)
    res = 0.0
    for num in counter.values():
        p = num/len(y)
        res += -p*log(p)
    return res
# 寻找熵最小的d和value
def try_split(X, y):
    best_entropy = float('inf')
    best_d, best_v = -1, -1
    for d in range(X.shape[1]):# 穷举搜索所有维度
        sorted_index = np.argsort(X[:, d])
        for i in range(1, len(X)): # 对每个样本进行遍历, 可以的阈值为两个点之间的值
            if X[sorted_index[i], d]  != X[sorted_index[i-1], d]:
                v = (X[sorted_index[i], d] + X[sorted_index[i-1], d])/2
                X_l, X_r, y_l, y_r = split(X, y, d, v)
                p_l, p_r = len(X_l)/len(X),len(X_r)/len(X) 
                e = p_l*entropy(y_l) + p_r*entropy(y_r)
                if e < best_entropy:
                    best_entropy, best_d, best_v = e, d, v    
    return  best_entropy, best_d, best_v
# 进行一次划分
best_entropy, best_d, best_v = try_split(X, y)
print("best_entropy=", best_entropy) 
print("best_d=", best_d)
print("best_v=", best_v)

best_entropy= 0.46209812037329684
best_d= 0
best_v= 2.45

# 存储划分结果
X1_l, X1_r, y1_l, y1_r = split(X, y, best_d, best_v)
entropy(y1_l)  # 上面的划分左边只有一种数据, 信息熵为0

0.0

entropy(y1_r) 

0.6931471805599453

# 左面不需要划分, 继续划分右面即可
best_entropy2, best_d2, best_v2 = try_split(X1_r,y1_r)
print("best_entropy=", best_entropy2)
print("best_d=", best_d2)
print("best_v=", best_v2)

best_entropy= 0.2147644654371359
best_d= 1
best_v= 1.75

X2_l, X2_r, y2_l, y2_r  = split(X1_r, y1_r, best_d2, best_v2)
entropy(y2_l)

0.30849545083110386

entropy(y2_r)  # 两个都不为为零, 都可以继续划分

0.10473243910508653

基尼系数(Gini)

基尼系数的计算公式:
在这里插入图片描述其性质与信息熵相同
在这里插入图片描述
基尼系数越大,不确定性越高。
基尼系数越小,不确定性越低
以二分类为例,在二分类中,其中一类的概率为x,则G = -2x^2 + 2x
如果所有类别的概率相等时,基尼系数最大。类别确定时,基尼系数为0。

使用基尼系数寻找最优划分

dt_clf = DecisionTreeClassifier(max_depth=2, criterion='entropy', random_state=42)  
dt_clf.fit(X, y)
plot_decision_boundary(dt_clf, [0.5, 7.5, 0, 3])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.scatter(X[y==2, 0], X[y==2, 1])
plt.show()

在这里插入图片描述

模拟使用基尼系数进行划分


# 基于维度d上的值value对X,y,进行划分
def split(X, y, d, value):
    index_a = (X[:, d] <= value)
    index_b = (X[:, d] > value)
    return  X[index_a],  X[index_b], y[index_a],  y[index_b]
from collections import Counter
from math import log
# 计算划分后的每一组基尼系数
def gini(y):
    counter = Counter(y)
    res = 1.0
    for num in counter.values():
        p = num/len(y)
        res -= p**2
    return res
# 寻找基尼系数最小的d和value
def try_split(X, y):
    best_gini = float('inf')
    best_d, best_v = -1, -1
    for d in range(X.shape[1]):# 穷举搜索所有维度
        sorted_index = np.argsort(X[:, d])
        for i in range(1, len(X)): # 对每个样本进行遍历, 可以的阈值为两个点之间的值
            if X[sorted_index[i], d]  != X[sorted_index[i-1], d]:
                v = (X[sorted_index[i], d] + X[sorted_index[i-1], d])/2
                X_l, X_r, y_l, y_r = split(X, y, d, v)
                p_l, p_r = len(X_l)/len(X),len(X_r)/len(X) 
                e = gini(y_l) + gini(y_r)
                if e < best_gini:
                    best_gini, best_d, best_v = e, d, v    
    return  best_gini, best_d, best_v
best_gini, best_d, best_v = try_split(X, y)
print("best_gini = ", best_gini)
print("best_d = ", best_d) # 在第0维度进行划分
print("best_v = ", best_v)

best_gini = 0.5
best_d = 0
best_v = 2.45


X1_l, X1_r, y1_l, y1_r = split(X, y, best_d, best_v)
print("gini_y1_l = ", gini(y1_l)) # 分完了
print("gini_y1_r = ", gini(y1_r))

gini_y1_l = 0.0
gini_y1_r = 0.5

best_gini2, best_d2, best_v2 = try_split(X1_r, y1_r)
print("best_gini = ", best_gini2)
print("best_d = ", best_d2) # 在第1维度进行划分
print("best_v = ", best_v2)

best_gini = 0.2105714900645938
best_d = 1
best_v = 1.75

X2_l, X2_r, y2_l, y2_r = split(X1_r, y1_r, best_d2, best_v2)
print("gini_y2_l = ", gini(y2_l)) 
print("gini_y2_r = ", gini(y2_r))

gini_y2_l = 0.1680384087791495
gini_y2_r = 0.04253308128544431
在这里插入图片描述

信息熵 VS 基尼系数
  • 信息熵计算比基尼系数稍慢。
  • scikit-learn中默认使用基尼系数。
  • 大多数时候二者没有特别的效果优劣

CART和决策树中的超参数

决策树又叫CART
CART = Classification And Regression Tree
根据某一个维度v和某一个域值d进行二分,这样得到的树就叫CART
scikit-learn创建决策树的方法就叫CART。
还有其它创建决策树的方法,例如ID3、C4.5、C5.0

创建好决策树,预测一个样本的时间复杂度为O(log(m))
训练模型的时间复杂度为:O(nmlog(m))
是非参数学习算法,非常容易产生过拟合。
实际使用决策树时都要进行剪枝,
1.降低复杂度2.解决过拟合

决策树使用不同的超参数的结果对比

生成数据


import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
X, y = datasets.make_moons(noise=0.25,random_state=666)
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

在这里插入图片描述

使用默认参数训练

使用默认参数训练会产生过拟合, 我们需要对其进行剪枝操作

from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier()
dt_clf.fit(X, y)
def plot_decision_boundary(model, axis):
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1,1),
        np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1,1)
    )
    X_new = np.c_[x0.ravel(), x1.ravel()]

    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)

    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])

    plt.contourf(x0, x1, zz, cmap=custom_cmap)
plot_decision_boundary(dt_clf, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

在这里插入图片描述

决策树算法api

class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, max_depth=None,random_state=None)
criterion
  • 特征选择标准
  • “gini"或者"entropy”,前者代表基尼系数,后者代表信息增益。一默认"gini",即CART算法。
min_samples_split
  • 内部节点再划分所需最小样本数
  • 这个值限制了子树继续划分的条件,如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分。 默认是2.如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。我之前的一个项目例子,有大概10万样本,建立决策树时,我选择了min_samples_split=10。可以作为参考。
min_samples_leaf
  • 叶子节点最少样本数
  • 这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝。 默认是1,可以输入最少的样本数的整数,或者最少样本数占样本总数的百分比。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。之前的10万样本项目使用min_samples_leaf的值为5,仅供参考。
max_depth
  • 决策树最大深度
  • 决策树的最大深度,默认可以不输入,如果不输入的话,决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间
random_state
  • 随机数种
    修改不同的超参数
dt_clf2 = DecisionTreeClassifier(max_depth=2)
dt_clf2.fit(X, y)
plot_decision_boundary(dt_clf2, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

在这里插入图片描述

dt_clf3 = DecisionTreeClassifier(min_samples_split=10) #小于10个样本不会再划分
dt_clf3.fit(X, y)
plot_decision_boundary(dt_clf3, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

在这里插入图片描述


dt_clf3 = DecisionTreeClassifier(min_samples_split=10) #小于10个样本不会再划分
dt_clf3.fit(X, y)
plot_decision_boundary(dt_clf3, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

在这里插入图片描述

dt_clf5 = DecisionTreeClassifier(max_leaf_nodes=4) # 树上最大的节点或叶子的数量
dt_clf5.fit(X, y)
plot_decision_boundary(dt_clf5, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.show()

在这里插入图片描述
使用这些参数时:

  1. 避免欠拟合
  2. 使用网格搜索来组合这些参数
  • 常见决策树的启发函数比较

在这里插入图片描述
在这里插入图片描述

ID3 算法

存在的缺点

​ (1) ID3算法在选择根节点和各内部节点中的分支属性时,采用信息增益作为评价标准。信息增益的缺点是倾向于选择取值较多的属性,在有些情况下这类属性可能不会提供太多有价值的信息.

​ (2) ID3算法只能对描述属性为离散型属性的数据集构造决策树。

C4.5算法

做出的改进(为什么使用C4.5要好)

​ (1) 用信息增益率来选择属性

​ (2) 可以处理连续数值型属性

​ (3)采用了一种后剪枝方法

​ (4)对于缺失值的处理

CART算法

CART算法相比C4.5算法的分类方法,采用了简化的二叉树模型,同时特征选择采用了近似的基尼系数来简化计算。

C4.5不一定是二叉树,但CART一定是二叉树。

决策树变量的两种类型:

数字型(Numeric):变量类型是整数或浮点数,如前面例子中的“年收入”。用“>=”,“>”,“<”或“<=”作为分割条件(排序后,利用已有的分割情况,可以优化分割算法的时间复杂度)。
名称型(Nominal):类似编程语言中的枚举类型,变量只能从有限的选项中选取,比如“婚姻情况”,只能是“单身”,“已婚”或“离婚”,使用“=”来分割。

如何评估分割点的好坏?

如果一个分割点可以将当前的所有节点分为两类,使得每一类都很“纯”,也就是同一类的记录较多,那么就是一个好分割点。

构建决策树采用贪心算法,只考虑当前纯度差最大的情况作为分割点。

决策树解决回归问题

回归问题是指找到一根直线/曲线最大程度的拟合样本点。
不同的回归算法对“拟合”有不同的理解。
例如线性回归算法定义“拟合”为:所有样本点到直线的MSE最小

import numpy as np
from sklearn import datasets

boston = datasets.load_boston()
x = boston.data
y = boston.target

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(x, y, random_state=666)
from sklearn.tree import DecisionTreeRegressor
dt_reg = DecisionTreeRegressor()
dt_reg.fit(X_train, y_train)
dt_reg.score(X_test, y_test) # 0.583145982914128
dt_reg.score(X_train, y_train) # 1.0

训练数据集上预测的准确率是100%,而在测试数据集上效果不好。 决策树非常容易产生过拟合现象,要消除过拟合就要使用上面介绍的超参数。

决策树的局限性

  1. 决策树的边界都是与某个轴平行的
    在这里插入图片描述
    2.高度依赖于参数,对样本敏感
    对样本敏感举例:
    这是150数据参与训练

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
def plot_decision_boundary(model, axis):
#     meshgrid 网格坐标矩阵
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
        np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),)
    ## 降维数据进行展平   flattern也是展平 
    X_new = np.c_[x0.ravel(), x1.ravel()]
    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)
    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
    
    plt.contourf(x0, x1, zz, cmap=custom_cmap)
iris = datasets.load_iris()
X = iris.data[:, 2:]
y = iris.target
dt_clf = DecisionTreeClassifier(max_depth=2, criterion='entropy') 
dt_clf.fit(X,y)
plot_decision_boundary(dt_clf, [0.5, 7.5, 0, 3])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.scatter(X[y==2, 0], X[y==2, 1])
plt.show()

在这里插入图片描述

  • 删除138号样本
X_new = np.delete(X, 138, axis=0)
y_new = np.delete(y, 138)
X_new.shape

(149, 2)

dt_clf2 = DecisionTreeClassifier(max_depth=2, criterion='entropy') 
dt_clf2.fit(X_new,y_new)
plot_decision_boundary(dt_clf2, [0.5, 7.5, 0, 3])
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.scatter(X[y==2, 0], X[y==2, 1])
plt.show()

在这里插入图片描述

决策树的学习曲线


import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
boston = datasets.load_boston()
X = boston.data
y = boston.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

学习曲线

基于RMSE绘制学习曲线

from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error
def plot_learning_curve(algo, X_train, X_test, y_train, y_test):
    train_score = []
    test_score = []
    for i in range(1, len(X_train)+1):
        algo.fit(X_train[:i], y_train[:i])
    
        y_train_predict = algo.predict(X_train[:i])
        train_score.append(mean_squared_error(y_train[:i], y_train_predict))
    
        y_test_predict = algo.predict(X_test)
        test_score.append(mean_squared_error(y_test, y_test_predict))
        
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               np.sqrt(train_score), label="train")
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               np.sqrt(test_score), label="test")
    plt.legend()
    plt.show()
plot_learning_curve(DecisionTreeRegressor(), X_train, X_test, y_train, y_test)

在这里插入图片描述
基于R^2值绘制学习曲线


from sklearn.metrics import r2_score

def plot_learning_curve_r2(algo, X_train, X_test, y_train, y_test):
    train_score = []
    test_score = []
    for i in range(1, len(X_train)+1):
        algo.fit(X_train[:i], y_train[:i])
    
        y_train_predict = algo.predict(X_train[:i])
        train_score.append(r2_score(y_train[:i], y_train_predict))
    
        y_test_predict = algo.predict(X_test)
        test_score.append(r2_score(y_test, y_test_predict))
        
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               train_score, label="train")
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               test_score, label="test")
    plt.legend()
    plt.axis([0, len(X_train)+1, -0.1, 1.1])
    plt.show()
    
plot_learning_curve_r2(DecisionTreeRegressor(), X_train, X_test, y_train, y_test)

在这里插入图片描述
以max_depth参数为例,看不同参数学习曲线的不同

plot_learning_curve_r2(DecisionTreeRegressor(max_depth=1), X_train, X_test, y_train, y_test)

在这里插入图片描述

plot_learning_curve_r2(DecisionTreeRegressor(max_depth=3), X_train, X_test, y_train, y_test)

在这里插入图片描述

plot_learning_curve_r2(DecisionTreeRegressor(max_depth=5), X_train, X_test, y_train, y_test)

在这里插入图片描述

plot_learning_curve_r2(DecisionTreeRegressor(max_depth=20), X_train, X_test, y_train, y_test)

在这里插入图片描述

模型复杂度曲线


import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
boston = datasets.load_boston()
X = boston.data
y = boston.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import r2_score
maxSampleLeaf = 506
train_scores = []
test_scores = []
for i in range(1, maxSampleLeaf+1):
    dt_reg = DecisionTreeRegressor(min_samples_leaf=i)
    dt_reg.fit(X_train, y_train)
    y_train_predict = dt_reg.predict(X_train)
    train_scores.append(r2_score(y_train, y_train_predict))
    test_scores.append(dt_reg.score(X_test, y_test))
    
plt.plot([i for i in range(1, maxSampleLeaf+1)], train_scores, label="train")
plt.plot([i for i in range(1, maxSampleLeaf+1)], test_scores, label="test")
plt.xlim(506, 1)
plt.legend()
plt.show()

在这里插入图片描述

maxSampleLeaf = 100
train_scores = []
test_scores = []
for i in range(1, maxSampleLeaf+1):
    dt_reg = DecisionTreeRegressor(min_samples_leaf=i)
    dt_reg.fit(X_train, y_train)
    y_train_predict = dt_reg.predict(X_train)
    train_scores.append(r2_score(y_train, y_train_predict))
    test_scores.append(dt_reg.score(X_test, y_test))
    
plt.plot([i for i in range(1, maxSampleLeaf+1)], train_scores, label="train")
plt.plot([i for i in range(1, maxSampleLeaf+1)], test_scores, label="test")
plt.xlim(maxSampleLeaf, 1)
plt.legend()
plt.show()

在这里插入图片描述

maxSamplesSplit = 300
train_scores = []
test_scores = []
for i in range(2, maxSamplesSplit+1):
    dt_reg = DecisionTreeRegressor(min_samples_split=i)
    dt_reg.fit(X_train, y_train)
    y_train_predict = dt_reg.predict(X_train)
    train_scores.append(r2_score(y_train, y_train_predict))
    test_scores.append(dt_reg.score(X_test, y_test))
    
plt.plot([i for i in range(2, maxSamplesSplit+1)], train_scores, label="train")
plt.plot([i for i in range(2, maxSamplesSplit+1)], test_scores, label="test")
plt.xlim(maxSamplesSplit, 2)
plt.legend()
plt.show()

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值