我们之前学习了knn分类器,贝叶斯分类器,今天来学习一种新的模型,决策树分类器
决策树分类器,顾名思义是利用树形结构进行多次判断,从而达到分类的目的,我们先来看示例一,判断是否能通过面试
第一步我们先要生成决策树,现在有三列数据,1.是否为985 2.学历 3.技能,我们生成决策树时,要先对这些条件信息进行求熵值,若熵值最小则证明,波动性较小,信息更稳定,可信度更高,因此要先进行求熵,在求熵时,需要提出每一列的数据,因此我们要先得到每一列数据的编号,可以利用X.shape[1],代码如下
class Decision_Tree:
# 决策树初始化
def __init__(self):
self.Tree = {}
def fit_Decision_Tree(self):
# 信息熵列的表示
Column = list(range(X.shape[1]))
print(Column)
self.Tree.update(self.get_Decision_Tree(Column,X,y))
def get_Decision_Tree(self,Column, X, y):
# 假定最小的信息熵(初值要尽可能大的赋)
comentropy_min = 100
# 假定最小的信息熵的列
min_Column = Column[0]
# 提取出第i个熵值
for i in Column:
Col = X[:, i]
enti = sum(self.calculate_comentropy(y[Col == standard])for standard in set(Col))
if enti < comentropy_min:
comentropy_min = enti
min_Column = i
# 新建子树
subtree = {}
# 将当前最小熵值的这一列数据存储起来
min_Col = X[:, min_Column]
# 将最小熵值列这一列的编号删去
Column.remove(min_Column)
for d in set(min_Col):
current_comentropy = self.calculate_comentropy(y[min_Col == d])
if current_comentropy < 1e-10: # 可完全分开的情况
subtree[d] = y[min_Col == d][0]
else:
subtree[d] = self.get_Decision_Tree(Column.copy(),X[min_Col == d,:],y[min_Col == d])
return {min_Column: subtree}
def calculate_comentropy(self,Y_DS_index):
counter = Counter(Y_DS_index)
counter.values()
entropy = 0
for num in counter.values():
probability = num / len(Y_DS_index)
entropy += -probability * log2(probability)
return entropy
def predict_all(self,new_data_index):
Dictionary=self.Tree
predict_result=[]
for line in new_data_index:
self.predict_single(line,Dictionary,predict_result)
return predict_result
def predict_single(self,line_index,Dictionary_index,predict_result_index):
#获取预测数据的信息和编号
for key,value in Dictionary_index.items ():
if key in Column:
Dictionary = value
col=line_index[key]
for key1,value1 in Dictionary.items():
if col == key1:
Dictionary=value1
if Dictionary == 'Yes' or Dictionary == 'No':
predict_result_index.append (Dictionary)
return 0
self.predict_single(line_index,Dictionary,predict_result_index)
这段代码是一个类方法,把定义的方法写在了一个类里面,包括决策树的初始化
def __init__(self):
self.Tree = {}
因为我们的决策树的值的呈现方式是以字典的形式,因此我们在一开始定义存储结构时选择字典的形式,在进行完决策树的初始化操作以后,我们还需要对其进行决策树的预处理,声明列编号,并且将生成的子树放到决策树的结构之中,代码如下
def fit_Decision_Tree(self):
# 信息熵列的表示
Column = list(range(X.shape[1]))
print(Column)
self.Tree.update(self.get_Decision_Tree(Column,X,y))
在进行完决策树的初始化和预处理以后,下一步就是构建决策树了,我们先来捋顺一下构建决策树的思路,在构建决策树时,我们要先选择出熵值最小的那一列,在最初时可以先假定一列,并且对最小熵值进行赋值,注意,此时在进行赋初值时要尽可能的大,同时不要陷入误区,这个我们所赋的初值是我们随机给定的一个数,并不是我们所假定的这一列的真正最小初值,我们之后还会对于数据集的每一列进行遍历,然后一列一列的与最小熵值进行比较,如果比它小,就把这个新的最小值赋给它,直到获得真正的最小熵值,之后我们再对所获得的最下熵值进行数值判断,在此我们让它和1e-10去比较,如果比他小则证明能完全分开,理由如下
当我们进行不断的迭代终有一个时刻,会使得在当前列的标准下判断时全为yes或全为no,而我们能对其进行确定的依据是,当前层次下,我们进行的当前标准刚好能将其完全分开,yes或no的概率值为1,它在进行熵值运算时求得的结果为0,若当前层次仍无法将其分开,只能进行新一轮的迭代,直到某一次能够将其彻底分开,彻底分开时,熵值的计算结果一定为0,代码如下
def get_Decision_Tree(self,Column, X, y):
# 假定最小的信息熵(初值要尽可能大的赋)
comentropy_min = 100
# 假定最小的信息熵的列
min_Column = Column[0]
# 提取出第i个熵值
for i in Column:
Col = X[:, i]
enti = sum(self.calculate_comentropy(y[Col == standard])for standard in set(Col))
if enti < comentropy_min:
comentropy_min = enti
min_Column = i
# 新建子树
subtree = {}
# 将当前最小熵值的这一列数据存储起来
min_Col = X[:, min_Column]
# 将最小熵值列这一列的编号删去
Column.remove(min_Column)
for d in set(min_Col):
current_comentropy = self.calculate_comentropy(y[min_Col == d])
if current_comentropy < 1e-10: # 可完全分开的情况
subtree[d] = y[min_Col == d][0]
else:
subtree[d] = self.get_Decision_Tree(Column.copy(),X[min_Col == d,:],y[min_Col == d])
return {min_Column: subtree}
下面来对其进行具体的细节分析,在求熵值时,我们运用for循环,并使用集合的去重的特性,在筛选时将这一列的全部的情况与数据列进行遍历和匹配,再利用具体的定义的熵值计算函数求出当前标准下的熵值,并将每一部分加起来得到真正的熵值,在求完当前熵值后将此列拿去判断,如果能分开则将子树分开,若不能分开,则将当前标准下选中的数据进行新一轮迭代,直至最终符合条件,最后将子树放入到树结构中。
我们在上一段代码中运用到的求熵值的具体操作,代码如下
def calculate_comentropy(self,Y_DS_index):
counter = Counter(Y_DS_index)
counter.values()
entropy = 0
for num in counter.values():
probability = num / len(Y_DS_index)
entropy += -probability * log2(probability)
return entropy
在决策树构建完成后,下一步就是进行预测
我预测的思路有些剑走偏锋,仅为参考,我们创建的子树是这样的(如下图)
我发现每一层的树结构之间他们的键的类型是交替出现的,一次是所在的列,一次是所在列的值,因此我们可以以此为判断标准,如果键值是Yes或No,则证明已经预测到位,可以将结果进行输出,并存到事先准备好的列表中;如果键值不是Yes或No,则将当前层次中树的键作为遍历的标准,将符合条件的数据提取出来作为新的数据传入下一次迭代的函数中,但是存在一个问题,for循环和迭代交替使用很容易造成混乱,而且在添加预测结果时,因为一开始所设定的空列表会使得在迭代的过程中被不断的刷新,因此我们可以改变一下函数的结构,利用迭代只预测一次的结果,然后再用for循环进行多次预测,代码如下,具体细节再细细分析
方法一:
def predict_all(self,new_data_index):
Dictionary=self.Tree
predict_result=[]
for line in new_data_index:
self.predict_single(line,Dictionary,predict_result)
return predict_result
def predict_single(self,line_index,Dictionary_index,predict_result_index):
#获取预测数据的信息和编号
for key,value in Dictionary_index.items ():
if key in Column:
Dictionary = value
col=line_index[key]
for key1,value1 in Dictionary.items():
if col == key1:
Dictionary=value1
if Dictionary == 'Yes' or Dictionary == 'No':
predict_result_index.append (Dictionary)
return 0
self.predict_single(line_index,Dictionary,predict_result_index)
代码中有一个新学到的点,在单次迭代预测中,可以利用for循环对多个变量进行遍历,我们对键和键值同时进行遍历,我们在原有的思路上做了一次优化,因为我们已知树的结构和层与层之间键的分布规律,列标号---列的内容---列标号----列的内容,因此我们可以一次脱两层,并且,如果我们将树完全脱掉,即使后面再次还原,如果有别的并发执行的程序用到了树,这始终是一大隐患,因此我们可以在单次预测函数的上一层中将树做一次备份,然后将这个备份传给单次预测函数,其中要注意的事,虽然我们的备份在单次预测函数中被一层层的脱掉,但是如果在回到上层函数中,备份中的树结构依旧是完整的。
整体思路:我们只需要在总的预测函数中利用for循环去执行单次预测函数,并且提前备份树结构,将备份传入单次预测函数中,并且初始化预测结果列表,在单词循环函数中,通过两次遍历,每次按着列标号---列的内容---列标号----列的内容脱掉两层,然后再将键值传入单次预测函数中,但此时要注意,两次的剥离,键值value名称不能一致,否则会发生冲突,剥离到最后直到字典中只剩下Yes或No,这时候将结果传入到预测结果列表中,但此时要注意,需要在上层函数中返回预测结果,否则预测结果会显示None,同时,不要忘记在树的结果=Yes或No的情况下,添加结束标记,否则会因为无遍历数据而报错。以上就是本示例的全部内容。
以上的预测方式太过于复杂,还涉及迭代,我们可以利用while循环来实现
方法二:
方法一中我们使用for循环+迭代的方式,代码较为复杂,结构也比较麻烦,并且迭代函数需要注意的细节还要严格的分开各个区域,一旦有公共的交集便很容易出错,因此我们可以用while来替代代码如下
def predict(self,new_data_index):
predict_result = []
for i in range(len(new_data_index)):
Dictionary = self.Tree
while Dictionary!='Yes'and Dictionary!='No':
col=list(Dictionary.keys())[0]
Dictionary = Dictionary[col][new_data_index[i][col]]
predict_result.append(Dictionary)
return predict_result
我学到的东西:方法二的思路很巧妙,先对于要预测的数据进行循环,在提取出决策树关于列标号的键,下一步就是简化决策树,此时决策树等于‘Yes’或‘No’则停止循环,否则的话,要将决策树进行相应的简化,利用我们已知的决策树所显示的列标号的键,并以此为线索去提取出预测数据中符合条件的对应列。并以此列为决策树的限制条件对其进行进一步的简化,PS(事先将决策树进行备份,然后对备份进行修改),只要最后不是return ~,备份决策树的数据就不会发生改变,决策树就可以一直用下去。最后再将for循环完成后的数据添加到预测结果的列表中。除此之外,还学到了字典转化为数组的操作
字典在对数据进行提取时在字典后面直接写键名就行,操作简便,在对字典进行直接的list操作时,得到的只是他的键的列表
例如
基础较薄弱,萌新入手,欢迎大佬们指教