机器学习实战-----决策树

决策树的优点:
计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。
缺点:
可能会产生过度匹配问题。
适用数据类型:
数值型和标称型。
决策树的简单工作原理:
在这里插入图片描述
在构造决策树时,我们需要解决的第一个问题是,当前数据集上哪个特征在划分数据分类时起决定性作用。为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。完成测试之后,原始数据集就被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。如果某个分支下的数据属于同一类型,则当前无需阅读的垃圾邮件已经正确地划分数据分类,无需进一步对数据集进行分割。如果数据子集内的数据不属于同一类型,则需要重复划分数据子集的过程。如何划分数据子集的算法和划分原始数据集的方法相同,直到所有具有相同类型的数据均在一个数据子集内。

创建分支的伪代码函数createBranch():

检测数据集中的每个子项是否属于同一分类:
    If so return 类标签;
    Else
        寻找划分数据集的最好特征
        划分数据集
        创建分支节点
           for每个划分的子集
               调用函数createBranch并增加返回结果到分支节点中
         return 分支节点

决策树的一般流程

(1)收集数据:可以使用任何方法。
(2)准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。
(3)分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
(4)训练算法:构造树的数据结构。
(5)测试算法:使用经验树计算错误率。
(6)使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

此处我们使用ID3算法划分数据集,该算法处理如何划分数据集,何时停止划分数据集。每次划分数据集时我们只选取一个特征属性,如果训练集中存在20个特征,第一次我们选择哪个特征作为划分的参考属性呢?
例如下表中的数据,包含5个海洋动物,特征包括:不浮出水面是否可以生存,以及是否有脚蹼。我们将这些动物分成两类:鱼类和非鱼类。我们现在想要决定依据第一个特征还是第二个特征划分数据,应如何采用量化的方法判断如何划分数据?

不浮出水面是否可以生存是否有脚蹼属于鱼类
1
2
3
4
5

采用量化的方法判断如何划分数据:
划分数据集的大原则是:将无序的数据变得更加有序。
信息增益:在划分数据集之前之后信息发生的变化称为信息增益。知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。
熵:信息的期望值。
如果待分类的事务可能划分在多个分类之中,则符号Xi的信息定义为:
在这里插入图片描述
其中P(Xi)是选择该分类的概率。
为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值,通过下面公式得到:
在这里插入图片描述
其中n是分类的数目。
计算给定数据集的香农熵:

from math import log
def calcShannonEnt(dataSet):
    #计算数据集中实例的总数
    numEntries = len(dataSet)
    labelCounts = {}
    #为所有可能分类创建字典
    for featVec in dataSet:
        #数据字典的键值是最后一列的数值
        currentLabel = featVec[-1]
        #如果当前键值不存在,则扩展字典并将当前键值加入字典,每个键值都记录了当前类别出现的次数
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    shannonEnt = 0.0
    #以2为底求对数
    #使用所有类标签的发送频率计算类别出现的概率,利用这个概率计算香农熵,统计所有类标签发送的次数
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries
        shannonEnt -= prob * log(prob,2)
    return shannonEnt

输入自己的createDataSet()函数:

def createDataSet():
    dataSet = [[1,1,'yes'],
               [1,1,'yes'],
               [1,0,'no'],
               [0,1,'no'],
               [0,1,'no']]
    labels = ['no surfacing','flippers']
    return dataSet,labels

显示结果:

import trees
myDat,labels = trees.createDataSet()
print(myDat)
print(trees.calcShannonEnt(myDat))

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
0.9709505944546686

Process finished with exit code 0

手算过程如下:
在这里插入图片描述
熵越高,则混合的数据也越多,我们可以在数据集中添加更多的分类,观察熵是如何变化的。这里我们增加第三个名为maybe的分类,测试熵的变化。
显示结果:

