当前位置: 首页 > news >正文

机器学习-决策树【手撕】

决策树

1、概述

决策树是一个预测模型,它代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表某个可能的属性值,而每个叶节点则对应从根节点到该叶节点所经历的路径所表示的对象的值。

2、相关概念

在决策树流程学习中,寻找最优划分属性是决策树过程中的重点。那么如何在众多的属性中选择最优的呢

2.1 信息熵

Ent = - \sum_{k=1}^{K} p_k * log_2p_k
import collections
def entropy(label):counter = collections.Counter(label)ent = 0.0for num in counter.values():p = num/len(label)ent += -p*np.log2(p)return ent

2.2 信息增益

def information_gain(data,a):Ent = entropy(data)feature_class = data[a].value_counts()gain = 0for key in feature_class.keys():weight = feature_class[key]/data.shape[0]Ent_v = entropy(data.loc[data[a]==key])gain += weight * Ent_vreturn Ent - gain

2.3 信息增益率

信息增益率计算公式如下所示:
某数据集 D D D有若干属性值以及对应的值标记值,其样本大小为 ∣ D ∣ |D| D,其中取一个属性特征为feature,该属性包含 V V V个不同的取值,属性值为第 v v v个值的大小为 ∣ D v ∣ ( ∑ v = 1 V ∣ D v ∣ = ∣ D ∣ ) |D_v|(\sum_{v=1}^{V} |D^v| = |D|) Dv(v=1VDv=D),该属性的信息熵为 E n t ( f e a t u r e ) Ent(feature) Ent(feature),则该属性对应的信息增益率为

Gain_ratio(D,feature) = \frac{Gain(D,feature)} {Ent(feature)}

def gain_ratio(data , a):#先计算固有值intrinsic_valueIV_a = 0feature_class = data[a].value_counts()  # 特征有多少种可能for v in feature_class.keys():weight = feature_class[v]/data.shape[0]IV_a += -weight*np.log2(weight)gain_ration = cal_information_gain(data,a)/IV_areturn gain_ration

2.4 基尼系数

