决策树算法初步理解与代码实现

#决策树算法
@(机器学习)[决策树算法]

1.什么是决策树:

二十个问题游戏:游戏的规则其中一个参与者想出一个事物,其他参与者通过提出问题来猜出事物,最多提出二十个问题,来缩小和确定物体。决策树的原理和二十个问题的原则很像。也是通过特征对数据进行归类。决策树(Decision Tree)及其变种是另一类将输入空间分成不同的区域,每个区域有独立参数的算法。决策树分类算法是一种基于实例的归纳学习方法,它可以使用不熟悉得数据集合,并从中提取出一系列得规则。树中的每个非叶子节点记录了使用哪个特征来进行类别的判断,每个叶子节点则代表了最后判断的类别。根节点到每个叶子节点均形成一条分类的路径规则。而对新的样本进行测试时,只需要从根节点开始,在每个分支节点进行测试,沿着相应的分支递归地进入子树再测试,一直到达叶子节点,该叶子节点所代表的类别即是当前测试样本的预测类别。每一个节点都是通过计算其熵来确定的。

2.树得组成:

根节点:第一个选择得节点(最重要大条件),在特质中可以起决定性左右的特征
非叶子节点:中间过程
叶子节点:最终得决策结果
节点:
添加节点相当于在数据中切了一刀(以什么条件进行分类数据)
越多得特征就是节点越多,数据得分类越多

3.决策树的优缺点

优点:计算复杂度不高,输出的结果易于理解,对中间值的缺失不敏感,可以处理不相关数据
缺点:可能产生过度匹配问题及过拟合问题
使用数据类型:数值型或者标称型

4.决策树得训练与测试

训练阶段:从给定得训练集构造出来一颗树(从跟节点开始选择特征),提取数据中的规则并创建规则的过程。
测试阶段:根据构造出来得树模型从上到下走一遍就好了

5.如何创建树

一旦构造好了抉择树,那么分类或者预测得任务就很简单了,只需要走一遍就行,难点在于如何构造出来一颗树.

5.1.选择节点(特征切分)

​ 根节点应该是分类得效果最好得一个,就像选老大一样.通过一种衡量标准,来计算通过不同特征进行分支选择后得分类情况,找到最好得那个当作根节点。

#### 5.2.一般步骤

​ 1)计算信息增益

​ 2)划分数据集

​ 3)递归创建决策树

5.3.衡量标准
5.3.1信息量

信息量是表达一段信息所能传递的信息量的大小,就是能让人获取到信息的多少。越不确定,越不可能发生,概率就越小,信息量也就越大,也就是信息越多。比如说“今天肯定会天黑”,实现概率100%,说了和没说差不多,信息量就是0。表达式如下

l ( x i ) = − log ⁡ 2 p ( x i ) l(x_i)=-\log_2p(x_i) l(xi)=log2p(xi)

其中 x i x_i xi代指信息, p ( x i ) p(x_i) p(xi) x i x_i xi的概率

5.3.2熵

​ 熵:熵是表示随机变量不确定得度量(解释:说白了就是物体内部得混乱程度,比如杂货商店里面什么都有那一定混乱,专卖店只卖一个牌子一定稳定得多)

公式如下:
H ( x ) = − ∑ i = 1 n P ( x i ) log ⁡ 2 P ( x i ) H(x) = -\sum_{i=1}^n P(x_i)\log_2P(x_i) H(x)=i=1nP(xi)log2P(xi)

其中 P ( x i ) P(x_i) P(xi)是选择该分类的概率

  • 熵:不确定性越大,得到得熵值也就越大,如下图
    在这里插入图片描述
    当p=0或1时,H§ = 0,随机变量完全没有不确定性
    当p=0.5时,H§ = 1,此时随机变量得不确定性最大
    5.2.例子:
    A集合[1,1,1,1,1,1,1,1,1,2,2]
    B集合[1,2,3,4,5,6,7,8,9,1,2]
    显示A集合得熵值要低,因为A得类别少相对稳定一些而B中得类别太多,熵值就会很大

  • 代码实现

    #信息熵,对数据集的要求必须是由列元素组成的列表,长度相同,最后一列为标签列
    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
        #计算熵
        for key in labelCounts:
            #获取每一个标签得概率
            pro = float(labelCounts[key])/numEntries
            shannonEnt -= pro*log(pro,2)
        return shannonEnt
    
5.3.3 ID3信息增益