myDat,labels = trees.createDataSet()
print(myDat)
print(trees.calcShannonEnt(myDat))
myDat[0][-1]='maybe'
print(myDat)
print(trees.calcShannonEnt(myDat))

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
0.9709505944546686
[[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
1.3709505944546687

Process finished with exit code 0

手算过程如下:
在这里插入图片描述

得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集。
划分数据集:
分类算法除了需要测量信息熵,还需要划分数据集,度量花费数据集的熵,以便判断当前是否正确地划分了数据集。我们将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。
按照给定特征划分数据集:

#三个输入参数:待划分的数据集、划分数据集的特征、特征的返回值
def splitDataSet(dataSet,axis,value):
    #创建新的list对象,数据集这个列表中的各个元素也是列表,
    # 我们要遍历数据集中的每个元素,一旦发现符合要求的值,则将其添加到创建的列表中。
    retDataSet = []
    for featVec in dataSet:
        #将符合特征的数据抽取出来
        #如果featVec[axis]=value,则输出去掉axis的数组值
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

显示结果:
假定存在两个列表,a和b:
(1)

a = [1,2,3]
b = [4,5,6]
#执行a.append(b),则列表得到了第四个元素,而且第四个元素也是一个列表
a.append(b)
print(a)

(2)

a = [1,2,3]
b = [4,5,6]
#执行a.extend(b),则得到一个包含a和b所有元素的列表
a.extend(b)
print(a)

结果:
(1)

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\trees.py 
[1, 2, 3, [4, 5, 6]]

Process finished with exit code 0

(2)

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\trees.py 
[1, 2, 3, 4, 5, 6]

Process finished with exit code 0

在前面的简单样本数据上测试函数splitDataSet():
显示结果:

myDat,labels = trees.createDataSet()
print(myDat)
print(trees.splitDataSet(myDat,0,1))
print(trees.splitDataSet(myDat,0,0))

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
[[1, 'yes'], [1, 'yes'], [0, 'no']]
[[1, 'no'], [1, 'no']]

Process finished with exit code 0

接下来将遍历整个数据集,循环计算香农熵和splitDataSet()函数,找到最好的特征划分方式。熵计算将会告诉我们如何划分数据集是最好的数据组织方式。
选择最好的数据集划分方式

def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0])-1
    #计算整个数据集的原始香农熵,保存最初的无序度量值,用于与划分完之后的数据集计算的熵值进行比较。
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeature = -1
    #创建唯一的分类标签列表
    #遍历数据集中的所有特征,使用列表推导(list Comprehension)来创建新的列表,
    for i in range(numFeatures):
        # 将数据集中所有第i个特征值或者所有可能存在的值写入新的list中。
        featList = [example[i] for example in dataSet]
        #实用python语言原生的集合(set)数据类型,从列表从创建集合使python语言得到列表中唯一元素值的最快方法。
        uniqueVals = set(featList)
        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

显示结果:

myDat,labels = trees.createDataSet()
print(trees.chooseBestFeatureToSplit(myDat))
print(myDat)

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
0
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]

Process finished with exit code 0

结果显示,第0个特征是最好的用于划分数据集的特征。
递归构建决策树:
从数据集构造决策树算法所需要的子功能模块,其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据将被向下传递道树分支的下一个节点,在这个节点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集。
递归结束的条件是:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。如果所有实例具有相同的分类,则得到一个叶子节点或者终止块。任何到达叶子节点的数据必然属于叶子节点的分类。如图所示:
在这里插入图片描述
如果数据集已经处理了所有属性,但是类标签依然不是唯一的,此时我们需要决定如何定义该叶子节点,在这种情况下,我们通常会采用多数表决的方法决定该叶子节点的分类。

def majorityCnt(classList):
    classCount={}
    #字典对象存储了classList中每个类标签出现的频率
    for vote in classList:
        if vote not in classCount.keys():classCount[vote] = 0
        classCount[vote] += 1
    #最后利用operator操作键值排序字典,
    sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True)
    #返回出现次数最多的分类名称。
    return sortedClassCount[0][0]

创建树的函数代码:

