import numpy as np
import matplotlib.pyplot as plt
from public_tests import *
%matplotlib inline
plt.rcParams["font.family"] = ["sans-serif","SimHei"]
plt.rcParams['axes.unicode_minus'] = False
X_train = np.array([[1,1,1],[1,0,1],[1,0,0],[1,0,0],[1,1,1],[0,1,1],[0,0,0],[1,0,1],[0,1,0],[1,0,0]])
y_train = np.array([1,1,0,0,1,0,0,1,1,0])
print ('训练集X_train的形状:', X_train.shape)
print ('训练集y_train的形状: ', y_train.shape)
print ('训练样本数量 (m):', len(X_train))
def compute_entropy(y):
"""
计算给定节点的熵
参数:
y (ndarray): Numpy数组,表示节点中每个样本是否可食用(1)或有毒(0)
返回:
entropy (float): 该节点的熵值
"""
entropy = 0.
if len(y) == 0:
return 0.0
p1 = np.sum(y == 1) / len(y)
p0 = 1 - p1
if p1 > 0:
entropy -= p1 * np.log2(p1)
if p0 > 0:
entropy -= p0 * np.log2(p0)
return entropy
print("根节点的熵: ", compute_entropy(y_train))
compute_entropy_test(compute_entropy)
def split_dataset(X, node_indices, feature):
"""
根据给定特征将节点数据分割为左右子节点
参数:
X (ndarray): 形状为(n_samples, n_features)的数据矩阵
node_indices (ndarray): 包含活动索引的列表,即当前步骤考虑的样本
feature (int): 用于分割的特征索引
返回:
left_indices (ndarray): 特征值为1的索引
right_indices (ndarray): 特征值为0的索引
"""
left_indices = []
right_indices = []
for i in node_indices:
if X[i, feature] == 1:
left_indices.append(i)
else:
right_indices.append(i)
return left_indices, right_indices
root_indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
feature = 0
left_indices, right_indices = split_dataset(X_train, root_indices, feature)
print("左子节点索引: ", left_indices)
print("右子节点索引: ", right_indices)
split_dataset_test(split_dataset)
def compute_information_gain(X, y, node_indices, feature):
"""
计算在给定特征上分割节点的信息增益
参数:
X (ndarray): 形状为(n_samples, n_features)的数据矩阵
y (array like): 包含n_samples目标变量的列表或ndarray
node_indices (ndarray): 包含活动索引的列表,即当前步骤考虑的样本
返回:
information_gain (float): 计算得到的信息增益
"""
left_indices, right_indices = split_dataset(X, node_indices, feature)
X_node, y_node = X[node_indices], y[node_indices]
X_left, y_left = X[left_indices], y[left_indices]
X_right, y_right = X[right_indices], y[right_indices]
information_gain = 0
node_entropy = compute_entropy(y_node)
left_entropy = compute_entropy(y_left)
right_entropy = compute_entropy(y_right)
w_left = len(left_indices) / len(node_indices)
w_right = len(right_indices) / len(node_indices)
weighted_entropy = w_left * left_entropy + w_right * right_entropy
information_gain = node_entropy - weighted_entropy
return information_gain
info_gain0 = compute_information_gain(X_train, y_train, root_indices, feature=0)
print("在棕色帽特征上分割根节点的信息增益: ", info_gain0)
info_gain1 = compute_information_gain(X_train, y_train, root_indices, feature=1)
print("在锥形茎形状特征上分割根节点的信息增益: ", info_gain1)
info_gain2 = compute_information_gain(X_train, y_train, root_indices, feature=2)
print("在单独生长特征上分割根节点的信息增益: ", info_gain2)
compute_information_gain_test(compute_information_gain)
def get_best_split(X, y, node_indices):
"""
返回分割节点数据的最佳特征
参数:
X (ndarray): 形状为(n_samples, n_features)的数据矩阵
y (array like): 包含n_samples目标变量的列表或ndarray
node_indices (ndarray): 包含活动索引的列表,即当前步骤考虑的样本
返回:
best_feature (int): 最佳分割特征的索引
"""
num_features = X.shape[1]
best_feature = -1
max_info_gain = 0
for feature in range(num_features):
info_gain = compute_information_gain(X, y, node_indices, feature)
if info_gain > max_info_gain:
max_info_gain = info_gain
best_feature = feature
return best_feature
best_feature = get_best_split(X_train, y_train, root_indices)
print("最佳分割特征: %d" % best_feature)
get_best_split_test(get_best_split)
tree = []
def build_tree_recursive(X, y, node_indices, branch_name, max_depth, current_depth):
"""
使用递归算法构建树,将数据集在每个节点分割为2个子组。
此函数仅打印树结构。
参数:
X (ndarray): 形状为(n_samples, n_features)的数据矩阵
y (array like): 包含n_samples目标变量的列表或ndarray
node_indices (ndarray): 包含活动索引的列表,即当前步骤考虑的样本。
branch_name (string): 分支名称。['Root', 'Left', 'Right']
max_depth (int): 结果树的最大深度。
current_depth (int): 当前深度。递归调用期间使用的参数。
"""
if current_depth == max_depth:
formatting = " "*current_depth + "-"*current_depth
print(formatting, "%s 叶子节点,索引为" % branch_name, node_indices)
return
if len(np.unique(y[node_indices])) == 1:
formatting = " "*current_depth + "-"*current_depth
print(formatting, "%s 纯节点,所有样本属于类别 %d" % (branch_name, y[node_indices][0]))
return
best_feature = get_best_split(X, y, node_indices)
tree.append((current_depth, branch_name, best_feature, node_indices))
formatting = "-"*current_depth
print("%s 深度 %d, %s: 基于特征 %d 分割" % (formatting, current_depth, branch_name, best_feature))
left_indices, right_indices = split_dataset(X, node_indices, best_feature)
build_tree_recursive(X, y, left_indices, "左", max_depth, current_depth+1)
build_tree_recursive(X, y, right_indices, "右", max_depth, current_depth+1)
build_tree_recursive(X_train, y_train, root_indices, "根", max_depth=4, current_depth=0)