机器学习:决策树练习

实验介绍

1.实验内容

本实验包括:

  • 学习并实现决策树算法----以ID3算法为例
  • 基于决策树算法预测隐形眼镜类型

2.实验目标

通过本实验掌握决策树算法的基本原理。

3.实验知识点

  • 香农熵
  • 信息增益
  • 决策树算法基本原理

4.实验环境

  • python 3.6.5
  • numpy 1.13.3
  • matplotlib 2.2.3

实验准备

点击屏幕右上方的下载实验数据模块,选择下载decision_tree_glass.tgz到指定目录下,然后再依次选择点击上方的File->Open->Upload,上传刚才下载的数据集压缩包,再使用如下命令解压:

!tar -zxvf decision_tree_glass.tgz
decision_tree_glass/
decision_tree_glass/lenses.txt
decision_tree_glass/classifierStorage.txt

【海洋动物分类】

实验步骤:【海洋动物分类】- 概述

下表数据包含5个海洋动物,特征包括:不浮出水面是否可以生存,以及是否有脚蹼。我们将这些动物分成两类:鱼类和非鱼类。本实验要求基于决策树算法(ID3)实现对下表数据的分类。

id 不浮出水面是否可以生存 是否有脚蹼 属于鱼类
1
2
3
4
5
import math
import numpy as np
import matplotlib.pyplot as plt
import os

实验步骤:【海洋动物分类】- 创建数据集

基于上述表格,创建数据集。

def createDataSet():
    """
    函数说明:创建数据集
    returns:
        dataSet - 数据集
        labels - 分类属性
    """
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing','flippers']
    return dataSet, labels

实验步骤:【海洋动物分类】- 计算香农熵

def calcShannonEnt(dataSet):
    """
    函数说明:计算给定数据集的香农熵
    parameters:
        dataSet - 数据集
    returns:
        shannonEnt - 香农熵
    """
    # 获取数据集的长度,即样本数
    numEntries = len(dataSet)
    
    # 创建一个字典,用于统计分类标签的出现次数
    labelCounts = {}
    
    # 遍历数据集的每一行,统计标签的出现频率
    for featVec in dataSet:
        currentLabel = featVec[-1]  # 标签在每行的最后一列
        if currentLabel not in labelCounts:
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    
    # 计算香农熵
    shannonEnt = 0.0
    for label in labelCounts:
        prob = labelCounts[label] / numEntries  # 类别的概率
        shannonEnt -= prob * math.log2(prob)  # 香农熵公式

    return shannonEnt

实验步骤:【海洋动物分类】- 特征选择(基于信息增益划分数据集)

信息增益是相对于特征而言的,信息增益越大,特征对最终的分类结果影响也就越大,我们就应该选择对最终分类结果影响最大的那个特征作为我们的分类特征。

splitDataSet函数是用来选择各个特征的子集的。chooseBestFeatureToSplit函数是选择选择最优特征的函数。

def splitDataSet(dataSet, axis, value):
    """
    函数说明: 按照给定特征划分数据集
    parameters:
        dataSet - 待划分的数据集
        axis - 划分数据集的特征 (第axis个特征)
        value - 特征值
    returns:
        retDataSet - 划分后的数据集
    """
    retDataSet = []
    
    # 遍历数据集
    for featVec in dataSet:
        if featVec[axis] == value:  # 如果特征值匹配
            reducedFeatVec = featVec[:axis]  # 取出特征值之前的部分
            reducedFeatVec.extend(featVec[axis+1:])  # 加上特征值之后的部分
            retDataSet.append(reducedFeatVec)  # 加入到新的数据集中
            
    return retDataSet

