赞
踩
决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。
简单说就是依据熵值计算,不断地做出选择,直到获得最终结果(熵值为0或者在设定的某个范围内)。根据计算方法不同,有ID3算法、C4.5算法、CART算法等。
每个对象就是一个节点,开始的地方叫根节点,最后不能分的地方叫叶子节点。
剪枝是决策树停止分支的方法之一,剪枝有分预先剪枝和后剪枝两种。
预先剪枝是在树的生长过程中设定一个指标,当达到该指标时就停止生长,这样做容易产生“视界局限”,就是一旦停止分支,使得节点N成为叶节点,就断绝了其后继节点进行“好”的分支操作的任何可能性。
后剪枝中树首先要充分生长,直到叶节点都有最小的不纯度值为止,因而可以克服“视界局限”,而且无需保留部分样本用于交叉验证,所以可以充分利用全部训练集的信息。但后剪枝的计算量代价比预剪枝方法大得多,特别是在大样本集中,不过对于小样本的情况,后剪枝方法还是优于预剪枝方法的。
以信息增益为准则来选择划分属性,选择划分后信息增益最大的属性进行划分,这种算法对属性可取数目较多时有偏好。
这里举个例子,比如说有个女生要选相亲对象,相亲对象有收入(income)、身高(hight)、长相(look)、体型(shape)等条件可以考察,那么我们用ID3算法来分析以下她的决策。
我们收集了她之前研究过的几十个相亲对象和决策情况,如下表,我们把它保存为文件data01.csv
收入 | 身高 | 长相 | 体型 | 是否见面 |
一般 | 高 | 帅 | 胖 | 是 |
高 | 低 | 帅 | 胖 | 是 |
高 | 低 | 普通 | 瘦 | 是 |
一般 | 低 | 丑 | 瘦 | 否 |
低 | 中 | 丑 | 匀称 | 否 |
低 | 中 | 丑 | 匀称 | 否 |
低 | 中 | 普通 | 瘦 | 否 |
一般 | 高 | 普通 | 匀称 | 是 |
高 | 高 | 帅 | 胖 | 是 |
高 | 中 | 帅 | 胖 | 是 |
低 | 中 | 普通 | 匀称 | 否 |
一般 | 低 | 丑 | 胖 | 否 |
高 | 低 | 普通 | 胖 | 是 |
一般 | 高 | 丑 | 瘦 | 否 |
一般 | 高 | 普通 | 匀称 | 是 |
一般 | 高 | 普通 | 瘦 | 是 |
一般 | 中 | 丑 | 胖 | 是 |
一般 | 中 | 帅 | 匀称 | 是 |
高 | 高 | 帅 | 胖 | 是 |
高 | 中 | 帅 | 胖 | 是 |
低 | 高 | 普通 | 瘦 | 是 |
高 | 低 | 普通 | 胖 | 是 |
低 | 低 | 丑 | 胖 | 否 |
低 | 中 | 丑 | 胖 | 否 |
高 | 低 | 普通 | 瘦 | 是 |
低 | 高 | 丑 | 瘦 | 否 |
一般 | 高 | 帅 | 匀称 | 是 |
高 | 低 | 普通 | 胖 | 是 |
低 | 中 | 丑 | 胖 | 否 |
一般 | 高 | 丑 | 瘦 | 是 |
然后我们用ID3算法来分析:
- from math import log
- import operator
- import numpy as np
- import pandas as pd
- from pandas import DataFrame, Series
- #构建决策和数字的映射
- productDict = {'高':1,'一般':2,'低':3,'中':2, '帅':1, '普通':2, '丑':3, '胖':3, '匀称':2,'瘦':1, '是':1, '否':0}
- #导入数据
- def Importdata(datafile):
- dataa = pd.read_excel(datafile) # datafile是excel文件,所以用read_excel,如果是csv文件则用read_csv
- #将文本中不可直接使用的文本变量替换成数字
-
- dataa['income'] = dataa['收入'].map(productDict) # 将每一列中的数据按照字典规定的转化成数字
- dataa['hight'] = dataa['身高'].map(productDict)
- dataa['look'] = dataa['长相'].map(productDict)
- dataa['shape'] = dataa['体型'].map(productDict)
- dataa['is_meet'] = dataa['是否见面'].map(productDict)
-
- data = dataa.iloc[:,5:].values.tolist() # 取量化后的几列,去掉文本列
- b = dataa.iloc[0:0,5:-1]
- labels = b.columns.values.tolist() # 将标题中的值存入列表中
-
- return data,labels
-
- #计算数据的熵(entropy)--原始熵
- def dataentropy(data, feat):
- lendata = len(data) # 数据条数
- labelCounts = {} # 数据中不同类别的条数
- for featVec in data:
- category = featVec[-1] # 每行数据的最后一个字(叶子节点)
- if category not in labelCounts.keys():
- labelCounts[category] = 0
- labelCounts[category] += 1 # 统计有多少个类以及每个类的数量
- entropy = 0
- for key in labelCounts:
- prob = float(labelCounts[key]) / lendata # 计算单个类的熵值
- entropy -= prob * log(prob,2) # 累加每个类的熵值
-
- return entropy
-
- #对数据按某个特征value进行分类
- def splitData(data,i,value):
- splitData = []
- for featVec in data:
- if featVec[i] == value:
- rfv = featVec[:i]
- rfv.extend(featVec[i+1:])
- splitData.append(rfv)
-
- return splitData
-
- #选择最优的分类特征
- def BestSplit(data):
- numFea = len(data[0]) - 1 # 计算一共有多少个特征,因为最后一列一般是分类结果,所以需要-1
- baseEnt = dataentropy(data,-1) # 定义初始的熵,用于对比分类后信息增益的变化
- bestInfo = 0
- bestFeat = -1
- for i in range(numFea):
- featList = [rowdata[i] for rowdata in data]
- uniqueVals = set(featList)
- newEnt = 0
- for value in uniqueVals:
- subData = splitData(data,i,value) # 获取按照特征value分类后的数据
- prob = len(subData) / float(len(data))
- newEnt += prob * dataentropy(subData,i) # 按特征分类后计算得到的熵
- info = baseEnt - newEnt # 原始熵与按特征分类后的熵的差值,即信息增益
- if (info > bestInfo): # 若按某特征划分后,若infoGain大于bestInf,则infoGain对应的特征分类区分样本的能力更强,更具有代表性。
- bestInfo = info # 将infoGain赋值给bestInf,如果出现比infoGain更大的信息增益,说明还有更好地特征分类
- bestFeat = i # 将最大的信息增益对应的特征下标赋给bestFea,返回最佳分类特征
-
- return bestFeat
-
- #按分类后类别数量排序,取数量较大的
- def majorityCnt(classList):
- c_count = {}
- for i in classList:
- if i not in c_count.keys():
- c_count[i] = 0
- c_count[i] += 1
- ClassCount = sorted(c_count.items(),key=operator.itemgetter(1),reverse=True) # 按照统计量降序排序
-
- return ClassCount[0][0] # reverse=True表示降序,因此取[0][0],即最大值
-
- #构建树
- def createTree(data,labels):
-
- classList = [rowdata[-1] for rowdata in data] # 取每一行的最后一列,分类结果(1/0)
- #print(classList)
- if classList.count(classList[0]) == len(classList):
- return classList[0]
- if len(data[0]) == 1:
- return majorityCnt(classList)
- bestFeat = BestSplit(data) # 根据信息增益选择最优特征
- bestLab = labels[bestFeat]
- myTree = {bestLab:{}} # 分类结果以字典形式保存
- del(labels[bestFeat])
- featValues = [rowdata[bestFeat] for rowdata in data]
- uniqueVals = set(featValues)
- for value in uniqueVals:
- subLabels = labels[:]
- myTree[bestLab][value] = createTree(splitData(data,bestFeat,value),subLabels)
-
- return myTree
-
- #主程序
- datafile = 'data/dateperson/date01.xlsx' # 文件所在位置
- data, labels = Importdata(datafile) # 导入数据
-
- jc=createTree(data, labels) # 输出决策树模型结果
-
- print(jc)
执行之后得到如下结果:
- PS C:\coding\machinelearning>ID3相亲决策实验.py
- {'income': {1: 1, 2: {'hight': {1: {'look': {1: 1, 2: 1, 3: {'shape': {0: 0, 1: 1}}}}, 2: 1, 3: 0}}, 3: {'hight': {1: {'look': {2: 1, 3: 0}}, 2: 0, 3: 0}}}}
- PS C:\coding\machinelearning>
从结果来看,小姐姐找相亲对象,首先看收入,收入高的一定见,收入中等的再看身高、长相等。收入低的除非又高又帅,否则免谈。
选择具有最大增益率的属性作为划分属性,具体做法是先从候选划分属性中找到信息增益高于平均水平的属性,再从中选择增益率最高的作为划分属性。
下面再用C4.5算法把上面小姐姐相亲的例子再算一遍
- from math import log
- import operator
- import numpy as np
- import pandas as pd
- from pandas import DataFrame, Series
- productDict = {'高':1,'一般':2,'低':3,'中':2, '帅':1, '普通':2, '丑':3, '胖':3, '匀称':2,'瘦':1, '是':1, '否':0}
- #导入数据
- def Importdata(datafile):
- dataa = pd.read_excel(datafile) # datafile是excel文件,所以用read_excel,如果是csv文件则用read_csv
- #将文本中不可直接使用的文本变量替换成数字
-
- dataa['income'] = dataa['收入'].map(productDict) # 将每一列中的数据按照字典规定的转化成数字
- dataa['hight'] = dataa['身高'].map(productDict)
- dataa['look'] = dataa['长相'].map(productDict)
- dataa['shape'] = dataa['体型'].map(productDict)
- dataa['is_meet'] = dataa['是否见面'].map(productDict)
-
- data = dataa.iloc[:,5:].values.tolist() # 取量化后的几列,去掉文本列
- b = dataa.iloc[0:0,5:-1]
- labels = b.columns.values.tolist() # 将标题中的值存入列表中
-
- return data,labels
-
- #计算数据的熵(entropy)--原始熵
- def dataentropy(data, feat):
- lendata = len(data) # 数据条数
- labelCounts = {} # 数据中不同类别的条数
- for featVec in data:
- category = featVec[-1] # 每行数据的最后一个字(叶子节点)
- if category not in labelCounts.keys():
- labelCounts[category] = 0
- labelCounts[category] += 1 # 统计有多少个类以及每个类的数量
- entropy = 0
- for key in labelCounts:
- prob = float(labelCounts[key]) / lendata # 计算单个类的熵值
- entropy -= prob * log(prob,2) # 累加每个类的熵值
-
- return entropy
-
- #对数据按某个特征value进行分类
- def splitData(data,i,value):
- splitData = []
- for featVec in data:
- if featVec[i] == value:
- rfv = featVec[:i]
- rfv.extend(featVec[i+1:])
- splitData.append(rfv)
-
- return splitData
-
- #选择最优的分类特征
- def BestSplit(data):
- numFea = len(data[0]) - 1 # 计算一共有多少个特征,因为最后一列一般是分类结果,所以需要-1
- baseEnt = dataentropy(data, -1) # 定义初始的熵,用于对比分类后信息增益的变化
- bestGainRate = 0
- bestFeat = -1
- for i in range(numFea):
- featList = [rowdata[i] for rowdata in data]
- uniqueVals = set(featList)
- newEnt = 0
- for value in uniqueVals:
- subData = splitData(data,i,value) # 获取按照特征value分类后的数据
- prob = len(subData) / float(len(data))
- newEnt += prob * dataentropy(subData, i) # 按特征分类后计算得到的熵
- info = baseEnt - newEnt # 原始熵与按特征分类后的熵的差值,即信息增益
- splitonfo = dataentropy(subData,i) # 分裂信息
- if splitonfo == 0: # 若特征值相同(eg:长相这一特征的值都是帅),即splitonfo和info均为0,则跳过该特征
- continue
- GainRate = info / splitonfo # 计算信息增益率
- if (GainRate > bestGainRate): # 若按某特征划分后,若infoGain大于bestInf,则infoGain对应的特征分类区分样本的能力更强,更具有代表性。
- bestGainRate = GainRate # 将infoGain赋值给bestInf,如果出现比infoGain更大的信息增益,说明还有更好地特征分类
- bestFeat = i # 将最大的信息增益对应的特征下标赋给bestFea,返回最佳分类特征
- return bestFeat
-
-
- def majorityCnt(classList):
- c_count = {}
- for i in classList:
- if i not in c_count.keys():
- c_count[i] = 0
- c_count[i] += 1
- ClassCount = sorted(c_count.items(),key=operator.itemgetter(1),reverse=True)#按照统计量降序排序
-
- return ClassCount[0][0]#reverse=True表示降序,因此取[0][0],即最大值
- #构建树
- def createTree(data,labels):
- classList = [rowdata[-1] for rowdata in data] # 取每一行的最后一列,分类结果(1/0)
- if classList.count(classList[0]) == len(classList):
- return classList[0]
- if len(data[0]) == 1:
- return majorityCnt(classList)
- bestFeat = BestSplit(data) # 根据信息增益选择最优特征
- bestLab = labels[bestFeat]
- myTree = {bestLab:{}} # 分类结果以字典形式保存
- del(labels[bestFeat])
- featValues = [rowdata[bestFeat] for rowdata in data]
- uniqueVals = set(featValues)
- for value in uniqueVals:
- subLabels = labels[:]
- myTree[bestLab][value] = createTree(splitData(data,bestFeat,value),subLabels)
-
- return myTree
-
-
-
- #主程序
- datafile = 'data/dateperson/date01.xlsx' # 文件所在位置
- data, labels = Importdata(datafile) # 导入数据
- jc=createTree(data, labels) # 输出决策树模型结果
-
- print(jc)
得到结果和上面的算法大致差不多
- PS C:\coding\machinelearning>C4.5相亲决策实验.py
- {'income': {1: 1, 2: {'look': {1: 1, 2: 1, 3: {'shape': {1: {'hight': {0: 0, 1: 1}}, 3: {'hight': {0: 0, 1: 1}}}}}}, 3: {'shape': {0: 0, 1: 1}}}}
- PS C:\coding\machinelearning>
选择划分后基尼指数最小的属性最为最优划分属性。
这次我们找了个数据集,Titanic号生还者情况的数据集,来分析下乘客的生还情况
- #数据处理的库
- from numpy.lib.type_check import real
- import pandas as pd
- #数据分类
- from sklearn.model_selection import train_test_split
- #算法库
- from sklearn.tree import DecisionTreeClassifier
- from sklearn.preprocessing import LabelEncoder
- from sklearn.model_selection import GridSearchCV
-
- #评估用到的库
- from sklearn.metrics import make_scorer
- from sklearn.metrics import accuracy_score
- from sklearn.metrics import f1_score
- from sklearn.metrics import recall_score
- from sklearn.metrics import precision_score
- #预测
-
- import numpy as np
-
- #读取数据
- data = pd.read_csv('data/titanic/train.csv')
- print(data.head())
- data.info()
- # 计算各特征缺失总数
- total = data.isnull().sum().sort_values(ascending=False)
- # 计算各特征缺失比例
- percent = (data.isnull().sum()/data.isnull().count()).sort_values(ascending = False)
- miss_data = pd.concat([total, percent], axis = 1, keys = ['Miss_Total', 'Miss_Percent'])
- miss_data.head()
- # 缺失值处理。
- # 删除‘Cabin’
- del data['deck']
- # 采用中位数填充缺失值
- data['age'] = data['age'].fillna(data['age'].median())
- # 众数填充缺失值
- data['embark_town'] = data['embark_town'].fillna(data['embark_town'].mode()[0])
- # 查看数据情况
- data.info()
-
- # 观察Name特征提取其中的Title称呼
- #data['Title'] = data['Name'].str.split(",", expand=True)[1].str.split(".", expand=True)[0]
- # 将字符型变量做数值化处理
- label = LabelEncoder()
- data['sex'] = label.fit_transform(data['sex'])
- data['class'] = label.fit_transform(data['class'])
- data['alone'] = label.fit_transform(data['alone'])
- #data['Embarked'] = data['Embarked'].astype(str)
- data['embark_town'] = label.fit_transform(data['embark_town'])
- # 考虑到PassengerId和Ticker为随机生成的变量,不作为影响目标变量的信息,因此特征选择时,将其去除
- features = ['class', 'age', 'n_siblings_spouses', 'parch', 'fare', 'sex', 'alone', 'embark_town','survived']
- data = data[features]
- data.head()
-
- #划分训练集和测试集
- X = data[['class', 'age', 'n_siblings_spouses', 'parch', 'fare', 'sex', 'alone', 'embark_town']]
- y = data[['survived']]
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2)#random_state为随机种子,确保每次划分的结果是相同的
-
- #训练模型
- dtc = DecisionTreeClassifier()
- dtc.fit(X_train, y_train)
- y_predict = dtc.predict(X_test)
-
-
- # 模型评分:准确率,查全率,查准率,F1得分
- accuracyScore = accuracy_score(y_test, y_predict)
- recallScore = recall_score(y_test, y_predict)
- precisionScore = precision_score(y_test, y_predict)
- f1Score = f1_score(y_test, y_predict)
- print("DecisionTreeClassifier Results")
- print("Accuracy :", accuracyScore)
- print("Recall :", recallScore)
- print("Precision :", precisionScore)
- print("F1 Score :", f1Score)
-
-
- param = {'max_depth': [1, 3, 5, 7]}
- # 采用网格搜索进行参数调优
- gsearch = GridSearchCV(estimator=dtc, param_grid=param, cv=5, scoring='f1')
- gsearch.fit(X=X_train, y=y_train)
- print("最优参数:{}".format(gsearch.best_params_))
- print("最优模型:{}".format((gsearch.best_estimator_)))
- print("模型最高分:{:.3f}".format(gsearch.score(X_test, y_test)))
-
-
-
- # 选择最优模型进行预测
- dtc = DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
- max_features=None, max_leaf_nodes=None,
- min_samples_leaf=1, min_samples_split=2,
- min_weight_fraction_leaf=0.0, random_state=None,
- splitter='best')
- dtc.fit(X_train, y_train)
- y_predict = dtc.predict(X_test[:10])
- # 打印预测结果
- print('===================预测值=======================')
- print(y_predict)
- # 打印真实值
- print('===================真实值=======================')
- #print(np.array(y_test[:10]).tolist())
- realz = np.array(y_test[:10]).ravel()
- print(realz)
- Accuracy = accuracy_score(realz, y_predict)
- print('准确率为:{:.2f}%'.format(Accuracy*100))
-
打印预测结果如下:
- PS C:\coding\machinelearning>CART实验(titanic).py
- survived sex age n_siblings_spouses parch fare class deck embark_town alone
- 0 0 male 22.0 1 0 7.2500 Third unknown Southampton n
- 1 1 female 38.0 1 0 71.2833 First C Cherbourg n
- 2 1 female 26.0 0 0 7.9250 Third unknown Southampton y
- 3 1 female 35.0 1 0 53.1000 First C Southampton n
- 4 0 male 28.0 0 0 8.4583 Third unknown Queenstown y
- <class 'pandas.core.frame.DataFrame'>
- RangeIndex: 627 entries, 0 to 626
- Data columns (total 10 columns):
- # Column Non-Null Count Dtype
- --- ------ -------------- -----
- 0 survived 627 non-null int64
- 1 sex 627 non-null object
- 2 age 627 non-null float64
- 3 n_siblings_spouses 627 non-null int64
- 4 parch 627 non-null int64
- 5 fare 627 non-null float64
- 6 class 627 non-null object
- 7 deck 627 non-null object
- 8 embark_town 627 non-null object
- 9 alone 627 non-null object
- dtypes: float64(2), int64(3), object(5)
- memory usage: 49.1+ KB
- <class 'pandas.core.frame.DataFrame'>
- RangeIndex: 627 entries, 0 to 626
- Data columns (total 9 columns):
- # Column Non-Null Count Dtype
- --- ------ -------------- -----
- 0 survived 627 non-null int64
- 1 sex 627 non-null object
- 2 age 627 non-null float64
- 3 n_siblings_spouses 627 non-null int64
- 4 parch 627 non-null int64
- 5 fare 627 non-null float64
- 6 class 627 non-null object
- 7 embark_town 627 non-null object
- 8 alone 627 non-null object
- dtypes: float64(2), int64(3), object(4)
- memory usage: 44.2+ KB
- DecisionTreeClassifier Results
- Accuracy : 0.7698412698412699
- Recall : 0.7142857142857143
- Precision : 0.7
- F1 Score : 0.7070707070707072
- 最优参数:{'max_depth': 5}
- 最优模型:DecisionTreeClassifier(max_depth=5)
- 模型最高分:0.731
- ===================预测值=======================
- [1 1 0 1 0 0 0 1 0 0]
- ===================真实值=======================
- [1 0 0 1 0 0 0 1 0 0]
- 准确率为:90.00%
- PS C:\coding\machinelearning>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。