信息增益:表示特征X使得Y得不确定性减少得程度。是信息量的差值。也就是说,一开始是A,用了条件后变成了B,则条件引起的变化是A-B,即信息增益(它描述的是变化Delta)。好的条件就是信息增益越大越好,即变化完后熵越小越好(熵代表混乱程度,最大程度地减小了混乱)。因此我们在树分叉的时候,应优先使用信息增益最大的属性,这样降低了复杂度,也简化了后边的逻辑。

g ( o u t l o o k 信 息 增 益 ) = g ( 总 体 的 熵 ) − g ( 特 征 熵 ) g(outlook信息增益) = g(总体的熵)-g(特征熵) g(outlook)=g()g()

3.1 信息增益计算
案例:
数据如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fMqG94f-1571228483441)(C:\Users\lv\Desktop\截图/决策树案例1.PNG)]
1计算总体的熵:
在历史数据中(14天)有9天打球,5天不打球,所以此时的熵应为
g ( x ) = − 9 14 log ⁡ 2 9 14 − 5 14 log ⁡ 2 5 14 = 0.940 g(x)=-\frac{9}{14}\log_2\frac{9}{14}-\frac{5}{14}\log_2\frac{5}{14}=0.940 g(x)=149log2149145log2145=0.940

2计算特征的熵值(以outlook特征为例)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YywatxiD-1571228483445)(C:\Users\lv\Desktop\截图/决策树案例2.PNG)]
Outlook = sunny时,熵值为0.971

− 2 5 log ⁡ 2 2 5 − 3 5 log ⁡ 35 = 0.971 -\frac{2}{5}\log_2\frac{2}{5}-\frac{3}{5}\log{3}{5}=0.971 52log25253log35=0.971
​ Outlook = overcast时,熵值为0
− 4 4 log ⁡ 2 4 4 − 0 = 0 -\frac{4}{4}\log_2\frac{4}{4}-0=0 44log2440=0

​ Outlook = rainy时,熵值为0.971
3 5 log ⁡ 2 3 5 − 2 5 log ⁡ 2 2 5 = 0.971 \frac{3}{5}\log_2\frac{3}{5}-\frac{2}{5}\log_2\frac{2}{5}=0.971 53log25352log252=0.971

​ 根据数据统计,outlook取值分别为sunny,overcast,rainy的概率分别为:5/14, 4/14, 5/14
​ 熵值计算:
5 14 ∗ 0.971 + 4 14 ∗ 0 + 5 14 ∗ 0.971 = 0.693 \frac{5}{14}*0.971+\frac{4}{14}*0+\frac{5}{14}*0.971=0.693 1450.971+1440+1450.971=0.693

3计算信息增益:
g ( o u t l o o k 信 息 增 益 ) = g ( 总 体 的 熵 ) − g ( 特 征 熵 ) = 0.940 − 0.693 = 0.247 g(outlook信息增益) = g(总体的熵)-g(特征熵)=0.940-0.693 = 0.247 g(outlook)=g()g()=0.9400.693=0.247

同样的方式可以计算出其他特征的信息增益,那么我们选择最大的那个就可以,相当于是遍历了一遍特征,找出来了大当家,然后再其余的中继续通过信息增益找二当家!
4 缺点:没有考虑到自身的熵,如果一个特征的取值比较稀少集中,类似与列号一样那么其熵就会很小但是没有意义

5代码实现

#选择最好的数据集划分方式
#dataSet:数据集
def choseBestFeatureToSplit(dataSet):
    #获取特征得数目,处理标签之外得其他特征数目
    unmFeature = len(dataSet[0])-1
    # print("unmFeature:",unmFeature)
    #计算整个数据集得熵
    bestEntropy = calcShannonEnt(dataSet)
    # print("bestEntropy:",bestEntropy)
    bestInfoGain = 0.0
    bestFeature = -1

    for i in range(unmFeature):
        #获取特征列表, 此处要求数据集得长度一致且最后一位为标签
        featList = [example[i] for example in dataSet]
        # print("第%d个得featList:"%i,featList)
        #去掉重复得特征
        uniqueVals = set(featList)
        # print('uniqueVals:',uniqueVals)
        newEntropy = 0.0
        #遍历不重复得特征
        for value in uniqueVals:
            #按照i列得value值进行数据分割
            subDataSet = splitDataSet(dataSet,i,value)
           #所分割得子数据占总数据得比例
            prob = len(subDataSet)/float(len(dataSet))

            newEntropy +=prob*calcShannonEnt(subDataSet)
            # print('newEntropy:',newEntropy)
        #找到newEntropy最小得那个i
        inforGain = bestEntropy-newEntropy  #计算信息增益
        if inforGain>bestInfoGain:
            bestInfoGain=inforGain
            bestFeature=i
    return bestFeature