def chooseBestFeatureToSplit(dataSet):
    """
    函数说明: 选择最优特征
    parameters:
        dataSet - 数据集
    returns:
        bestFeature - 信息增益最大的(最优)特征的索引值
    """
    # 获取数据集的特征数量
    numFeatures = len(dataSet[0]) - 1  # 最后一列是标签,不计算
    baseEntropy = calcShannonEnt(dataSet)  # 计算原始数据集的香农熵
    bestInfoGain = 0.0  # 用于记录最大的增益
    bestFeature = -1  # 用于记录最优特征的索引
    
    # 遍历所有特征
    for i in range(numFeatures):
        # 获取该特征所有可能的取值
        featureValues = [example[i] for example in dataSet]
        uniqueVals = set(featureValues)  # 去重
        
        newEntropy = 0.0  # 计算划分后的香农熵
        for value in uniqueVals:
            # 根据该特征的值划分数据集
            subDataSet = splitDataSet(dataSet, i, value)
            # 计算该子集的香农熵,并加权
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)
        
        # 计算信息增益
        infoGain = baseEntropy - newEntropy
        
        # 更新最大信息增益
        if infoGain > bestInfoGain:
            bestInfoGain = infoGain
            bestFeature = i  # 记录最优特征
    
    return bestFeature

实验步骤:【海洋动物分类】- 构建决策树

决策树构建算法流程:得到原始数据集,基于最好的属性值划分数据集。第一次划分之后,数据将被向下传递到树分支的下一个节点,在这个节点上,再次划分数据。采用递归的原则处理数据集。

递归结束条件:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。

def createTree(dataSet, labels):
    """
    函数说明: 创建决策树
    Parameters:
        dataSet - 训练数据集
        labels - 分类属性标签
    Returns:
        myTree - 决策树
    """
    # 获取数据集的类别标签(最后一列)
    classList = [example[-1] for example in dataSet]
    
    # 如果类别标签中所有样本都属于同一类,停止划分,返回该类标签
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    
    # 如果数据集中的特征已经用完,返回出现次数最多的标签
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    
    # 选择最佳特征
    bestFeature = chooseBestFeatureToSplit(dataSet)
    bestFeatureLabel = labels[bestFeature]
    
    # 创建树的根节点,存储特征的名字
    myTree = {bestFeatureLabel: {}}
    
    # 获取该特征的所有唯一取值
    featureValues = [example[bestFeature] for example in dataSet]
    uniqueVals = set(featureValues)
    
    # 对每个取值进行递归构建子树
    for value in uniqueVals:
        subLabels = labels[:]
        # 删除当前选择的特征
        subLabels.pop(bestFeature)
        # 对数据集进行划分
        subDataSet = splitDataSet(dataSet, bestFeature, value)
        # 递归构建子树
        myTree[bestFeatureLabel][value] = createTree(subDataSet, subLabels)
    
    return myTree

def majorityCnt(classList):
    """
    函数说明: 返回类别标签中出现次数最多的标签
    Parameters:
        classList - 类别标签列表
    Returns:
        majorityClass - 出现次数最多的类别标签
    """
    classCount = {}
    for vote in classList:
        if vote not in classCount:
            classCount[vote] = 0
        classCount[vote] += 1
    # 按照出现次数从大到小排序,返回出现次数最多的类别标签
    sortedClassCount = sorted(classCount.items(), key=lambda x: x[1], reverse=True)
    return sortedClassCount[0][0]

实验步骤:【海洋动物分类】- 使用决策树进行分类

def classify(inputTree, featLabels, testVec):
    """
    函数说明: 使用决策树进行分类
    Parameters:
        inputTree - 已经生成的决策树
        featLabels - 存储选择的最优特征标签
        testVec - 测试数据列表,顺序对应最优特征标签
    Returns:
        classLabel - 分类结果
    """
    # 获取决策树的第一个特征(根节点)
    firstStr = list(inputTree.keys())[0]
    # 获取该特征的索引
    secondDict = inputTree[firstStr]
    
    # 获取该特征的索引在 featLabels 中的位置
    featIndex = featLabels.index(firstStr)
    
    # 获取测试样本在该特征上的值
    key = testVec[featIndex]
    
    # 根据测试样本的特征值找到对应的子树
    valueOfFeat = secondDict[key]
    
    # 如果该子树是一个标签(叶节点),直接返回该标签
    if isinstance(valueOfFeat, str):
        return valueOfFeat
    else:
        # 否则递归调用 classify 函数,继续向下遍历树
        classLabel = classify(valueOfFeat, featLabels, testVec)
        return classLabel

