from random import choice
from collections import Counter
import math
# ==========
# 定义数据集
# ==========
D = [
{'色泽': '青绿', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'},
{'色泽': '乌黑', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'},
{'色泽': '乌黑', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'},
{'色泽': '青绿', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'},
{'色泽': '浅白', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': '是'},
{'色泽': '青绿', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '软粘', '好瓜': '是'},
{'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '稍糊', '脐部': '稍凹', '触感': '软粘', '好瓜': '是'},
{'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '硬滑', '好瓜': '是'},
{'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '沉闷', '纹理': '稍糊', '脐部': '稍凹', '触感': '硬滑', '好瓜': '否'},
{'色泽': '青绿', '根蒂': '硬挺', '敲声': '清脆', '纹理': '清晰', '脐部': '平坦', '触感': '软粘', '好瓜': '否'},
{'色泽': '浅白', '根蒂': '硬挺', '敲声': '清脆', '纹理': '模糊', '脐部': '平坦', '触感': '硬滑', '好瓜': '否'},
{'色泽': '浅白', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '模糊', '脐部': '平坦', '触感': '软粘', '好瓜': '否'},
{'色泽': '青绿', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '稍糊', '脐部': '凹陷', '触感': '硬滑', '好瓜': '否'},
{'色泽': '浅白', '根蒂': '稍蜷', '敲声': '沉闷', '纹理': '稍糊', '脐部': '凹陷', '触感': '硬滑', '好瓜': '否'},
{'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '软粘', '好瓜': '否'},
{'色泽': '浅白', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '模糊', '脐部': '平坦', '触感': '硬滑', '好瓜': '否'},
{'色泽': '青绿', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '稍糊', '脐部': '稍凹', '触感': '硬滑', '好瓜': '否'},
]
# ==========
# 决策树生成类
# ==========
class DecisionTree:
def __init__(self, D, label, chooseA):
self.D = D # 数据集
self.label = label # 属性"好瓜" 作为标签
self.chooseA = chooseA # 划分方法:根据信息增益
self.A = list(filter(lambda key: key != label, D[0].keys())) # 属性集合A(当然要去除"好瓜"这一判断属性了)
# 获得A的每个className属性的可选项,加入到A_item集合中,如 色泽:对应 {'乌黑', '青绿', '浅白'}
self.A_item = {}
for a in self.A:
self.A_item.update({a: set(self.getClassValues(D, a))}) # 此处的set,有个集合去重操作
'''
# print("self.A_item is" ,self.A_item)
最终的A_item:
{
'色泽': {'乌黑', '青绿', '浅白'},
'根蒂': {'硬挺', '稍蜷', '蜷缩'},
'敲声': {'清脆', '浊响', '沉闷'},
'纹理': {'清晰', '稍糊', '模糊'},
'脐部': {'稍凹', '凹陷', '平坦'},
'触感': {'硬滑', '软粘'}
}
'''
self.root = self.generate(self.D, self.A) # 生成树并保存根节点
# print("self.root is", self.root)
# 获得D中所有className属性的值
def getClassValues(self, D, className):
'''
:param D: 数据集
:param className: 每个className
:return: D中className属性对应的所有值
'''
return list(map(lambda sample: sample[className], D))
# D中样本是否在A的每个属性上相同
def isSameInA(self, D, A):
for a in A:
types = set(self.getClassValues(D, a))
#print("types is ", types)
if len(types) > 1:
return False
return True
# 构建决策树,递归生成节点
def generate(self, D, A):
'''
:param D: 数据集
:param A: 所有className属性(不包含"好瓜")
:return:
'''
node = {} # 生成节点
remainLabelValues = self.getClassValues(D, self.label)
# D中的所有标签 如['是', '是', '是', '是', '是', '是', '是', '是', '否', '否', '否', '否', '否', '否', '否', '否', '否']
remainLabelTypes = set(remainLabelValues)
# D中含有哪几种标签 如{'是', '否'}
if len(remainLabelTypes) == 1: # 当前节点包含的样本全属于同个类别,无需划分
return remainLabelTypes.pop() # 标记Node为叶子结点,值为仅存的标签
most = max(remainLabelTypes, key=remainLabelValues.count) # D占比最多的标签 此处为"是"8个,"否"9个(此处为"否")
if len(A) == 0 or self.isSameInA(D, A):
# 当前属性集为空,或是所有样本在所有属性上取值相同,无法划分
return most # 标记Node为叶子结点,值为占比最多的标签
# ******划分:******
a = self.chooseA(D, A, self) # a即为计算过的信息增益中(值最大)的className属性,作为节点,此时算出为"纹理"
print("a(每次选的最大信息增益属性) is ", a)
for type in self.A_item[a]:
condition = (lambda sample: sample[a] == type) # 决策条件
remainD = list(filter(condition, D)) # 过滤掉不满足condition条件的样本
#print("remainD is ",remainD)
if len(remainD) == 0:
# 当前节点包含的样本集为空,不能划分
node.update({type: most}) # 标记Node为叶子结点,值为占比最多的标签
else:
# 继续对剩下的样本按其余属性划分
remainA = list(filter(lambda x: x != a, A)) # 未使用的属性
_node = self.generate(remainD, remainA) # 递归生成子代节点
node.update({type: _node}) # 把生成的子代节点更新到当前节点
return {a: node}
# ==========
# 定义划分方法
# ==========
# 信息熵
def Ent(D, label, a, a_v):
'''
:param D:
:param label:
:param a: 某一具体className属性 如"色泽"
:param a_v: 此属性对应的具体值 ,如"青绿、乌黑、浅白"三种中的一个
:return:
'''
D_v = filter(lambda sample: sample[a] == a_v, D) # 如此处,举个例子,选的是色泽="青绿"的瓜,共6个
D_v = map(lambda sample: sample[label], D_v) # 将色泽="青绿"的瓜按标签分类为好瓜=3个,坏瓜=3个
D_v = list(D_v)
D_v_length = len(D_v)
counter = Counter(D_v)
info_entropy = 0
for k, v in counter.items(): # 计算色泽="青绿"信息熵
p_k = v / D_v_length
info_entropy += p_k * math.log(p_k, 2)
return -info_entropy
# 信息增益
def information_gain(D, A, tree: DecisionTree):
gain = {}
for a in A:
gain[a] = 0
values = tree.getClassValues(D, a)
counter = Counter(values)
# print("counter.items() is", counter.items())
for a_v, nums in counter.items(): # 分别计算所有className属性的信息增益,此处以属性=色泽为例注释
gain[a] -= (nums / len(D)) * Ent(D, tree.label, a, a_v) # 分别计算"青绿、乌黑、浅白"所对应的(信息熵 *该属性值所占比)
#print("gain is ", gain)
return max(gain.keys(), key=lambda key: gain[key]) # 选择信息增益最大的那个属性作为节点,此时算出来是"纹理"
# ==========
# 创建决策树
# ==========
if __name__ == '__main__':
desicionTreeRoot = DecisionTree(D, label='好瓜', chooseA=information_gain).root
print('决策树:', desicionTreeRoot)
最终生成的决策树如下:
a(每次选的最大信息增益属性) is 纹理
a(每次选的最大信息增益属性) is 触感
a(每次选的最大信息增益属性) is 根蒂
a(每次选的最大信息增益属性) is 色泽
a(每次选的最大信息增益属性) is 触感
决策树: {'纹理': {'稍糊': {'触感': {'软粘': '是', '硬滑': '否'}}, '清晰': {'根蒂': {'稍蜷': {'色泽': {'乌黑': {'触感': {'软粘': '否', '硬滑': '是'}}, '浅白': '是', '青绿': '是'}}, '蜷缩': '是', '硬挺': '否'}}, '模糊': '否'}}
有话就说, 感谢ww提供的材料~哈哈游表示很开心哈