gini系数计算方式如下:
某数据集 D D D有若干属性值以及对应的标记值,其总样本大小为 ∣ D ∣ |D| D,该集合中样本标记类别总共有 K K K类,第 k k k类样本所占比例为 p k ( k = 1 , 2 , … , K p_k (k=1,2,…,K pk(k=1,2,,K)则该数据集对应的基尼系数为:

Gini(D) = 1 - \sum_{k=1}^Kp_{k}^2
#计算基尼指数
def gini(data):data_label = data.iloc[:, -1]label_num = data_label.value_counts() #有几类,每一类的数量res = 0for k in label_num.keys():p_k = label_num[k]/len(data_label)res += p_k ** 2return 1 - res

而取其中一个属性类型为 f e a t u r e feature feature,选定该类型的一个值为 v a l u e value value,根据 f e a t u r e feature feature这个属性是否为 v a l u e value value将数据集分为两个子集 D 1 D_1 D1 D 2 D_2 D2,则该属性的 f e a t u r e feature feature对应的 G i n i Gini Gini系数为:

Gini\_index(D,feature) = \frac{|D_1|}{|D|}Gini(D_1) +\frac{|D_2|}{|D|}Gini(D_2)

所以该函数中需要首先排除每一列中存在的重复值,计算出该值进行分类的Gini系数,计算出每个属性的每个非重复值的基尼系数,之后找到最小的Gini系数对应的属性维度以及对应值。

# 计算每个特征取值的基尼指数,找出最优切分点
def gini_index(data,a):feature_class = data[a].value_counts()res = []for feature in feature_class.keys():weight = feature_class[feature]/len(data)gini_value = gini(data.loc[data[a] == feature])res.append([feature, weight * gini_value])res = sorted(res, key = lambda x: x[-1])return res[0]

3、决策树

数据进行划分。

def split(feature, label, dimension):Len_label = len(label)#数据的长度a_d = np.unique(feature[:,dimension])#指定维度下的不同样本Len_a = len(a_d)#指定维度下的样本长度Feature = []Label = []split_feature = []split_label = []#初始化列表for j in range(Len_a):Feature.append(feature)Label.append(label)#对不同的a进行分类for j in range(Len_a):#从后往前判定,并删除不是该特征的数据for i in range(Len_label-1, -1, -1):if feature[i,dimension] != a_d[j]:Feature[j] = np.delete(Feature[j],i,axis=0)Label[j] = np.delete(Label[j],i,axis=0)#将输出结果整合为一个列表for j in range(Len_a):split_feature.append(Feature[j])split_label.append(Label[j])return split_feature,split_label

3.1 ID3决策树

通过寻找特征集合中信息增益最大属性,并进行分支。
输入:特征集合,标记集合
输出:该次划分的最佳信息增益,最佳划分维度S
计算信息增益公式:
某数据集 D D D有若干特征值以及对应的标记值,其总样本大小为 ∣ D ∣ |D| D,这里取一个属性类型 f e a t u r e feature feature,该特征包含 V V V个不同的取值,特征值为第 v v v个值的数量为 ∣ D v ∣ |D^v| Dv,则该特征值对应的信息增益为:

Gain(G,feature) = Ent(D) - \sum_{v=1}^K \frac{|D^v|}{D}Ent(D^v)

所以需要计算出每个特征的信息增益并输出,然后返回最大的信息增益并返回该特征的维数以及最大的信息增益值。

def one_split_ID3(feature,label):Ent_D = entropy(label)len_data, len_dimension = np.shape(feature)Gain = []for i in range(len_dimension):split_feature,split_label = split(feature,label,i) len_split = len(split_label)E = 0.0 for j in range(len_split):Ent_D_v = entropy(split_label[j])len_D_v = len(split_label[j])p = len_D_v/len_dataE = E + p*Ent_D_v gain = Ent_D - E Gain.append(gain)best_entropy = 0best_dimension = 0for i in range(len_dimension):if Gain[i] > best_entropy:best_entropy = Gain[i]best_dimension = i return best_entropy, best_dimension

3.2 C4.5决策树

与ID3决策树不同,通过遍历找出该属性集合中信息增益率,最大的属性,进行节点分支。

#输出每个特征的信息增益率,之后返回最大的信息增益率对应的属性维数以及最大的信息增益率
def one_split_C4_5(feature, label):#计算区域D的信息熵Ent_D = entropy(label)len_data,len_dimension = np.shape(feature)Grain_ratio = []#求出不同维度分类下的信息增益率for i in range(len_dimension):split_feature,split_label = split(feature, label, i)len_split = len(split_label)E = 0.0for j  in range(len_split):Ent_D_v = entropy(split_label[j] )len_D_v = len(split_label[j])p = len_D_v/len_dataE = E + p*Ent_D_vgrain = Ent_D - EEnt_f = entropy(feature[:,i])grain_ratio = grain / Ent_f#计算信息增益率Grain_ratio.append(grain_ratio)#通过比较得到最好的信息增益率以及维度best_entropy = 0best_dimension = 0for i in range(len_dimension):if Grain_ratio[i] > best_entropy:best_entropy = Grain_ratio[i]best_dimension = i  return best_entropy, best_dimension

3.3 CART决策树

CART决策树的结点划分,是遍历找出该属性集合中基尼系数(使用CART算法中的公式计算)最小的属性以及最佳的划分值

#找到最小的基尼系数对应的属性维数以及对应的分类值
def one_split_CART(feature, label):len_data,len_dimension = np.shape(feature)#初始化所需要求的三个值best_entropy = 10000.0best_dimension = 10000best_value =10000    for i in range(len_dimension):split_feature,split_label = split(feature, label, i)unique_f = np.unique(feature[:,i])len_uni = len(unique_f)      Gini_index = []       for j in unique_f:#初始化所用到的计数器count1 = 0count2 = 0count3 = 0count4 = 0           #对二分类问题中的不同情况进行计数for k in range(len_data):if feature[k,i] == j:count1 = count1 + 1count2 = count2 + label[k]if feature[k,i] != j:count3 = count3 +1count4 = count4 + label[k]           p1 = count2/count1p2 = count4/count3#实现基尼系数的计算公式gini_D_1 = (count1/len_data) * ((1-p1)*(1-p1) +p1* p1)gini_D_2 = (count3/len_data) * ((1-p2)*(1-p2) +p2* p2)gini_index = gini_D_1 + gini_D_2Gini_index.append(gini_index)                      #通过比较找到最好的维度以及对应的分类值    for d in range(len_uni):if Gini_index[d] < best_entropy:best_entropy = Gini_index[d]best_dimension = i#对应的维度值i即为最佳维度best_value = unique_f[d]#此d对应的分类值即为最佳分类值           return best_entropy,best_dimension,best_value

决策树剪枝

剪枝分为预剪枝和后剪枝,预剪枝指在决策树生成过程中对每个节点先进行估计,如果划分能够带来准确率的上升,则进行划分,否则就不划分该节点;后剪枝则是在先使用训练集生成决策树,再对每个节点进行评估,若将其中节点替换能够提升决策树准确率,则替换,否则则不替换。
一般情况下,后剪枝的欠拟合风险小,泛化能力强于预剪枝。所以,我们这边选择实现后剪枝方法。

import numpy as np
import pandas as pd
### 将数据进行转换
def drop_exist_feature(data,best_feature):attr = pd.unique(data[best_feature])new_data = [(nd,data[best_feature] == nd) for nd in attr]new_data = [(n[0],n[1].drop([best_feature],axis=1)) for n in new_data]return new_data
# 预测单条数据
def predict(Tree,test_data):first_feature = list(Tree.keys())[0]second_dict = Tree[first_feature]input_first = test_data.get(first_feature)input_value = second_dict[input_first]if is_dict(input_value,test_data): ## 判断是分支还是字典class_label = predict(input_value,test_data)else:class_label = input_valuereturn class_label
#测试很多案例,话返回准确率
def predict_more(Tree, test_data, test_label):cnt = 0#计算如果该节点不剪枝的准确率for i in range(len(test_data)):after_data = test_data.reset_index().loc[i].to_dict()pred = predict(Tree,  after_data)if pred == test_label[i]:cnt += 1return cnt / len(test_label)
#用于预测节点剪枝后的预测正确数
def equalNums(label, featPreLabel):res = 0for l in label:if l == featPreLabel:res += 1return res
# 后剪枝
def post_purnning(tree,test_data,test_label,names):newTree = tree.copy()names = np.asarray(names)# 取决策点的名称,即特征名称feature_name = list(tree.keys())[0]# 取特征的列featcol =  np.argwhere(names==feature_name)[0][0]names = np.delet(names,[featcol])newTree[feature_name] = tree[feature_name].copy()feature_value_dict = newTree[feature_name]feature_pre_label = feature_value_dict.pop('prun_label')# 分割测试数据,如果有数据的话,则进行测试或者递归调用split_data = drop_exist_feature(test_data,feature_name)split_data = dict(split_data)for feature_value in feature_value_dict.keys():if type(feature_value_dict[feature_value]) == dict:split_data_feature = split_data[feature_value]split_data_label = split_data[feature_value].iloc[:,-1].valuesnewTree[feature_name][feature_value] = post_purnning(   feature_value_dict[feature_value_dict],split_data_feature,split_data_label,split_data_feature.columns)ratio_PreDivision = equalNums(test_label, feature_pre_label) / test_label.size#计算如果该节点不剪枝的准确率ratioAfterDivision = predict_more(newTree, test_data, test_label)if ratioAfterDivision < ratio_PreDivision:newTree = feature_pre_label # 返回剪枝结果,其实也就是走到当前节点的数据最多的那一类return newTree

相关文章:

  • spawn_group_template | spawn_group | linked_respawn
  • 【Flink-CDC】Flink CDC 介绍和原理概述
  • 编码风格之(5)GNU软件编码风格(3)
  • c# MathNet.Numerics 圆拟合使用案例
  • 08章【文件与IO】
  • CMS如何调优
  • 如何在Docker下部署MinIO存储服务通过Buckets实现文件的远程上传
  • keil5 查看stm32 寄存器的值
  • MySQL对数据库的操作
  • 软件是什么?前端,后端,数据库
  • http503错误是什么原因
  • Redis(五)
  • 初识k8s(概述、原理、安装)
  • 微服务不死 — 共享变量在策略引擎项目的落地详解
  • cookie in selenium 定时更新token
  • 【译】理解JavaScript:new 关键字
  • DataBase in Android
  • Docker: 容器互访的三种方式
  • Java|序列化异常StreamCorruptedException的解决方法
  • Javascript 原型链
  • Kibana配置logstash,报表一体化
  • Mac转Windows的拯救指南
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • PAT A1120
  • Python3爬取英雄联盟英雄皮肤大图
  • Python学习笔记 字符串拼接
  • 初识 beanstalkd
  • 工作手记之html2canvas使用概述
  • 前端技术周刊 2019-01-14:客户端存储
  • 浅谈web中前端模板引擎的使用
  • 如何进阶一名有竞争力的程序员?
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 使用API自动生成工具优化前端工作流
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 赢得Docker挑战最佳实践
  • 云大使推广中的常见热门问题
  • Semaphore
  • 昨天1024程序员节,我故意写了个死循环~
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​批处理文件中的errorlevel用法
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • # Maven错误Error executing Maven
  • #13 yum、编译安装与sed命令的使用
  • (1)SpringCloud 整合Python
  • (Git) gitignore基础使用
  • (MATLAB)第五章-矩阵运算
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (强烈推荐)移动端音视频从零到上手(上)
  • (算法二)滑动窗口
  • (一)appium-desktop定位元素原理
  • (转) Face-Resources