目录
一、什么是决策树
决策 + 树,我们的决策过程就像树的成长一样。由一个根(节点)不断的发散。
决策树是一种树形结构,其中每个内部节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶节点代表一种类别。
叶节点:存放决策结果
内部节点:内部属性及其对应输出,按照输出选择分支
决策过程:从根节点出发,根据数据的各个属性,计算结果,选择对应的输出分支,直到达到叶子节点,得到结果
下面给出一个比较形象的例子:
当一个男生追求你的时候,总不能随随便便答应他吧!需要选择一下:
相信看到这,已经对于决策树有了初步的了解。
二、决策树学习
一般由三个步骤组成:特征选择、决策树生成、决策树修剪
Quinlan
在1986年提出的ID3
算法、1993年提出的C4.5
算法Breiman
等人在1984年提出的CART
算法
决策树学习本质:从训练数据集中归纳出一组分类规则。
- 需要一个与训练数据矛盾较小的决策树,同时具有很好的泛化能力。
- 决策树学习的损失函数:通常是正则化的极大似然函数。
- 决策树学习的策略:损失函数为目标函数的最小化。
1、特征选择
决策树泛化能力差,需要剪枝。
上述三个准则:
1、ID3:样本集合D对特征A的信息增益
- 熵越大,随机变量的不确定性就越大
- 信息增益(information gain)表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。
- 选择信息增益大的
2、样本集D对特征A 的信息增益比(C4.5)
3、样本集合D的 基尼指数(CART)
4、python代码展示:
def get_data():
datasets = [['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否'],
]
labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'分类']
# 字符串前加 u, 后面字符串以 Unicode 格式 进行编码,一般用在中文字符串前面,防止乱码
return datasets, labels;
# ---------书上贷款例子-----------------
datasets, labels = get_data()
def cal_entropy(datasets): # 经验熵H(D)
data_len = len(datasets)
label_count = {}
for i in range(data_len):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
entropy = -sum([(p / data_len) * log(p / data_len, 2) for p in label_count.values()])
return entropy
def cond_entropy(datasets, axis=0): # 经验条件熵H(D|A)
data_len = len(datasets)
feature_set = {}
for i in range(data_len):
feature = datasets[i][axis]
if feature not in feature_set:
feature_set[feature] = []
feature_set[feature].append(datasets[i])
cond_ent = sum([(len(p) / data_len) * cal_entropy(p) for p in feature_set.values()])
return cond_ent
def info_gain(entropy, cond_ent): # 信息增益
return entropy - cond_ent
def info_gain_train(datasets): # 基于特征信息增益的特征选择
count = len(datasets[0]) - 1
entropy = cal_entropy(datasets)
best_feature = []
for i in range(count):
info_gain_i = info_gain(entropy, cond_entropy(datasets, axis=i))
best_feature.append((i, info_gain_i))
print("特征({})- info_gain - {:.3f}".format(labels[i], info_gain_i))
best_feature_i = max(best_feature, key=lambda x: x[-1])
print("特征({})的信息增益最大,选为根节点的特征".format(labels[best_feature_i[0]]))
info_gain_train(np.array(datasets))
2、决策树生成
准则使用说明:使用信息增益最大、信息增益比最大或基尼指数最小作为特征选择的准则。
从根节点开始,递归地形成决策树。
ID3
算法只有树的生成,所以该算法生成的树容易产生过拟合C4.5
算法与ID3
算法相似,进行了改进。C4.5
在生成的过程中,用信息增益比来选择特征。
python代码:
class Node():
def __init__(self, root=True, label=None, feature_name=None, feature=None):
self.root = root
self.label = label
self.feature_name = feature_name
self.feature = feature
self.tree = {}
self.result = {
'label:': self.label,
'feature:': self.feature,
'tree:': self.tree
}
def __repr__(self): # 类似str方法,更侧重程序员调试
print('{}'.format(self.result))
def add_node(self, val, node):
self.tree[val] = node
def predict(self, features):
if self.root is True:
return self.label
return self.tree[features[self.feature]].predict(features)
class DTree():
def __init__(self, epsilon=0.1): # 信息增益阈值, < epsilon 时,结束决策树展开
self.epsilon = epsilon
self._tree = {}
@staticmethod
def cal_entropy(datasets): # 经验熵H(D)
data_len = len(datasets)
label_count = {}
for i in range(data_len):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
entropy = -sum([(p / data_len) * log(p / data_len, 2) for p in label_count.values()])
return entropy
def cond_entropy(self, datasets, axis=0): # 经验条件熵H(D|A)
data_len = len(datasets)
feature_set = {}
for i in range(data_len):
feature = datasets[i][axis]
if feature not in feature_set:
feature_set[feature] = []
feature_set[feature].append(datasets[i])
cond_ent = sum([(len(p) / data_len) * self.cal_entropy(p) for p in feature_set.values()])
return cond_ent
@staticmethod
def info_gain(entropy, cond_ent): # 信息增益
return entropy - cond_ent
def info_gain_train(self, datasets): # 基于特征信息增益的特征选择
count = len(datasets[0]) - 1
entropy = self.cal_entropy(datasets)
best_feature = []
for i in range(count):
info_gain_i = info_gain(entropy, cond_entropy(datasets, axis=i))
best_feature.append((i, info_gain_i))
print("特征({})- info_gain - {:.3f}".format(labels[i], info_gain_i))
best_feature_i = max(best_feature, key=lambda x: x[-1])
return best_feature_i
def train(self, train_data):
'''
:input: 数据集D(DataFrame格式),特征集A,阈值eta
:return: 决策树DT
'''
_, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]
# 1. 若所有D实例都属于同一分类,不用分了,直接返回那个类
if len(y_train.value_counts()) == 1:
return Node(root=True, label=y_train.iloc[0])
# 2. 若没有特征A,返回D中数量最多的分类
if len(features) == 0:
return Node(root=True, label=y_train.value_counts().sort_values(
ascending=False).index[0])
# 3. 计算最大信息增益,取为特征
max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
max_feature_name = features[max_feature]
# 4. 如果信息增益小于阈值epsilon,置为单节点,将实例数最大的类作为节点标记
if max_info_gain < self.epsilon:
return Node(root=True, label=y_train.value_counts().sort_values(
ascending=False).index[0])
# 5. 构建Ag子集
node_tree = Node(root=False, feature_name=max_feature_name, feature=max_feature)
feature_list = train_data[max_feature_name].value_counts().index
for f in feature_list:
sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1)
# 6. 递归生成树
sub_tree = self.train(sub_train_df)
node_tree.add_node(f, sub_tree)
return node_tree
def fit(self, train_data):
self._tree = self.train(train_data)
return self._tree
def predict(self, X_test):
return self._tree.predict(X_test)
train_data = pd.DataFrame(datasets, columns=labels)
dt = DTree()
tree = dt.fit(train_data)
print(dt.predict(['老年', '否', '否', '一般']))
print(dt.predict(['青年', '否', '是', '一般']))
print(dt.predict(['中年', '是', '否', '好']))
print(dt.predict(['老年', '否', '是', '一般']))
3、决策树的剪枝
学习时,考虑准确性,树复杂,过拟合,泛化能力差,需要剪枝。
方法:极小化决策树整体损失函数