def createTree(dataSet,labels):
    #首先创建了名为classList的列表变量,其中包含了数据集的所有类标签。
    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)
    #当前数据集选取的最好特征存储在变量bestFeat中,得到列表包含的所有属性值。
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    #字典变量myTree存储了树的所有信息
    myTree = {bestFeatLabel:{}}
    del (labels[bestFeat])
    #得到列表包含的所有属性值
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    #最后代码遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree(),
    # 得到的返回值将被插入到字典变量myTree中,因此函数终止执行时,字典中将会嵌套很多代表叶子节点信息的字典数据。
    for value in uniqueVals:
        #复制类标签,并将其存储在新列表变量subLabels中
        #python语言中函数参数是列表类型时,参数是按照引用方式传递的,
        # 为了保证每次调用createTree()时不改变原始列表的内容,使用新变量subLabels代替原始列表。
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
    return myTree

显示结果:

print(trees)
myDat,labels = trees.createDataSet()
myTree = trees.createTree(myDat,labels)
print(myTree)

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
<module 'trees' from 'D:\\exercise\\pythonProject\\TREE\\trees.py'>
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

Process finished with exit code 0

在Python中使用Matplotlib注解绘制树形图-----使用文本注解绘制树节点

import matplotlib.pyplot as plt
#定义文本框和箭头格式
decisionNode = dict(boxstyle = "sawtooth",fc = "0.8")
leafNode = dict(boxstyle = "round4", fc = "0.8")
arrow_args = dict(arrowstyle = "<-")

#绘制带箭头的注解
#定义plotNode()函数执行了实际的绘图功能,该函数需要一个绘图区,该区域由全局变量createPlot.ax1定义。

def plotNode(nodeTxt, centerPt, parentPt, nodeType):
    createPlot.ax1.annotate(nodeTxt,xy = parentPt,xycoords = 'axes fraction',
                            xytext = centerPt,textcoords = 'axes fraction',
                            va = "center",ha="center",bbox = nodeType,arrowprops=arrow_args
                            )
def createPlot():
    fig = plt.figure(1,facecolor='white')
    fig.clf()
    createPlot.ax1 = plt.subplot(111,frameon = False)
    plotNode('a decision node',(0.5,0.1),(0.1,0.5),decisionNode)
    plotNode('a leaf node',(0.8,0.1),(0.3,0.8),leafNode)
    plt.show()

显示结果:

print(treePlotter.createPlot())

结果:

在这里插入图片描述
获取叶节点的数目和树的层数:

def getNumLeafs(myTree):
    numLeafs = 0
    #第一个关键字是第一次划分数据集的类别标签,附带的数值表示子节点的取值。
    #从第一个关键字出发,我们能可以遍历整棵树的所有子节点。
    firstStr = list(myTree.keys())[0]
    secondDict = myTree[firstStr]
    #测试节点的数据类型是否为字典
    for key in secondDict.keys():
        #使用python提供的type()函数可以判断子节点是否为字典类型。
        # 如果子节点是字典类型,则该节点也是一个判断节点,需要递归调用getNumLeafs()函数。
        if type(secondDict[key]).__name__ =='dict':
            #getNumLeafs()函数遍历整棵树,累计叶子节点的个数,并返回该数值。
            numLeafs += getNumLeafs(secondDict[key])
        else: numLeafs += 1
    return numLeafs

