# encoding: utf-8
from utils import *
from DecisionNode import *
import numpy as np
from sklearn.model_selection import train_test_split
class Tree:
def __init__(self, min_samples_leaf=5, partition_rate=1, B1=5, B2=5, B3=None):
self.root = None
self.min_samples_leaf = min_samples_leaf
self.features_attr = None
self.b_1 = B1/2
self.b_2 = B2/2
self.b_3 = B3/2 if B3 != None else B3
self.partition_rate = partition_rate
self.criterion = cal_gini
assert self.b_1 >= 0
assert self.b_2 >= 0
assert self.b_3 == None or self.b_3 >= 0
def fit(self, X, y, features_attr=None):
assert len(features_attr) == X.shape[1]
np.random.seed()
self.features_attr = features_attr
X_e, X_s, y_e, y_s = train_test_split(X, y, test_size=self.partition_rate/(self.partition_rate+1))
structure_points = np.concatenate((np.array(X_s), np.array([y_s]).T), axis=1)
estimation_points = np.concatenate((np.array(X_e), np.array([y_e]).T), axis=1)
self.root = self.__build_tree(structure_points, estimation_points)
def predict(self, X):
if np.ndim(X) == 1:
return self.__predict_rec(X, self.root)
else:
result = []
for sample in X:
result.append(self.__predict_rec(sample, self.root))
return result
def __predict_rec(self, X, node):
global feature, threshold
if node.feature != -1:
feature = node.feature
threshold = node.threshold
if node.label is not None:
classLabel = feature, threshold, node.label
return classLabel
else:
feat_value = X[node.feature]
feat_attr = self.features_attr[node.feature]
threshold = node.threshold
if feat_value is None or feat_value is np.nan:
choice = np.random.randint(1, 3)
if choice == 1:
return self.__predict_rec(X, node.true_branch)
else:
return self.__predict_rec(X, node.false_branch)
else:
if feat_attr == 'd':
if feat_value == threshold:
return self.__predict_rec(X, node.true_branch)
else:
return self.__predict_rec(X, node.false_branch)
elif feat_attr == 'c':
if feat_value >= threshold:
return self.__predict_rec(X, node.true_branch)
else:
return self.__predict_rec(X, node.false_branch)
def __split(self, dataset, split_feature, threshold):
true_index = []
false_index = []
if self.features_attr[split_feature] == 'd':
for i in range(len(dataset)):
if dataset[i][split_feature] == threshold:
true_index.append(i)
else:
false_index.append(i)
elif self.features_attr[split_feature] == 'c':
for i in range(len(dataset)):
if dataset[i][split_feature] >= threshold:
true_index.append(i)
else:
false_index.append(i)
return true_index, false_index
def __split_pair(self, dataset, candidate_features):
current = self.criterion(dataset[:, -1])
ret = {}
for feat in candidate_features:
col = dataset[:, feat]
unique_col = np.unique(col)
attr = self.features_attr[feat]
ret[feat] = []
threshold_list = []
if attr == 'd' or unique_col.shape == 1:
threshold_list = unique_col
elif attr == 'c':
threshold_list = [(unique_col[i]+unique_col[i+1]) / 2 for i in range(len(unique_col)-1)]
for t in threshold_list:
true_index, false_index = self.__split(dataset, feat, t)
p = float(len(true_index)) / len(dataset)
gain = current - p * self.criterion(dataset[true_index, -1]) - \
(1-p) * self.criterion(dataset[false_index, -1])
ret[feat].append([gain, t])
ret[feat] = np.array(ret[feat])
ret[feat] = ret[feat][np.argsort(-ret[feat][:, 0])]
return ret
def __build_tree(self, structure_points, estimation_points):
if len(cal_label_dic(structure_points[:, -1])) == 1:
return DecisionNode(label=voting(cal_label_dic(estimation_points[:, -1])))
candidate_features = []
for i in range(structure_points.shape[1]-1):
if len(np.unique(structure_points[:, i])) > 1:
candidate_features.append(i)
if candidate_features == []:
return DecisionNode(label=voting(cal_label_dic(estimation_points[:, -1])))
info_gain_dict = self.__split_pair(structure_points, candidate_features)
info_gain_feat_max = []
for key, val in info_gain_dict.items():
info_gain_feat_max.append([key, val[0][0]])
info_gain_feat_max = np.array(info_gain_feat_max)
info_gain_feat_max = info_gain_feat_max[np.argsort(-info_gain_feat_max[:, 1])]
info_gain_feat_max_norm = self.b_1 * max_min_normalization(info_gain_feat_max[:, 1])
split_feature = int(info_gain_feat_max[mutinomial(info_gain_feat_max_norm)][0])
info_gain_chosen_feat_norm = self.b_2 * max_min_normalization(info_gain_dict[split_feature][:, 0])
threshold = info_gain_dict[split_feature][mutinomial(info_gain_chosen_feat_norm)][1]
true_index_s, false_index_s = self.__split(structure_points, split_feature, threshold)
true_index_e, false_index_e = self.__split(estimation_points, split_feature, threshold)
if len(true_index_e) == 0 or len(false_index_e) == 0:
return DecisionNode(label=voting(cal_label_dic(estimation_points[:, -1]), self.b_3))
if len(true_index_e) <= self.min_samples_leaf:
true_branch = DecisionNode(label=voting(cal_label_dic(estimation_points[true_index_e, -1])))
else:
true_branch = self.__build_tree(structure_points[true_index_s], estimation_points[true_index_e])
if len(false_index_e) <= self.min_samples_leaf:
false_branch = DecisionNode(label=voting(cal_label_dic(estimation_points[false_index_e, -1])))
else:
false_branch = self.__build_tree(structure_points[false_index_s], estimation_points[false_index_e])
return DecisionNode(feature=split_feature, threshold=threshold,
true_branch=true_branch, false_branch=false_branch)
这段代码定义了一个名为 Tree 的类,用于构建和使用决策树进行分类任务。下面详细介绍该类实现的具体功能:
1. 类的初始化
def __init__(self, min_samples_leaf=5, partition_rate=1, B1=5, B2=5, B3=None):
...
- 初始化决策树的一些参数:
min_samples_leaf:叶子节点所需的最小样本数,默认为 5。partition_rate:用于将数据集划分为结构点集和估计点集的比例,默认为 1。B1、B2、B3:用于归一化和决策的参数,其中B3可以为None。
- 初始化根节点
self.root为None,特征属性self.features_attr为None,并设置分裂准则self.criterion为cal_gini函数(该函数在代码中未给出,但推测是用于计算基尼不纯度)。 - 对参数
b_1、b_2、b_3进行断言检查,确保它们的值非负。
2. 模型拟合方法
def fit(self, X, y, features_attr=None):
...
- 检查
features_attr的长度是否与特征矩阵X的列数相等。 - 设置随机种子,以确保结果的可重复性。
- 存储特征属性
features_attr。 - 使用
train_test_split函数将数据集(X, y)划分为结构点集(X_s, y_s)和估计点集(X_e, y_e),划分比例由partition_rate决定。 - 将结构点集和估计点集分别合并为
structure_points和estimation_points。 - 调用
__build_tree方法递归地构建决策树,并将根节点赋值给self.root。
3. 预测方法
def predict(self, X):
...
- 处理输入数据
X的维度:- 如果
X是一维数组,则直接调用__predict_rec方法进行预测。 - 如果
X是二维数组,则对每个样本调用__predict_rec方法进行预测,并将结果存储在列表中返回。
- 如果
def __predict_rec(self, X, node):
...
- 递归地对样本
X进行预测:- 如果当前节点有标签,则返回该节点的特征、阈值和标签。
- 如果样本的特征值缺失(为
None或np.nan),则随机选择一个子节点继续递归预测。 - 根据特征的属性(离散
'd'或连续'c')和阈值,选择合适的子节点继续递归预测。
4. 数据划分方法
def __split(self, dataset, split_feature, threshold):
...
- 根据指定的特征
split_feature和阈值threshold,将数据集dataset划分为满足条件的true_index和不满足条件的false_index。 - 对于离散特征,比较特征值是否等于阈值;对于连续特征,比较特征值是否大于等于阈值。
def __split_pair(self, dataset, candidate_features):
...
- 计算每个候选特征在不同阈值下的信息增益:
- 首先计算当前数据集的基尼不纯度
current。 - 对于每个候选特征,找出其所有可能的阈值。
- 对于每个阈值,调用
__split方法将数据集划分为两部分,并计算信息增益。 - 将每个特征的信息增益和对应的阈值存储在字典
ret中,并按信息增益降序排序。
- 首先计算当前数据集的基尼不纯度
5. 决策树构建方法
def __build_tree(self, structure_points, estimation_points):
...
- 递归地构建决策树:
- 如果结构点集的标签只有一种,则返回一个叶子节点,标签为估计点集的多数标签。
- 找出所有可能的候选特征。
- 如果没有候选特征,则返回一个叶子节点,标签为估计点集的多数标签。
- 调用
__split_pair方法计算每个候选特征的信息增益。 - 对信息增益进行归一化处理,并根据
B1、B2参数选择最佳分裂特征和阈值。 - 根据最佳分裂特征和阈值将结构点集和估计点集分别划分为两部分。
- 如果某一部分的样本数为 0 或小于等于
min_samples_leaf,则返回一个叶子节点,标签为该部分的多数标签。 - 递归地构建左右子树,并返回一个决策节点。
总结
Tree 类实现了一个决策树分类器,支持离散和连续特征。在构建决策树时,使用基尼不纯度作为分裂准则,并通过信息增益选择最佳分裂特征和阈值。同时,该类支持处理缺失值,并可以根据用户指定的参数进行模型的训练和预测。

2210

被折叠的 条评论
为什么被折叠?