#划分数据集(就是找到符合条件得数据,把相关列删掉再拼接在一起)
#dataSet:数据集
#axis:要匹配得位置(列号)
#value:要匹配得值
def splitDataSet(dataSet,axis,value):
    retDataSet=[]
    for featVec in  dataSet:
        #判断featVec是否符合规定条件
        if featVec[axis]==value:
            # print("featVec:",featVec)
            #获取axis之间得部分
            reducedFeatVec = featVec[:axis]
            # print('reducedFeatVec',reducedFeatVec)
            #获取axis之后得部分
            tem=featVec[axis+1:]
            # print('tem:',tem)
            #合并除去axis之外得数据
            reducedFeatVec.extend(tem)
            # print('reducedFeatVec 2',reducedFeatVec)
            retDataSet.append(reducedFeatVec)
    return retDataSet
5.3.4 C4.5:信息增益率(解决ID3问题,考虑自身熵)

4.1 那么下面来看信息增益存在的一个问题:假设某个属性存在大量的不同值,如ID编号(在上面例子中加一列为ID,编号为a~n),在划分时将每个值成为一个结点,这样就形成了下面的图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9tpP3Id-1571228483449)(C:\Users\lv\Desktop\截图/1364523479_1485.png)]
那么导致这样的偏差的原因是什么呢?从上面的例子应该能够感受出来,原因就是该特征可以选取的值过多。解决办法自然就想到了如何能够对节点分支过多的情况进行惩罚,这样就引入了公式来表示属性A的内部信息。
4.2 属性A的内部信息(Intrinsic Information of an Attribute)
I n t I ( Y , X ) = − ∑ i X i Y log ⁡ 2 X i Y IntI(Y,X) = -\sum_i\frac{X_i}{Y}\log_2\frac{X_i}{Y} IntI(Y,X)=iYXilog2YXi

注 : X 表 示 某 个 属 性 A 的 随 机 变 量 , ∣ X i ∣ 表 示 属 性 A 的 第 i 个 分 类 的 个 数 , \color{red}{注:X表示某个属性A的随机变量,\left | X_i \right |表示属性A的第i个分类的个数,} XAXiAi

∣ Y ∣ 表 示 样 本 的 总 个 数 , ∣ X i ∣ ∣ Y ∣ 表 示 属 性 A 的 第 i 个 分 类 占 样 本 总 个 数 的 比 例 \color{red}{\left | Y \right |表示样本的总个数,\frac{\left | X_i \right |}{\left | Y \right |}表示属性A的第i个分类占样本总个数的比例} YYXiAi

4.3 信息增益率
g = g ( 信 息 增 益 ) I n t I ( Y , X ) g = \frac{g(信息增益)}{IntI(Y,X)} g=IntI(Y,X)g()

注 : 就 是 在 信 息 增 益 上 又 除 了 一 个 量 , 这 个 量 能 够 起 到 惩 罚 的 作 用 , 类 别 越 多 这 个 量 越 大 。 \color{red}{注:就是在信息增益上又除了一个量,这个量能够起到惩罚的作用,类别越多这个量越大。}

如:
(1)Day日期的内部信息
I n t I ( D a y ) = 14 ∗ ( − 1 14 ∗ log ⁡ 2 1 14 ) = 3.8074 IntI(Day) = 14*(-\frac{1}{14}*\log_2\frac{1}{14})=3.8074 IntI(Day)=14(141log2141)=3.8074

(2)Outlook天气的内部信息
I n t I ( O u t l o o k ) = − 5 14 log ⁡ 2 5 14 − 4 14 log ⁡ 2 4 14 − 5 14 log ⁡ 2 5 14 = 1.5774 IntI(Outlook)=-\frac{5}{14}\log_2\frac{5}{14}-\frac{4}{14}\log_2\frac{4}{14}-\frac{5}{14}\log_2\frac{5}{14}=1.5774 IntI(Outlook)=145log2145144log2144145log2145=1.5774

(3)Day日期信息的增益率
g ( ) = g ( 总 体 ) I n t I ( D a y ) = 0.940 3.8094 = 1.3788 g() = \frac{g(总体)}{IntI(Day)}=\frac{0.940}{3.8094}=1.3788 g()=IntI(Day)g()=3.80940.940=1.3788