#getTreeDepth()计算遍历过程中遇到判断系节点的个数。该函数的终止条件是叶子节点,
# 一旦到达了叶子节点,则从递归调用中返回,并将计算树深度的变量加一。
def getTreeDepth(myTree):
    maxDepth = 0
    #python3返回的是dict_keys对象,支持iterable,但不支持indexable,需要将其明确的转化成List
    firstStr = list(myTree.keys())[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else: thisDepth = 1
        if thisDepth>maxDepth:maxDepth = thisDepth
    return maxDepth
def retrieveTree(i):
    listOfTrees = [{'no surfacing':{0:'no',1:{'flippers':{0:'no',1:'yes'}}}},
                   {'no surfacing':{0:'no',1:{'flippers':{0:{'head':{0:'no',1:'yes'}},1:'no'}}}}
                   ]
    return listOfTrees[i]

显示结果:

print(treePlotter.retrieveTree(1))
myTree = treePlotter.retrieveTree(0)
print(treePlotter.getNumLeafs(myTree))   #调用getNumLeafs()函数返回值为3,等于树0的叶子节点数
print(treePlotter.getTreeDepth(myTree))   #调用getTreeDepths()函数正确返回树的层数。

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
{'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}
3
2

Process finished with exit code 0

plotTree函数:

def plotMidText(cntrPt,parentPt,txtString):
    xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
    yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
    createPlot.ax1.text(xMid,yMid,txtString,va="center",ha="center",rotation=30)


def plotTree(myTree,parentPt,nodeTxt):
    # 计算宽与高
    #全局变量plotTree.totalW存储树的宽度,全局变量plotTree.totalD存储树的深度,
    # 使用这两个变量计算树节点的摆放位置。这样可以将树绘制在水平方向和垂直方向的中心位置。
    #树的宽度用于计算放置判断节点的位置,主要的计算原则是将它放在所有叶子节点的中间,而不仅仅是它子节点的中间。
    numLeafs = getNumLeafs(myTree)
    depth = getTreeDepth(myTree)
    firstStr = list(myTree.keys())[0]
    #全局变量plotTree.xOff和plotTree.yOff追踪已经绘制的节点位置,以及放置下一个节点的恰当位置。
    # 绘制图形的x轴有效范围是0.01.0,y轴有效范围也是0.01.0.
    # 通过计算树包含的所有叶子节点数,划分图形的宽度,从而计算得到当前节点的中心位置,即:我们按照叶子节点的数目将X轴划分为若干部分。
    # 按照图形比例绘制树形图的最大好处是无需关心实际输出图形的大小,一旦图形大小发生了变化,函数会自动按照图形大小重新绘制。
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW,plotTree.yOff)
    #标记子节点属性值
    #使用plotMidText()计算父节点和子节点的中间位置,并在此添加简单的文本标签信息。
    # 采用函数getNumLeafs()getTreeDepth()以相同的方式递归遍历整棵树,
    # 如果节点是叶子节点则在图形上画出叶子节点,如果不是叶子节点则递归调用plotTree()函数。
    # 在绘制了所有子节点之后,增加全局变量Y的偏移。
    plotMidText(cntrPt,parentPt,nodeTxt)
    plotNode(firstStr,cntrPt,parentPt,decisionNode)
    secondDict = myTree[firstStr]
    #减少y偏移
    #按照比例减少全局变量plotTree.yOff,并标注此处要绘制子节点,
    # 这些节点即可以是叶子节点也可以是判断节点,此处需要只保存绘制图形的轨迹。
    #因为我们是自顶向下绘制图形,因此需要依次递减y坐标值,而不是递增y坐标值。
    plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':
            plotTree(secondDict[key],cntrPt,str(key))
        else:
            plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
            plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),cntrPt,leafNode)
            plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key))
    plotTree.yOff = plotTree.xOff + 1.0/plotTree.totalD
def createPlot(inTree):
    fig = plt.figure(1,facecolor='white')
    fig.clf()
    axprops = dict(xticks = [],yticks = [])
    createPlot.ax1 = plt.subplot(111,frameon=False,**axprops)

    plotTree.totalW = float(getNumLeafs(inTree))
    plotTree.totalD = float(getTreeDepth(inTree))
    plotTree.xOff = -0.5/plotTree.totalW
    plotTree.yOff = 1.0
    plotTree(inTree,(0.5,1.0),'')
    plt.show()

显示结果:

myTree = treePlotter.retrieveTree(0)
print(treePlotter.createPlot(myTree))
myTree['no surfacing'][3]='maybe'
print(myTree)
print(treePlotter.createPlot(myTree))

结果:
在这里插入图片描述
在这里插入图片描述

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
None
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}, 3: 'maybe'}}
None

Process finished with exit code 0

