文章目录
决策树
一、决策树是用来干什么的
如下所示的决策树,通过对每一个节点(特征)的决策来判断是否是猫。
我们的决策树算法就是要在所有可能的决策树中找到一个在训练集上面表现良好的,同时在新数据上面的泛化能力良好的决策树模型。
1、我们应该使用哪个特征作为该节点
决策树上面的每一个节点我们应该使用哪个特征,能够最大化纯度(将不同类型的物品区分开),在决定哪一个特征作为根节点时,我们需要判断,当我们使用该特征作为根节点,它区分的纯净度如何?我们肯定是选择纯净度最高的那个特征作为根节点,示例如下:
2、我们什么时候可以停止划分(不再进行分支)
一种存在多种情况时,我们可以不再划分分支,示例如下:
1、当划分的纯度达到100%时;2、当决策树达到设定的最大阈值(深度)时;3、纯度得分的改进,低于某个阈值,最低的纯度改进或者是降低了纯度(保持树的规模较小,减少过拟合的风险);4、当可以划分的样本数据太少时;
二、熵(衡量一个集合不纯度的指标)
下面是关于熵的函数图像:其中p1表示示例中正例的比率,H(p1)代表它对应的熵值。
下面是熵的计算公式:
下面是实现计算熵的代码实现:
# UNQ_C1
# GRADED FUNCTION: compute_entropy
def compute_entropy(y):
"""
Computes the entropy for
Args:
y (ndarray): Numpy array indicating whether each example at a node is
edible (`1`) or poisonous (`0`)
Returns:
entropy (float): Entropy at that node
"""
# You need to return the following variables correctly
entropy = 0.
num = 0 # 记录正例的数量
p_1 = 0
### START CODE HERE ###
if(len(y)!=0):
for i in range(len(y)):
if(y[i]==1):
num += 1
p_1 = num/len(y)
if(p_1==0 or p_1==1):
entropy = 0
else:
entropy = -p_1*np.log2(p_1)-(1-p_1)*np.log2(1-p_1)
### END CODE HERE ###
return entropy
三、根据熵来决定该节点使用哪一种特征进行决策
在节点上如何决定分裂特征的方法,即基于哪种特征选择能够最大程度地减少熵。熵地减少也称为信息增益。
上面地ppt示例为我们很好地说明了如何根据熵来选择分裂特征的一个详细步骤:首先我们需要说明的是在计算某一个特征分裂的熵时,是将左右分支的熵做了一个加权平均,这个权是来自于分裂给该分支的样本数量占总样本的比率(上图有详细的计算方式)。我们在根据熵值决定使用哪一个特征进行分裂时,是比较的该特征分裂得到的熵值与上一个节点的熵的差值,谁最大就选那个特征进行分裂;当该熵值差小于设定的阈值时,表明我们可以停止分裂了,以避免过拟合。详细步骤如下:
1、计算上一个节点的熵值;2、计算该特征分裂后的左右分支的各自的熵值;3、将左右两个熵值进行加权平均,得到该特征分裂的总熵值;4、计算得到每一种特征分裂的熵值与上一个节点熵的差值(信息增益);5、比较差值,其中差值最大的作为新的特征分裂。
上述信息增益的通用公式如下:
四、构建一个完整的决策树
1、详细步骤如下:
1、从树的根节点开始,包含所有的训练示例,并计算所有可能的信息增益;
2、选择提供最高信息增益的特征进行分裂;
3、根据选定的特征将数据集分成两个子集,创建左右分支,并且将两个数据集分别发送到左右分支,完成了根节点的特征分裂;
4、继续上面的分裂(针对某一个节点,再次计算所有特征分裂的信息增益),直到满足停止标准。
注意:这里说明一下同一深度使用相同地特征进行分裂是没有问题的。
特征分裂的代码示例如下:
# UNQ_C2
# GRADED FUNCTION: split_dataset
def split_dataset(X, node_indices, feature):
"""
Splits the data at the given node into
left and right branches
Args:
X (ndarray): Data matrix of shape(n_samples, n_features)
node_indices (ndarray): List containing the active indices. I.e, the samples being considered at this step.
feature (int): Index of feature to split on
Returns:
left_indices (ndarray): Indices with feature value == 1
right_indices (ndarray): Indices with feature value == 0
"""
# You need to return the following variables correctly
left_indices = []
right_indices = []
### START CODE HERE ###
for i in node_indices:
if(X[i][feature]==1):
left_indices.append(i)
else:
right_indices.append(i)
### END CODE HERE ###
return left_indices, right_indices
计算信息增益的代码如下:
# UNQ_C3
# GRADED FUNCTION: compute_information_gain
def compute_information_gain(X, y, node_indices, feature):
"""
Compute the information of splitting the node on a given feature
Args:
X (ndarray): Data matrix of shape(n_samples, n_features)
y (array like): list or ndarray with n_samples containing the target variable
node_indices (ndarray): List containing the active indices. I.e, the samples being considered in this step.
Returns:
cost (float): Cost computed
"""
# Split dataset
left_indices, right_indices = split_dataset(X, node_indices, feature)
# Some useful variables
X_node, y_node = X[node_indices], y[node_indices]
X_left, y_left = X[left_indices], y[left_indices]
X_right, y_right = X[right_indices], y[right_indices]
# You need to return the following variables correctly
information_gain = 0
### START CODE HERE ###
# Weights
w_left = len(left_indices)/len(node_indices)
w_right = len(right_indices)/len(node_indices)
#Weighted entropy
entropy = w_left*compute_entropy(y_left)+w_right*compute_entropy(y_right)
#Information gain
information_gain = compute_entropy(y_node)-entropy
### END CODE HERE ###
return information_gain
选择最佳特征分裂的代码示例如下:
# UNQ_C4
# GRADED FUNCTION: get_best_split
def get_best_split(X, y, node_indices):
"""
Returns the optimal feature and threshold value
to split the node data
Args:
X (ndarray): Data matrix of shape(n_samples, n_features)
y (array like): list or ndarray with n_samples containing the target variable
node_indices (ndarray): List containing the active indices. I.e, the samples being considered in this step.
Returns:
best_feature (int): The index of the best feature to split
"""
# Some useful variables
num_features = X.shape[1]
# You need to return the following variables correctly
best_feature = -1
best_gin = 0.
### START CODE HERE ###
for i in range(num_features):
gin = compute_information_gain(X,y,node_indices,i)
if(gin>best_gin):
best_gin = gin
best_feature = i
### END CODE HERE ##
return best_feature
利用上面的函数构建决策树的详细代码如下:
# 全局变量:用于记录整棵树的构建信息(非评分部分)
tree = []
def build_tree_recursive(X, y, node_indices, branch_name, max_depth, current_depth):
"""
使用递归算法构建决策树(仅打印结构,不返回模型)。
Args:
X (ndarray): 样本特征矩阵,形状 (n_samples, n_features)
y (array-like): 样本标签,长度 n_samples
node_indices (ndarray): 当前节点正在考虑的样本索引列表
branch_name (str): 当前分支名称,如 'Root'、'Left'、'Right'
max_depth (int): 允许的最大树深度
current_depth (int): 当前递归深度
"""
# -------------------------------
# 1. 终止条件:已达到最大深度
# -------------------------------
if current_depth == max_depth:
# 根据当前深度生成缩进和分隔符,打印叶节点信息
formatting = " " * current_depth + "-" * current_depth
print(formatting, "%s leaf node with indices" % branch_name, node_indices)
return # 停止继续分裂
# -------------------------------
# 2. 选择最佳划分特征
# -------------------------------
best_feature = get_best_split(X, y, node_indices) # 返回最佳特征索引
# 记录当前节点信息到全局列表 tree
tree.append((current_depth, branch_name, best_feature, node_indices))
# 打印当前节点的分裂信息
formatting = "-" * current_depth
print("%s Depth %d, %s: Split on feature: %d" % (formatting, current_depth, branch_name, best_feature))
# -------------------------------
# 3. 根据最佳特征划分样本索引
# -------------------------------
left_indices, right_indices = split_dataset(X, node_indices, best_feature)
# -------------------------------
# 4. 递归构建左右子树
# -------------------------------
# 左子树:feature 值为 1 的样本
build_tree_recursive(X, y, left_indices, "Left", max_depth, current_depth + 1)
# 右子树:feature 值为 0 的样本
build_tree_recursive(X, y, right_indices, "Right", max_depth, current_depth + 1)
最后直接调用即可:
build_tree_recursive(X_train, y_train, root_indices, "Root", max_depth=2, current_depth=0)
五、使用分类特征的一种独热编码
之前我们看的示例,每一个特征都只有两个值,现在我们每个特征有多个离散值,我们又应该如何做呢?
下面是一个示例,我们可以通过这种方式来构建决策时。
使用独热编码是一种更好的解决方式。上述有关耳朵的特征一共有3个可能的取值,独热编码将这个三个可能的取值变成三个新的特征,每个特征的取值是0或1。
通过上面的修改方式,我们就可以继续使用决策树算法构建我们的决策树模型了。
那某一个特征是可以取任何数值的连续值特征呢?我们又应该如何做?
六、修改决策树,处理连续值特征
如下示例:动物的重量使用连续值(有一定的范围)来表示:
解决方式如下:选择一个阈值,将连续值划分成两部分,按照之前的信息增益计算方式,选择信息增益最大的那个阈值将该特征进行0/1划分,然后进行常规的决策树构建。示例如下:
七、回归树(决策树解决回归问题)
回归树:即将决策树泛化到解决回归问题。
示例如下:我们的需求是通过动物的一些特征来预测动物的重量,很明显他是一个回归任务。
在分类问题中,我们是以增加信息增益为目标构建决策树,而在回归问题中我们是试图减少每个子集目标值的方差。
在构建决策树的过程中,就变成了如下的计算方式(与之前的很类似,熵变成了方差):
八、决策树的集成
1、集成决策树是用来解决什么问题的?
单个的决策树在解决实际问题时,对数据十分敏感,当其中一个数据发生改变时,可能会引起特征分裂发生改变。下面是一个示例:
集成决策树,即训练多个决策树,让多个决策树对测试集进行预测,进行投票,投票最多的结果作为我们的预测结果,这使得整体算法对任何单棵树的影响变得不那么敏感。示例如下:
2、认识有放回采样
例如我们的原始数据样本里面有10个动物(猫和狗),我们对原始数据进行有放回抽样得到10个样本,这个新的数据集可能没有完全包含原始数据集。通过这样的抽样方法,我们可以得到很多个跟原始数据集类似的新的数据集。
3、随机森林(强大的树集成算法)
首先构建一个B个决策树的集成树(里面每个决策树的数据集来自于有放回抽样得到的数据集)
对上面的集成树进行修改,尝试在每个节点随机化特征选择得到随机森林:
例如,当到了某一个点有n个特征可以选择进行分裂时,我们可以从n个特征集合里面随机选择k(n很大时,k一般取根号n)个特征作为我们的分裂备选,然后选择这k个特征中信息增益最高的作为分裂特征。这样就可以得到随机森林算法。算法的优势是:在进行抽样时,就已经模拟了一部分的数据变化,意味着数据集的一些小的改动并不会太大影响整个随机森林算法的结果。
九、提升决策树算法(XGBoost)
类似于刚才的集成树的做法,只是在训练第二个决策树时,这里采用的训练数据集更有可能选择之前的决策树分类错误的样本(而不是1/m),即将更多的注意力放在尚未处理好的例子子集上,并使用下一个决策树来尝试处理这些问题。
XGBoost就是这样一个算法,它内置了正则化以防止过拟合。下面是代码实现XGBoost的示例:
十、何时使用决策树而不是神经网络
1、决策树和集成树适用的场景:
适合处理结构化数据、不太适合处理图像、视频、音频和文本这些数据,决策树的训练速度非常快,并且构建决策树的过程易于理解;
2、神经网络适用的场景:
神经网络在所有的类型的数据上都表现良好,包括表格和一些结构化数据、或者是一些混合结构化和非结构化的数据,针对于图像、视频、音频和文本这些数据,神经网络是首选算法。缺点是训练时间较长,优势是可以利用迁移学习。
下面是他们各自的优缺点图示:
本周实践练习即上面所说的全部代码。