if __name__ == '__main__':
    # 创建数据集和标签
    dataSet, labels = createDataSet()
    
    # 创建决策树
    decisionTree = createTree(dataSet, labels)
    
    # 测试样本
    testVec1 = [1, 0]  # 测试样本1
    testVec2 = [1, 1]  # 测试样本2
    
    # 使用决策树对testVec1和testVec2进行分类
    result1 = classify(decisionTree, labels, testVec1)
    result2 = classify(decisionTree, labels, testVec2)
    
    # 输出分类结果
    print(f"testVec1 分类结果: {result1}")
    print(f"testVec2 分类结果: {result2}")
testVec1 分类结果: no
testVec2 分类结果: yes

【预测隐形眼镜类型】

实验步骤:【预测隐形眼镜类型】- 概述

本实验要求基于决策树算法,帮助人们判断需要佩戴的镜片类型。

数据介绍

隐形眼镜数据集是非常著名的数据集,它包含很多换着眼部状态的观察条件以及医生推荐的隐形眼镜类型。隐形眼镜类型包括硬材质(hard)、软材质(soft)以及不适合佩戴隐形眼镜(no lenses)。

数据集一共有24组数据,数据的Labels依次是age、prescript、astigmatic、tearRate、class,也就是第一列是年龄,第二列是症状,第三列是是否散光,第四列是眼泪数量,第五列是最终的分类标签。

实验步骤:【预测隐形眼镜类型】- 创建决策树

编写代码,基于隐形眼镜数据集构造决策树,并输出。

import math
import numpy as np
import matplotlib.pyplot as plt
import os
def calcShannonEnt(dataSet):
    """
    函数说明:计算给定数据集的香农熵
    parameters:
        dataSet - 数据集
    returns:
        shannonEnt - 香农熵
    """
    # 获取数据集的长度,即样本数
    numEntries = len(dataSet)
    
    # 创建一个字典,用于统计分类标签的出现次数
    labelCounts = {}
    
    # 遍历数据集的每一行,统计标签的出现频率
    for featVec in dataSet:
        currentLabel = featVec[-1]  # 标签在每行的最后一列
        if currentLabel not in labelCounts:
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    
    # 计算香农熵
    shannonEnt = 0.0
    for label in labelCounts:
        prob = labelCounts[label] / numEntries  # 类别的概率
        shannonEnt -= prob * math.log2(prob)  # 香农熵公式

    return shannonEnt
def splitDataSet(dataSet, axis, value):
    """
    函数说明: 按照给定特征划分数据集
    parameters:
        dataSet - 待划分的数据集
        axis - 划分数据集的特征 (第axis个特征)
        value - 特征值
    returns:
        retDataSet - 划分后的数据集
    """
    retDataSet = []
    
    # 遍历数据集
    for featVec in dataSet:
        if featVec[axis] == value:  # 如果特征值匹配
            reducedFeatVec = featVec[:axis]  # 取出特征值之前的部分
            reducedFeatVec.extend(featVec[axis+1:])  # 加上特征值之后的部分
            retDataSet.append(reducedFeatVec)  # 加入到新的数据集中
            
    return retDataSet
def chooseBestFeatureToSplit(dataSet):
    """
    函数说明: 选择最优特征
    parameters:
        dataSet - 数据集
    returns:
        bestFeature - 信息增益最大的(最优)特征的索引值
    """
    # 获取数据集的特征数量
    numFeatures = len(dataSet[0]) - 1  # 最后一列是标签,不计算
    baseEntropy = calcShannonEnt(dataSet)  # 计算原始数据集的香农熵
    bestInfoGain = 0.0  # 用于记录最大的增益
    bestFeature = -1  # 用于记录最优特征的索引
    
    # 遍历所有特征
    for i in range(numFeatures):
        # 获取该特征所有可能的取值
        featureValues = [example[i] for example in dataSet]
        uniqueVals = set(featureValues)  # 去重
        
        newEntropy = 0.0  # 计算划分后的香农熵
        for value in uniqueVals:
            # 根据该特征的值划分数据集
            subDataSet = splitDataSet(dataSet, i, value)
            # 计算该子集的香农熵,并加权
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)
        
        # 计算信息增益
        infoGain = baseEntropy - newEntropy
        
        # 更新最大信息增益
        if infoGain > bestInfoGain:
            bestInfoGain = infoGain
            bestFeature = i  # 记录最优特征
    
    return bestFeature