测试算法:使用决策树执行分类
使用决策树的分类函数:

def classify(inputTree,featLabels,testVec):
    firstStr = list(inputTree.keys())[0]
    secondDict = inputTree[firstStr]
    #将标签字符串转换为索引
    #使用Index方法查找当前列表中第一个匹配firstStr变量的元素
    featIndex = featLabels.index(firstStr)
    #然后代码递归遍历整棵树
    for key in secondDict.keys():
        #比较testVec变量中的值与树节点的值,如果到达叶子节点,则返回当前节点的分类标签。
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__=='dict':
                classLabel = classify(secondDict[key],featLabels, testVec)
            else:classLabel = secondDict[key]
    return classLabel

显示结果:

myDat,labels = trees.createDataSet()
print(labels)
myTree = treePlotter.retrieveTree(0)
print(myTree)
print(trees.classify(myTree,labels,[1,0]))
print(trees.classify(myTree,labels,[1,1]))

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
['no surfacing', 'flippers']
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
no
yes

Process finished with exit code 0

使用算法:决策树的存储
使用pickle模块存储决策树:

def storeTree(inputTree,filename):
    import pickle
    fw = open(filename,'wb')
    pickle.dump(inputTree,fw)
    fw.close()
def grabTree(filename):
    import pickle
    fr = open(filename,'rb')
    return pickle.load(fr)

显示结果:

myTree = treePlotter.retrieveTree(0)
print(trees.storeTree(myTree,'classifierStorage.txt'))
print(trees.grabTree('classifierStorage.txt'))

结果:

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
None
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

Process finished with exit code 0

示例:使用决策树预测隐形眼镜类型

(1)收集数据:提供的文本文件
(2)准备数据:解析tab键分隔的数据行
(3)分析数据:快速检查数据,确保正确地解析数据内容,使用createPlot()函数绘制最终的树形图。
(4)训练算法:使用createTree()函数。
(5)测试算法:编写测试函数验证决策树可以正确分类给定的数据实例。
(6)使用算法:存储树的数据结构,以便下次使用时无需重新构造树。

显示结果:

fr = open('lense.txt')
lenses = [inst.strip().split('\t') for inst in fr.readlines()]
lensesLabels = ['age','prescript','astigmatic','tearRate']
lensesTree = trees.createTree(lenses,lensesLabels)
print(lensesTree)
print(treePlotter.createPlot(lensesTree))

结果:

在这里插入图片描述

D:\exercise\机器学习实战\Scripts\python.exe D:\exercise\pythonProject\TREE\mian.py 
{'tearRate': {'normal': {'astigmatic': {'yes': {'prescript': {'hyper': {'age': {'pre': 'no lenses', 'young': 'hard', 'presbyopic': 'no lenses'}}, 'myope': 'hard'}}, 'no': {'age': {'pre': 'soft', 'young': 'soft', 'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}}}}}, 'reduced': 'no lenses'}}
None

Process finished with exit code 0

lense.txt数据:

young	myope	no	reduced	no lenses
young	myope	no	normal	soft
young	myope	yes	reduced	no lenses
young	myope	yes	normal	hard
young	hyper	no	reduced	no lenses
young	hyper	no	normal	soft
young	hyper	yes	reduced	no lenses
young	hyper	yes	normal	hard
pre	myope	no	reduced	no lenses
pre	myope	no	normal	soft
pre	myope	yes	reduced	no lenses
pre	myope	yes	normal	hard
pre	hyper	no	reduced	no lenses
pre	hyper	no	normal	soft
pre	hyper	yes	reduced	no lenses
pre	hyper	yes	normal	no lenses
presbyopic	myope	no	reduced	no lenses
presbyopic	myope	no	normal	no lenses
presbyopic	myope	yes	reduced	no lenses
presbyopic	myope	yes	normal	hard
presbyopic	hyper	no	reduced	no lenses
presbyopic	hyper	no	normal	soft
presbyopic	hyper	yes	reduced	no lenses
presbyopic	hyper	yes	normal	no lenses
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值