(4)Outlook天气的信息增益率
g ( ) = g ( o u t l o o k 增 益 ) I n t i ( o u t l o o k ) = 0.3949 1.5774 = 0.2503 g()=\frac{g(outlook增益)}{Inti(outlook)}=\frac{0.3949}{1.5774}=0.2503 g()=Inti(outlook)g(outlook)=1.57740.3949=0.2503

5.3.5 CART:使用GINI系数来当衡量标准

GINI系数:
G i n i ( p ) = ∑ k = 1 k P k ( 1 − P k ) = 1 − ∑ k = 1 k P k 2 ( 和 熵 得 衡 量 标 准 类 似 , 计 算 方 式 不 同 ) Gini(p) = \sum_{k=1}^kP_k(1-P_k)=1-\sum_{k=1}^kP_k^2(和熵得衡量标准类似,计算方式不同) Gini(p)=k=1kPk(1Pk)=1k=1kPk2()
(和熵得衡量标准类似,计算方式不同)

5.4创建树

代码实现

#创建决策树
def createTree(dataSet,labels):
    #获取数据集得标签
    classList = [example[-1] for example in dataSet]
    print('classList:',classList)
    #如果都是同标签退出
    if classList.count(classList[0])==len(classList):
        return classList[0]
    #如果只有标签则退出
    if len(dataSet[0])==1:
        return majorityCnt(dataSet)
    #获取最好的分割数据得分割点
    bestFeat = choseBestFeatureToSplit(dataSet)
    print('bestFeat',bestFeat)
    #获取分割点得列名称,不是数据集中得标签类
    bestFeatLabel = labels[bestFeat]
    print('bestFeatLabel:',bestFeatLabel)
    myTree = {bestFeatLabel:{}}
    #删除列名称
    del labels[bestFeat]
    #获取最佳切割点的数据值组成列表
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    print('uniqueVals:',uniqueVals)
    for value in uniqueVals:
        subLables = labels[:]
        tem = createTree(splitDataSet(dataSet,bestFeat,value),subLables)
        myTree[bestFeatLabel][value] = tem
    return myTree
#获取次数最多得分类名称
#classList:数据集
def majorityCnt(classList):
    classCount={}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote]=0
        classCount[vote]+=1
        # classCount[vote]=classCount.get(vote,-1)+1
    soetedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return soetedClassCount[0][0]
5.5画图

由于对matplotlib还在学习中就直接上代码

import matplotlib.pyplot as plt
#设置文本框和箭头得样式
decisionNode = dict(boxstyle="sawtooth",fc='0.8')
leafNode = dict(boxstyle="round4",fc='0.8')
arrow_args = dict(arrowstyle='<-')

#绘制带箭头得标注
#nodeTxt:文本
#centerPt:文本得位置
#parentPt:点得位置
#nodeType:对方框得设置
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(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()


#获取数得叶子节点
def getNumLeafs(myTree):
    numLeafs = 0
    firstStr = list(myTree.keys())[0]
    seconDict = myTree[firstStr]
    for key in seconDict.keys():
        if type(seconDict[key]).__name__=='dict':
            numLeafs+=getNumLeafs(seconDict[key])
        else:
            numLeafs+=1
    return numLeafs
#获取树得层数
def getTreeDepth(myTree):
    maxDepth=0
    firstStr = list(myTree.keys())[0]
    seconDict = myTree[firstStr]
    for key in seconDict.keys():
        if type(seconDict[key]).__name__=='dict':
            thisDepth = 1+getTreeDepth(seconDict[key])
        else:
            thisDepth=1
        if thisDepth>maxDepth:
            maxDepth=thisDepth
    return maxDepth

#在父子节点间显示文字  --文字--》
def plotMidText(cntrPt,parentPt,textString):
    xMid=(parentPt[0]-cntrPt[0])/2.0+cntrPt[0]
    yMid=(parentPt[1]-cntrPt[1])/2.0+cntrPt[1]
    createPlot.ax1.text(xMid,yMid,textString)

#绘制图
#myTree:树结构
#parentPt:父节点位置
#nodeText:文字

def plotTree(myTree,parentPt,nodeTxt):
    #计算高度和宽度
    numLeafs = getNumLeafs(myTree)
    depth = getTreeDepth(myTree)

    firstStr = list(myTree.keys())[0]
    #计算子节点位置
    cntrPt = (plotTree.xOff+(1.0+float(numLeafs))/2.0/plotTree.totalW,plotTree.yOff)

    #向父子节点之间打印文字
    plotMidText(cntrPt,parentPt,nodeTxt)
    #标注文字
    plotNode(firstStr,cntrPt,parentPt,decisionNode)
    secondDict = myTree[firstStr]
    #根据树得高度设置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:
            #根据树得宽度设置x坐标
            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.yOff+1.0/plotTree.totalD
5.6封装测试以保存模型
def classify(inputTree,featLabels, testVec ):
    firstStr = list(inputTree.keys())[0]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex]==key:
            if type(secondDict[key]).__name__=='dict':
                classLabel = classify(secondDict[key],featLabels,testVec)
            else:
                classLabel = secondDict[key]
    return  classLabel


