AI-机器学习-自学笔记(五)决策树算法

        决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。

        简单说就是依据熵值计算,不断地做出选择,直到获得最终结果(熵值为0或者在设定的某个范围内)。根据计算方法不同,有ID3算法、C4.5算法、CART算法等。

        每个对象就是一个节点,开始的地方叫根节点,最后不能分的地方叫叶子节点。

剪枝

        剪枝是决策树停止分支的方法之一,剪枝有分预先剪枝和后剪枝两种。

        预先剪枝是在树的生长过程中设定一个指标,当达到该指标时就停止生长,这样做容易产生“视界局限”,就是一旦停止分支,使得节点N成为叶节点,就断绝了其后继节点进行“好”的分支操作的任何可能性。

        后剪枝中树首先要充分生长,直到叶节点都有最小的不纯度值为止,因而可以克服“视界局限”,而且无需保留部分样本用于交叉验证,所以可以充分利用全部训练集的信息。但后剪枝的计算量代价比预剪枝方法大得多,特别是在大样本集中,不过对于小样本的情况,后剪枝方法还是优于预剪枝方法的。

ID3算法

        以信息增益为准则来选择划分属性,选择划分后信息增益最大的属性进行划分,这种算法对属性可取数目较多时有偏好。

这里举个例子,比如说有个女生要选相亲对象,相亲对象有收入(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算法

        选择具有最大增益率的属性作为划分属性,具体做法是先从候选划分属性中找到信息增益高于平均水平的属性,再从中选择增益率最高的作为划分属性。

下面再用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>

CART算法

选择划分后基尼指数最小的属性最为最优划分属性。

这次我们找了个数据集,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>

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值