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

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

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>

你可能感兴趣的:(AI自学笔记,决策树,机器学习,算法)