def createTree(dataSet, labels):
    """
    函数说明: 创建决策树
    Parameters:
        dataSet - 训练数据集
        labels - 分类属性标签
    Returns:
        myTree - 决策树
    """
    # 获取数据集的类别标签(最后一列)
    classList = [example[-1] for example in dataSet]
    
    # 如果类别标签中所有样本都属于同一类,停止划分,返回该类标签
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    
    # 如果数据集中的特征已经用完,返回出现次数最多的标签
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    
    # 选择最佳特征
    bestFeature = chooseBestFeatureToSplit(dataSet)
    bestFeatureLabel = labels[bestFeature]
    
    # 创建树的根节点,存储特征的名字
    myTree = {bestFeatureLabel: {}}
    
    # 获取该特征的所有唯一取值
    featureValues = [example[bestFeature] for example in dataSet]
    uniqueVals = set(featureValues)
    
    # 对每个取值进行递归构建子树
    for value in uniqueVals:
        subLabels = labels[:]
        # 删除当前选择的特征
        subLabels.pop(bestFeature)
        # 对数据集进行划分
        subDataSet = splitDataSet(dataSet, bestFeature, value)
        # 递归构建子树
        myTree[bestFeatureLabel][value] = createTree(subDataSet, subLabels)
    
    return myTree

def majorityCnt(classList):
    """
    函数说明: 返回类别标签中出现次数最多的标签
    Parameters:
        classList - 类别标签列表
    Returns:
        majorityClass - 出现次数最多的类别标签
    """
    classCount = {}
    for vote in classList:
        if vote not in classCount:
            classCount[vote] = 0
        classCount[vote] += 1
    # 按照出现次数从大到小排序,返回出现次数最多的类别标签
    sortedClassCount = sorted(classCount.items(), key=lambda x: x[1], reverse=True)
    return sortedClassCount[0][0]
def classify(inputTree, featLabels, testVec):
    """
    函数说明: 使用决策树进行分类
    Parameters:
        inputTree - 已经生成的决策树
        featLabels - 存储选择的最优特征标签
        testVec - 测试数据列表,顺序对应最优特征标签
    Returns:
        classLabel - 分类结果
    """
    # 获取决策树的第一个特征(根节点)
    firstStr = list(inputTree.keys())[0]
    # 获取该特征的索引
    secondDict = inputTree[firstStr]
    
    # 获取该特征的索引在 featLabels 中的位置
    featIndex = featLabels.index(firstStr)
    
    # 获取测试样本在该特征上的值
    key = testVec[featIndex]
    
    # 根据测试样本的特征值找到对应的子树
    valueOfFeat = secondDict[key]
    
    # 如果该子树是一个标签(叶节点),直接返回该标签
    if isinstance(valueOfFeat, str):
        return valueOfFeat
    else:
        # 否则递归调用 classify 函数,继续向下遍历树
        classLabel = classify(valueOfFeat, featLabels, testVec)
        return classLabel

if __name__ == "__main__":
    fr = open('decision_tree_glass/lenses.txt') # 打开数据集文件
    lenses = [inst.strip().split('\t') for inst in fr.readlines()] # 解析tab键分割的数据行
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
    lensesTree = creatTree(lenses, lensesLabels)
    # 测试样本
    testVec3 = ['young', 'myope', 'no', 'reduced']  # 测试样本1
    testVec4 = ['pre', 'hyper', 'no', 'normal']  # 测试样本2
    
    # 使用决策树对testVec1和testVec2进行分类
    result3 = classify(lensesTree, lensesLabels, testVec3)
    result4 = classify(lensesTree, lensesLabels, testVec4)
    
    # 输出分类结果
    print(f"testVec1 分类结果: {result3}")
    print(f"testVec2 分类结果: {result4}")

testVec1 分类结果: no lenses
testVec2 分类结果: soft
posted @ 2025-01-16 15:16  hjdssj  阅读(133)  评论(0)    收藏  举报