"""
以下是保存和读取决策树模型
"""
#保存树模型
def storeTree(inputTree,filename):
    import pickle
    fw =open(filename,'wb+')
    pickle.dump(inputTree,fw)
    fw.close()

#读取树模型
def grabTree(filename):
    import pickle
    fw = open(filename,'rb+')
    myTree = pickle.load(fw)
    fw.close()
    return myTree

6 特值是连续值

进行离散化就可以了

7.决策树剪枝策略:

为什么要剪枝:决策树过拟合风险很大,理论上可以完全得分开数据
剪枝策略:
1 预剪枝:边建立决策树边进行剪枝操作
限制深度,叶子节点个数,叶子节点样本数,信息增益量等
2 后剪枝:当建立完决策树后进行剪枝操作

C a ( T ) = C ( T ) + α ∗ ∣ T l e a f ∣ ( 叶 子 节 点 越 多 , 损 失 越 大 ) C_a(T) = C(T)+\alpha*|T_leaf| (叶子节点越多,损失越大) Ca(T)=C(T)+αTleaf

C ( T ) 表 示 当 前 损 失 , 对 于 每 一 个 叶 子 节 点 来 说 , 用 叶 子 节 点 得 样 本 数 乘 上 熵 值 或 g i n n i 系 数 \color{red}{C(T)表示当前损失,对于每一个叶子节点来说,用叶子节点得样本数乘上熵值或ginni系数} C(T)ginni

α ∗ ∣ T l e a f ∣ 要 限 制 叶 子 节 点 得 个 数 \color{red}{\alpha*|T_leaf| 要限制叶子节点得个数} αTleaf

8 .集成算法

8.1 目的

​ 让机器学习效果更好

8.2 集成算法种类
##### 		8.2.1 Bagging算法

​ 全称是bootstrap aggregation ,训练多个分类器取平均值 f ( x ) = 1 M ∑ m = 1 M f m ( x ) f(x) = \frac{1}{M}\sum_{m=1}^M f_m(x) f(x)=M1m=1Mfm(x),就是并行训练一堆分类器。

​ 最典型的代表就是随机森林,随机森林要做到数据采样随机,特征选择随机 (就是数量一样,内容不同),随机可以保证泛化能力较强

​ 森林:很多决策树并行放在一起

​ 随机森林的优势:

  • 1.能够处理很高维度(feature很多)的数据,并且不用做特征选择

  • 2.在训练完后,它能够给出哪些feature比较重要

  • 3.容易做成并行化方法,速度比较快

  • 4.可以进行可视化展示,便于分析

    评估某个特征的重要性:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Z2XM573-1571228483457)(C:\Users\lv\Desktop\截图\特值评估.png)]

8.2.2 Boosting 算法

​ 从弱学习器开始加强,通过加权来进行训练 F m ( x ) = F m − 1 ( x ) + a r g m i n n ∑ i = 1 n L ( y i , F m − 1 ( x i ) + h ( x i ) ) F_m(x)=F_{m-1}(x)+argmin_n\sum_{i=1}^n L(y_i,F_{m-1}(x_i)+h(x_i)) Fm(x)=Fm1(x)+argminni=1nL(yi,Fm1(xi)+h(xi))(加入一棵树,要比原来的强)

​ 代表算法:AdaBoost, Xgboost

​ Adaboost会根据前一次的分类效果调整数据权重

​ (如果某一个数据在这次分错了,那么在下一次我就会给它更大的权重)

​ 最终的结果:每个分类器根据自身的准确性来确定各自的权重,再合体

8.2.3 Stacking 算法

​ 聚合多个分类或回归模型 (KNN,SVM,RF等等 )可以分阶段来做(第一阶段得出各自结果,第二阶段再用前一阶段结果训练 )

初 步 学 习 , 总 结 理 解 , 若 有 错 误 , 还 望 指 点 , 谢 谢 \color{red}{初步学习,总结理解,若有错误,还望指点,谢谢}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值