目录
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
用于理解决策树模型,并以此进行复现
一、ID3算法是什么?
ID3(Iterative Dichotomiser 3)算法是一种用于构建决策树的经典算法,由Ross Quinlan在1986年提出。下面是关于ID3算法的优点和缺点:
优点:
1. 简单易懂:ID3算法使用信息增益作为特征选择的准则,其基本思想直观易懂,容易理解和实现。
2. 高效快速:ID3算法采用自顶向下递归构建决策树,每次选择信息增益最大的特征进行划分,因此算法执行速度相对较快。
3. 可处理多类别特征:ID3算法能够处理多类别的分类问题,不限制特征的取值类型。
缺点:
1. 对缺失数据敏感:ID3算法不能直接处理缺失数据,当训练集存在缺失数据时,会导致特征选择受到影响,进而影响生成的决策树模型。
2. 容易过拟合:ID3算法倾向于选择具有较多取值的特征进行划分,这样容易导致产生复杂的决策树模型,可能出现过拟合的情况,特别是当训练数据噪声较大时。
3. 不支持连续特征:ID3算法的特征选择只考虑了离散特征,对于连续特征,需要进行离散化处理才能应用于ID3算法。
需要注意的是,ID3算法是决策树构建算法的基础,并且被后续的C4.5和CART算法等进一步改进和优化。因此,实际应用中可以根据具体情况选择更适合的决策树算法。
二、算法详解
2.1 信息熵
2.2 条件熵
假设训练集中某个属性有 个属性值,则会产生
个分 支,第
个分支包含了取该属性值的所有样本。 若训练集中有
个样本,第
个分支包含了
个,则该结 点的权重为
,即样本数越多的分支结点影响越大。那么 经过划分后条件熵为
其中是第
个分支的信息熵。
2.3 伪代码
函数 ID3(数据集 D, 特征集 A)
创建节点 node
if 数据集 D 中所有实例属于同一类别 C:
将 node 标记为 C 类别叶节点
返回 node
if 特征集 A 为空集:
将 node 标记为数据集 D 中实例数最多的类别叶节点
返回 node
选择最优特征 best_feature = 通过计算信息增益选择最大的特征
将 node 标记为选择的最优特征 best_feature
for best_feature 的每个取值 value:
令 data_subset = 根据 best_feature 的取值将数据集 D 分割成子集
if data_subset 为空集:
创建叶节点,并将其标记为数据集 D 中实例数最多的类别叶节点
将该节点加到 node 作为子节点
else:
递归调用 ID3(data_subset, A - {best_feature}),并将返回的子树根节点加到 node 作为子节点
返回 node
三、算法复现
3.1 导入基本库
import numpy as np
import copy
3.2 信息熵函数
def entropy(labels): # 计算数据集的信息熵
unique_labels, label_counts = np.unique(labels, return_counts=True) # 获取目标变量的唯一值以及统计次数
total_count = len(labels) # 总样本量个数
prob = label_counts / total_count # 该唯一值的占比
ent = -np.sum(prob * np.log2(prob)) # 计算信息熵
return ent
3.3 无特征可用,返回实例集中最多的类别
def majority_vote(labels): # 多数表决确定叶节点标签
unique_values, counts = np.unique(labels, return_counts=True) # 使用numpy.unique()函数统计每个元素的出现次数
majority_label = unique_values[np.argmax(counts)] # 找到出现次数最多的元素
return majority_label
3.4 返回最大信息增益对应的特征列
def choose_best_feature(data): # 选择最佳划分特征,返回索引
labels = data[:,-1] # 获取当前样本集的目标变量,固定-1表示最后一列是标签
base_entropy = entropy(labels) # 获取当前样本集的信息熵
best_info_gain = 0.0 # 设定初始信息增益为0
best_column = -1 # 设定初始最优特征列为-1
for index in range(data.shape[1]-1): # 获取当前样本集的每一列
unique_values = np.unique(data[:,index]) # 获取当前样本集的第index列的唯一值
new_entropy = 0.0 # 当前index列的条件熵初始设为0
sub_data = data[:,[index,-1]] # 获取子数据集(仅包含两列):第index列,目标变量
for value in unique_values: # 遍历第index列的所有特征值
label_i = sub_data[sub_data[:,0]==value,1] # 根据特定的特征值value划分出的样本子集,并只获取目标变量这一列
new_entropy += len(label_i)/len(labels) * entropy(label_i) # 计算index列的特征值value对应的节点的条件熵,并进行累加
info_gain = base_entropy - new_entropy # index列的信息增益 = 当前样本集的信息熵-index列的条件熵
if info_gain > best_info_gain: # 获取信息增益最大对应的index列
best_info_gain = info_gain
best_column = index
return best_column # 返回信息增益最大对应的index列
3.5 分支节点切割
def split_data(data,index_column,value_column): # 给定index列,以及其某个特征值value
# 筛选满足条件的样本
filtered_data = data[data[:, index_column] == value_column] # 获取index列中所有特征值为value的样本
return np.delete(filtered_data, index_column, axis=1) # 剔除index列,并返回
3.6 递归构建决策树
def create_decision_tree(data,columns): # data是numpy格式,包含目标变量
# 目标变量索引。从0开始,且固定为最后一列
# columns为列名称
labels = data[:,-1]
# 如果数据集中的所有实例属于同一类别,则返回该类别
if len(set(labels)) == 1:
return labels[0]
# 如果没有特征可用,返回实例集中最多的类别
if data.shape[1] == 1:
return majority_vote(labels)
best_column_index = choose_best_feature(data)
# 获取的是信息增益最大的特征列对应的索引
bestcolumn=columns[best_column_index] # 依据索引获取特征列名称
decision_tree = {bestcolumn: {}} # 构建当前节点下的决策树
del(columns[best_column_index] ) # 列名称列表剔除已选取的最优特征
uniqueVals=set(data[:,best_column_index]) # 获取已选取的最优特征的唯一值
for value in uniqueVals: # 依据已选取的最优特征的唯一值,进行数据切割,并进行下一节点的树构建
sub_columns = copy.deepcopy(columns)
decision_tree[bestcolumn][value] = create_decision_tree(split_data(data, best_column_index, value), sub_columns)
return decision_tree
3.7 美观输出决策树
def print_decision_tree(decision_tree, indent=''):
# 遍历并输出决策树
# 当前节点为叶节点时,直接输出结果
if isinstance(decision_tree, str):
print(indent + decision_tree)
return
# 当前节点为内部节点时,继续遍历子节点
for key, value in decision_tree.items():
print(indent + key + ":")
if isinstance(value, dict):
print_decision_tree(value, indent + ' ')
else:
print(indent + ' ' + value)
3.8 预测
def DecisionTree_predict(decision_tree, Columns, test_sample):
root_feature = list(decision_tree.keys())[0] # 获取根节点的特征
root_dict = decision_tree[root_feature] # 获取根节点的取值对应的子树
root_feature_index = Columns.index(root_feature) # 获取根节点特征在特征列表中的索引
for value in root_dict: # 遍历根节点取值对应的子树的所有可能取值
if test_sample[root_feature_index] == value: # 如果测试样本的特征值与当前取值相等
if isinstance(root_dict[value], dict): # 如果该取值对应的子树还是一个字典(非叶子节点)
class_label = DecisionTree_predict(root_dict[value], Columns, test_sample) # 递归向下查询子树
else: # 如果该取值对应的子树是一个标签(叶子节点)
class_label = root_dict[value] # 将该标签作为分类结果
return class_label # 返回分类结果
四、实例验证
4.1 获取数据集
def createDataSet(): # 创造示例数据
dataSet=[['青绿','蜷缩','浊响','清晰','凹陷','硬滑','好瓜'],
['乌黑','蜷缩','沉闷','清晰','凹陷','硬滑','好瓜'],
['乌黑','蜷缩','浊响','清晰','凹陷','硬滑','好瓜'],
['青绿','蜷缩','沉闷','清晰','凹陷','硬滑','好瓜'],
['青绿','稍蜷','浊响','清晰','稍凹','软粘','好瓜'],
['乌黑','稍蜷','浊响','稍糊','稍凹','软粘','好瓜'],
['乌黑','稍蜷','浊响','清晰','稍凹','硬滑','好瓜'],
['乌黑','稍蜷','沉闷','稍糊','稍凹','硬滑','坏瓜'],
['青绿','硬挺','清脆','清晰','平坦','软粘','坏瓜'],
['浅白','蜷缩','浊响','模糊','平坦','软粘','坏瓜'],
['青绿','稍蜷','浊响','稍糊','凹陷','硬滑','坏瓜'],
['浅白','稍蜷','沉闷','稍糊','凹陷','硬滑','坏瓜'],
['乌黑','稍蜷','浊响','清晰','稍凹','软粘','坏瓜'],
['青绿','蜷缩','沉闷','稍糊','稍凹','硬滑','坏瓜']]
labels = ['色泽','根蒂','敲声','纹理','脐部','触感',"目标"] #六个特征+1个特征列
return dataSet,labels
data,Columns = createDataSet()
data = np.array(data)
4.2 调用算法
columns = copy.deepcopy(Columns)
decision_tree = create_decision_tree(data,columns)
print_decision_tree(decision_tree) # 输出决策树结果
4.3 进行预测
testSample=['浅白','硬挺','清脆','模糊','平坦','硬滑'] # 待测样本
DecisionTree_predict(decision_tree,Columns,testSample)
总结
依据ID3算法,进行复现。在复现中,尽可能所有运算只通过numpy实现,减少for循环的调用,做到减少运算量,加快运算结果。