目录
sklearn.tree.DecisionTreeClassifier 代码分析
工大菜凯,希望通过做笔记记录自己学的知识,也希望能帮助到同样在入门的同学 ❥侵权立删~
决策树法的基本知识点:
- 决策树是基于特征对实例进行分类的过程,分为3个步骤:特征选择、决策树生成、决策树剪枝(考虑全局最优,对已生成的树自下而上进行剪枝,将树变得更加简单,减少过拟合现象,提高树的泛化能力)
- 决策树生成学习局部的模型,而决策树剪枝学习整体的模型
- 决策树学习的本质:从训练数据集中归纳出一组分类规则,使其与训练数据集矛盾较小、具有较好的泛化能力
- 决策树是由结点和有向边组成的,其中结点又分为:内部结点(表示一个特征或属性)和叶结点(表示一个类)
- 决策树 If-then规则的集合:互斥并完备
- 由决策树的根节点-->叶节点的每一条路构建一条规则
- 路径上的内部结点的特征对应着规则的条件
- 叶结点的类对应着规则的结论
决策树生成的基本过程
- 训练数据都放在根结点,选择最优特征(如果特征数很多,在决策树学习开始前,可以只留下对训练数据有足够分类能力的特征)
- 根据特征将训练数据集分割成多个子集
- 使各个子集都有一个在当前条件下最好的分类
- 如果这些子集已经基本正确分类,则构建叶结点,将这些子集分到所对应的叶结点中去
- 如果还有子集不能被基本正确的分类,那么对这些子集选择新的最优特征,继续对其进行分割,构建相应的结点。
- 递归的进行下去,直到正确分类,就生成了一颗决策树
决策树特征选择的准则
- 特征选择的准则:信息增益、信息增益比
- 信息增益:表示在得知特征X的信息下而使得类Y的信息的不确定性减少的程度
- 信息增益比:在分类问题困难时,也就是说在训练数据集的经验熵大的时候,信息增益值会偏大,反之,信息增益会偏小。使用信息增益比可以对这一问题进行校正
- 熵:表示随机变量不确定性的度量(熵越大,随机变量的不确定性就越大)
条件熵
信息增益
- 信息增益大的特征具有更强的分类能力
:表示由于特征A而使得对数据集D的分类不确定性减少的程度
:表示对数据集D进行分类的不确定性
:表示在特征A给定的条件下对数据集D进行分类的不确定性
- 根据特征选择的信息增益准则方法:对训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征,做为该结点处的最优特征
信息增益算法
- 将训练数据集D分成K类
- 特征A有n个不同的取值,根据特征A的n个不同的取值将训练数据集D分成n个子集
- 由特征A不同的取值生成的子集中属于类的样本集合为
信息增益比的计算
信息增益算法的相关例题
题目:根据信息增益准则选择最优特征
- 将整个训练数据表分成两类 其中 k=1,2 (是,否)
代码分析:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log
import pprint
def create_data():
datasets = [['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否'],
]
labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']
# 前缀"u"代表 它们是Unicode字符串、"u"代表Unicode字符串的前缀
# 返回数据集和每个维度的名称
return datasets, labels
- 训练数据框的显示
datasets, labels = create_data()
train_data = pd.DataFrame(datasets, columns=labels)
# 经验熵H(D)函数定义
def calc_ent(datasets):
# 训练数据样本个数 data_length =15
data_length = len(datasets)
label_count = {}
for i in range(data_length):
# datasets[i][-1] 代表每一个样本的类别信息
label = datasets[i][-1]
# 这段代码是在判断一个标签是否在label_count字典中,如果不在则将其添加,并将对应的值设为0
if label not in label_count:
# label_count[label] = 0 将label作为键,将其对应的值设置为0
label_count[label] = 0
# 计算相同类别样本的数目
label_count[label] += 1
# 经验熵H(D)的计算
ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])
return ent
# 经验条件熵 H(D|A) 函数
def cond_ent(datasets, axis=0):
data_length = len(datasets)
# 创建一个空字典,用于存储每个特征值对应的样本子集
feature_sets = {}
for i in range(data_length):
# axis 是对特征选择
feature = datasets[i][axis]
# 判断特征值是否已经存在于字典中,如果不存在,则将其添加为键,对应的值初始化为空列表
if feature not in feature_sets:
feature_sets[feature] = []
# 将当前样本添加到对应特征值的样本子集中,形成对应特征的样本子集,便于样本子集经验熵的计算
feature_sets[feature].append(datasets[i])
# 其中p是子集列表
cond_ent = sum([(len(p)/data_length)*calc_ent(p) for p in feature_sets.values()])
return cond_ent
# 信息增益g(D,A)=H(D)-H(D|A) 函数
def info_gain(ent, cond_ent):
return ent - cond_ent
def info_gain_train(datasets):
# 题中count = 4 表示训练数据每个样本的特征数量 因为最后一个特征是样本的目标变量 所以要-1
count = len(datasets[0]) - 1
# 计算训练数据集的经验熵
ent = calc_ent(datasets)
# 创建一个的空列表 存储特征与对应特征的信息增益
best_feature = []
# 其中c代表特征的选择
for c in range(count):
c_info_gain = info_gain(ent, cond_ent(datasets, axis=c))
# 将每一个特征与其对应的信息增益,形成一个数据对存放在best_feature列表中
best_feature.append((c, c_info_gain))
print('特征({}) - info_gain - {:.3f}'.format(labels[c], c_info_gain))
# 比较各个特征下的信息增益大小
best_ = max(best_feature, key=lambda x: x[-1])
return '特征({})的信息增益最大,选择为根节点特征'.format(labels[best_[0]])
决策树生成算法
ID3算法
- ID3算法只有树的生成,容易产生过拟合现象
具体方法
- 从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立结点,再对子节点递归调用以上方法,构建决策树(知道3所有特征的信息增益均很小或没有特征可以选择为止)
ID3算法的相关例题
题目:对训练数据表,利用ID3建立决策树
- 由信息增益算法的相关例题可知,特征(有自己的房子)的信息增益值最大,所以选择特征作为根结点的特征
- 特征将训练数据集D划分为两个子集(取值为:“是”)和(取值为:“否”)。由于通过训练数据表可知,子集(取值为:“是”)只有同一类样本点(类别为:“是”),所以它成为一个叶结点,结点的类标记为:“是”
- 对(取值为:“否”),则需从特征(年龄)、(有工作)和(信贷情况)中选择新的特征,计算各个特征的信息增益
选择信息增益最大的特征(有工作)作为结点的特征,由于 (有工作)有两个可能的取值,从这一结点引出两个子结点:一个对应“是”(有工作)子结点(即 没有房 但有工作),包含3个羊样本,它们都属于同一类(类别:“是”),所以这是一个叶结点,类标记为:“是”;另一个对应“否”(无工作)的子结点(即 没有房 也没有工作),包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为“否”
ID3算法决策树生成代码分析
# 定义节点类 二叉树
class Node:
#__init__方法用于初始化节点对象 参数包括 root(布尔值,表示是否为根节点)label(节点标签)
# feature_name(特征名称)和 feature(特征值)
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 = {}
# 创建一个结果字典result
self.result = {'label:': self.label, 'feature': self.feature, 'tree': self.tree}
# __repr__ 是 Python 中的一个特殊方法,用于返回节点对象的字符串表示 作用:如图 1所示
def __repr__(self):
return '{}'.format(self.result)
# add_node 用于向结点的tree字典中添加子结点 有两个参数,val表示特征值 node 表示对应的子结点
# 在决策树模型中,每个结点都可以有多个子结点,每个子结点对应一个特征值
# 通过调用add_node方法,可以将子结点添加到结点tree字典中,以便构建决策树的结结构 作用:如图2所示
def add_node(self, val, node):
self.tree[val] = node
# 用于预测给定特征的样本所述的类别,该方法有一个参数features 表示输入样本的特征
# 根据输入样本的特征值features[self.feature]在当前结点的tree字典中找到对应的子结点
# 并递归调用子结点的predict方法来进行预测,实现在决策树上沿着特征值路径向下预测的过程
def predict(self, features):
if self.root is True:
return self.label
return self.tree[features[self.feature]].predict(features)
class DTree:
# 初始化决策树对象,其中epsilon是一个阈值参数
def __init__(self, epsilon=0.1):
self.epsilon = epsilon
# _tree 用于存储建立的决策树结构
self._tree = {}
# 熵
@staticmethod
def calc_ent(datasets):
data_length = len(datasets)
label_count = {}
for i in range(data_length):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])
return ent
# 经验条件熵
def cond_ent(self, datasets, axis=0):
data_length = len(datasets)
feature_sets = {}
for i in range(data_length):
feature = datasets[i][axis]
if feature not in feature_sets:
feature_sets[feature] = []
feature_sets[feature].append(datasets[i])
cond_ent = sum([(len(p)/data_length)*self.calc_ent(p) for p in feature_sets.values()])
return cond_ent
# 信息增益
# @staticmethod 是 Python 中的一个装饰器,用于将一个方法声明为静态方法。提高代码的可读性
# 静态方法不需要依赖类的实例,可以直接通过类名调用,并且在方法内部无法访问类的实例属性和方法
# 调用方法 DTree.calc_ent(datasets) 通过类名DTree直接调用静态方法calc_ent来计算数据集的熵
@staticmethod
def info_gain(ent, cond_ent):
return ent - cond_ent
def info_gain_train(self, datasets):
count = len(datasets[0]) - 1
ent = self.calc_ent(datasets)
best_feature = []
for c in range(count):
c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))
best_feature.append((c, c_info_gain))
# 比较大小
best_ = max(best_feature, key=lambda x: x[-1])
return best_
def train(self, train_data):
"""
input:数据集D(DataFrame格式),特征集A,阈值eta
output:决策树T
"""
# _ 代表训练数据集的所有样本及特征(除了最后一列样本目标标签)
# y_train 代表训练样本目标标签,选择训练数据集的所有行(:),并且选择最后一列目标标签
# features 代表训练数据集的所有列名(特征名),形成包含训练特征名的列表
_, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]
# 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T
# .value_counts()是一个pandas Series对象的方法,用于计算每个唯一值的频率
# len()函数用于获取这个频率计数结果的长度,也就是唯一值的数量
# len(y_train.value_counts()) == 1,表示训练数据集 y_train 中只有一个唯一的类别
if len(y_train.value_counts()) == 1:
return Node(root=True,
label=y_train.iloc[0])
# 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T
# 创建并返回一个Node对象,该对象表示一个决策树的结点
# y_train.value_counts()计算了每个唯一值的频率,并返回一个包含唯一值和对应频率的pandas
# Series对象。
# .sort_values(ascending=False)将频率按降序排列,以便找到最常见的类别
# 最后,.index[0]提取出排名第一的类别的索引值
# 整个返回语句的意思是:创建一个根节点,其标签为训练数据集中最常见的类别
if len(features) == 0:
return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])
# 3,计算最大信息增益 同5.1,Ag为信息增益最大的特征
max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
# max_feature表示的是最大信息增益特征的位置 不是最大信息增益的名称
# 使用max_feature作为索引,从features列表中获取相应的特征名称,并赋值给max_feature_name
max_feature_name = features[max_feature]
# 4,Ag的信息增益小于阈值epsilon,则置T为单节点树,并将D中是实例数最大的类Ck作为该节点的类标记,返回T
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)
# 看图3解释
feature_list = train_data[max_feature_name].value_counts().index
# 看图4 图5 解释 axis=1 表示对列进行操作
# sub_train_df为构建子树的训练数据集
for f in feature_list:
sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1)
# 6, 对 node_tree 递归生成树
# 看图6 解释
sub_tree = self.train(sub_train_df)
# f 父结点 sub_tree 子结点 递归生成决策树
node_tree.add_node(f, sub_tree)
#最后输出决策树
return node_tree
# 看图7 解释 用于训练决策树模型并返回生成的决策树 _tree 并可以使用该模型进行预测或其他操作
def fit(self, train_data):
self._tree = self.train(train_data)
return self._tree
# 看图8解释 predict是一个递归函数(自己中包含自己)
# 所以可以将测试样本沿着决策树从根节点开始依次判断
def predict(self, X_test):
return self._tree.predict(X_test)
# 根据训练数据集create_data 进行决策树模型训练
datasets, labels = create_data()
data_df = pd.DataFrame(datasets, columns=labels)
dt = DTree()
# 决策树模型训练 生成的模型赋值给tree
tree = dt.fit(data_df)
C4.5算法
- C4.5算法与ID3算法相似,C4.5算法对ID3算法进行了改进,C4.5算法在生成的过程中,用信息增益比来选择特征
最小二乘回归树的生成算法
- 用平方误差表示回归树对于训练数据的预测误差
遍历所有输入变量,找到最优的切分变量 j ,构成一对(j,s)依此将输入空间划分成两个区域,生成一颗回归树
分类树的生成
CART生成算法
- 给定输入随机变量X条件下输出变量Y的条件概率分布的学习方法
CART生成算法的相关例题
决策树的剪枝
- 决策树剪枝:通过极小化决策树整体的损失函数或代价函数来实现,为了减小模型复杂度(过拟合)
- 过拟合的原因:学习时过多地考虑如何提高训练数据的正确分类,从而构建出过于复杂的决策树
0 控制模型与训练数据的拟合程度和模型复杂度两者之间的影响
- 较大的促使选择较简单的模型
- 较小的促使选择较复杂的模型
- =0只考虑模型与训练数据的拟合程度,不考虑模型的复杂程度
当确定时
- 子树越大,与训练数据的拟合越好,模型复杂度越高
- 子树越小,与训练数据的拟合不好,模型复杂度越低
CART剪枝算法
sklearn.tree.DecisionTreeClassifier 代码分析
- criterion(准则): string (字符串), optional (default=”gini”) (默认为:基尼)
- The function to measure the quality of a split. Supported criteria are “gini” for the Gini impurity and “entropy” for the information gain.(用于衡量分割质量的函数。支持的标准有:"基尼 "表示基尼不纯度,"熵 "表示信息增益)
# data
def create_data():
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
# 前100行的(第一列(特征)、第二列(特征) 和倒数第一列(目标标签列)
data = np.array(df.iloc[:100, [0, 1, -1]])
# print(data)
return data[:,:2], data[:,-1]
# X代表各个样本的特征数据 y代表各个样本的目标标签
X, y = create_data()
# 将数据集分成70%为训练数据 30%为测试数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
import graphviz
# 调用sklearn 中的决策树算法,并利用训练数据进行决策树的训练
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train,)
tree_pic = export_graphviz(clf, out_file="mytree.pdf")
with open('mytree.pdf') as f:
dot_graph = f.read()