【机器学习原理】决策树分类算法

上一篇:朴素贝叶斯分类算法

一、决策树分类

  • 决策树的分类方法
  • 分支节点划分
  • 纯度度量

1. if - else

if - else两个特性:

  • 一是能够利用if-else进行条件判断,但需要首先给出判断条件;
  • 二是能无限嵌套,也就是说在一套if-else的条件执行体中,能够再嵌套另一套if-else,一层一层地无限嵌套下去。

我们还是从最简单的二元分类问题开始吧。假定待分类的数据集中每个样本都有A、B、C三个特征维度,每个特征维度当然还可能有不同的赋值,这里我们为了简便,假定每个特征只有“是”和“否”两种赋值,请你完成二元分类。数据集如表7-1所示。
在这里插入图片描述二元分类的任务是判断给定样本属于正类还是负类,而判断的依据只能根据样本的各个特征维度的值。对数据集进行简单分析后我们发现,只有在A、B、C三个特征维度值都为“是”的情况下,样本才为正类,只要出现一个“否”,样本都被归为负类。那么,用if-else进行判别的算法的伪代码大概是这样的:

if (特征A):
	if (特征B):
		if (特征C):
			类别 = 正类
		else:
			类表 = 负类
	else:
		类表 = 负类
else:
	类别 = 负类

把上面的伪代码画成流程图,就能明显看到一棵典型的二叉树,决策树里的“树”指的就是这种形状结构(见图7-1)。
在这里插入图片描述通观if-else进行分类的整个过程,可以看到大致分为三步:首先以特征维度作为判断条件,然后构建其树形结构,最后一层一层地进行判断。这个过程非常类似决策树的分类过程。

2. 如何种植一棵有灵魂的“树”

在编程中, if-else所依赖的判断条件是由程序员来填写,但在机器学习里,我们能做的只有两件事,第一件事是选择模型,第二件事是往模型“嘴”里塞数据,剩下的就只能坐在一旁干着急。上面这棵可以决策的树得依靠我们把判别条件填进去,它要想成为真正的决策树,就得学会怎样挑选判别条件。这是决策树算法的灵魂,也是接下来需要重点探讨的问题。

第一个要紧问题就是:判别条件从何而来呢?分类问题的数据集由许多样本构成,而每个样本数据又会有多个特征维度,譬如学生资料数据集的样本就可能包含姓名、年龄、班级、学号等特征维度,它们本身也是一个集合,我们称为特征维度集。数据样本的特征维度都可能与最终的类别存在某种关联关系,决策树的判别条件正是从这个特征维度集里产生的。

部分教材认为只有真正有助于分类的才能叫特征,原始数据里面的这些记录项目只能称为属性(Attribute),而把特征维度集称为属性集,所以在这些教材中,决策树是从称为树形集的集合中选择判别条件。这里为了保持本书用语的连贯性,我们仍然称之为“特征维度”。当然,这只是用语习惯上的不同,在算法原理上是没有任何区别的。

3. 决策条件的选择艺术

生活经验告诉我们:挑重要的问题先问。决策树也确实是按这个思路来选择决策条件的。思考这个问题,可以从“怎样才算是好的决策条件”开始。决策树最终是要解决分类问题,那么最理想的情况当然是选好决策条件后,一个if-else就正好把数据集按正类和负类分成两个部分。

不过,现实通常没有“一刀切”这么理想,总会有一些不识时务的样本“跑”到不属于自己的类别里,我们退而求其次,希望分类结果中这些不识时务的杂质越少越好,也就是分类结果越纯越好。

依照这个目标,决策树引入了“纯度”的概念,集合中归属同一类别的样本越多,我们就说这个集合的纯度越高。每一次使用if-else进行判别,二元分类问题的数据集都会被分成两个子集,那么怎么评价分类的效果呢?可以通过子集的纯度。子集纯度越高,说明杂质越少,分类效果就越好。

节点纯度的度量规则

使用决策树这套框架的分类算法有很多,其中最著名的决策树算法一共有三种,分别是ID3、C4.5和CART,这三种决策树算法分别采用了信息增益、增益率和基尼指数这三种不同的指标作为决策条件的选择依据。
虽然三种决策树算法分别选择了三种不同的数学指标,但这些指标都有一个共同的目的:提高分支下的节点纯度(Purity)
决策树算法中使用了大量二叉树进行判别,在一次判别后,最理想的情况就是二叉树的一个分支纯粹是正类,另一个分支纯粹是负类,这样就意味着完整和准确地完成了一次分类。但大多数的判别结果都没有这么理性,所以一个分支下会既包含正类又包含负类,不过我们希望看到的是一个分支包含的样本尽可能地都属于同一个类,也就是希望这个分支下面的样本类别越纯越好,所以用了“纯度”来进行描述。纯度有三点需要记住:

  • 当一个分支下的所有样本都属于同一个类时,纯度达到最高值。
  • 当一个分支下样本所属的类别一半是正类一半是负类时,纯度取得最低值。
  • 纯度考察的是同一个类的占比,并不在乎该类究竟是正类还是负类,譬如某个分支下无论是正类占70%,还是负类占70%,纯度的度量值都是一样的。

纯度的度量方法

我们的任务就是要找到一种满足纯度达到最大值和最小值条件的纯度度量函数,它既要满足这三点需求,又要能作为量化方法。
现在让我们把这三点要求作成图像(可视化的图像有助于更直观地理解),同时,如果我们能够找到一款函数符合这个图像,就等于找到了符合条件的函数。我们约定所作图像的横轴表示某个类的占比,纵轴表示纯度值,首先来分析极值点的位置。
根据第一点要求,某个类占比分别达到最大值和最小值时,纯度达到最高值。最大值好理解,为什么最小值也能令纯度达到最高?可以反过来想,这个类取得最小值时,另一个类就取得了最大值,所以纯度也就最高。根据分析,我们知道了纯度将在横坐标的头尾两个位置达到最大值。
根据第二点要求,纯度的最小值出现在某个类占比为50%的时候。换句话说,当横坐标为0.5时,纯度取得最低值。
现在可以作出图像了。根据上述分析的纯度最高值和最低值随类占比的变化情况,我们用一条平滑的曲线连接起这三个点,则所作出来的图像应该类似一条微笑曲线(见图7-2)。
在这里插入图片描述
不过我们在机器学习中更喜欢计算的是“损失值”,那么对纯度度量函数的要求正好与纯度函数的要求相反,因为纯度值越低意味着损失值越高,反之则越低。所以纯度度量函数所作出来的图像正好相反(见图7-3)。
在这里插入图片描述

4. 决策树剪枝问题

经过前面的讨论我们已经约略知道,决策树会根据数据集的各个维度的重要性扩充if-else分支,这个扩充工作会一直持续下去,直到满足停止条件。其中有两个停止条件涉及属性维度,其中一个停止条件是可供进行分支判断的属性维度已经全部用完,在这种停止条件下决策树很容易出现过拟合的情况。
这是因为训练集数据可能出现“假性关联”的问题。每个数据集都会有各种各样的属性维度,总会出现一些属性维度与样本分类实际上并不存在关联关系的情况。在使用决策树算法时,最理想的情况是这些不相关属性维度通常都会留到最后,要么是样本已经依赖前面有效的属性维度划分完毕,要么是剩余样本在这些无关属性下表现完全一样而无法继续依赖现有属性来形成判断分支。但现实是可能由于各种原因,如数据集收集片面或随机扰动等,导致数据出现了假性关联,那么这些实际无效的属性维度就会被决策树算法当作有效的分支判断条件。用这种存在假性关联数据集训练得到的决策树模型就会出现过度学习的情况,学到了并不具备普遍意义的分类决策条件,也就是出现过拟合,导致决策树模型的分类有效性降低。

“剪枝”这个术语名字很形象,如同园林里的树木经过修剪枝条后会更具观赏价值,给决策树剪枝也是为了提高决策树算法分类的有效性。具体的剪枝算法有很多款,但根据剪枝操作触发时机的不同,基本可以分成两种,一种称为预剪枝,另一种称为后剪枝

  • 所谓预剪枝,即在分支划分前就进行剪枝判断,如果判断结果是需要剪枝,则不进行该分支划分,也就是还没形成分支就进行“剪枝”,用我们更常用的说法即将分支“扼杀在萌芽状态”。
  • 所谓后剪枝,则是在分支划分之后,通常是决策树的各个判断分支已经形成后,才开始进行剪枝判断。后剪枝可能更符合我们日常中对“剪枝”一词的理解。

无论预剪枝还是后剪枝,剪枝都分为剪枝判断和剪枝操作两个步骤,只有判断为需要剪枝的,才会实际进行剪枝操作。看来这个剪枝判断是防止决策树算法过拟合的重点了,会不会很复杂呢?剪枝判断是各款剪枝算法的主要发力点,很难一概而论,但总的来说就是遵从一个原则:**如果剪枝后决策树模型的分类在验证集上的有效性能够得到提高,就判定为需要进行剪枝,否则不剪枝。**请注意,这里剪枝所使用的数据集不再是训练模型所使用的训练集,而是选择使用验证集来进行相关判断。

举个例子,假设当前某个数据集的特征E被决策树算法选作分支判断条件,但在验证集,由特征E判断为正类的样本中实际包含了7个正类、4个负类,而判断为负类的样本中实际包含了5个正类、3个负类,如图7-4所示。
在这里插入图片描述也就是在19个样本中,有7个正类、3个负类一共11个样本正确分类,这时特征E的分类有效性为 11 19 \frac{11}{19} 1911。这时如果进行剪枝,也就是把根据特征E划分的子树去掉,取当前集合中占比最大的类也就是正类作为当前分类,则可以将12个正类样本正确划分,也就是分类有效性为 12 19 \frac{12}{19} 1912,剪枝后的有效性高于剪枝前的有效性,因此判断为进行剪枝。完成剪枝操作之后,分支判断变更如图7-5所示。

在这里插入图片描述

二、决策树分类的算法原理

1. 基本思路

我们已经知道,决策树算法是依靠树形结构来完成分类,而这个巨大的树形判别结构由许许多多通过if-else判别语句构成的分支组合而成。再巨大的树形结构也是由一个if-else判别分支开始的。
这时就遇到决策树的第一个问题,if-else要进行判别,需要首先提供判别条件,那么判别条件从何而来?这个问题分为两个步骤解决。

  • 第一步是来源,数据集中的数据都是以特征维度进行组织的,这些特征维度也可以作为一个集合,称为帖子维度集,或者称为属性集。我们要发现特征维度与类别可能存在的关联关系,所以判别条件就从这个集合中来。
  • 第二步是选择。特征维度往往有许多个,所以特征维度集也不止一个元素,那么就存在一个问题:选取哪一个特征维度作为当前if-else的判别条件呢?这就需要进行比较,要比较就需要有标准,所以我们引入了“纯度”的概念,哪个特征维度“提纯”效果最好,就选哪个特征维度作为判别条件。

在最开始的时候,决策树选取了一个特征维度作为判别条件,在数据结构中通常称之为“根节点”,根节点通过if-else形成最初的分支,决策树就算“发芽”了。如果这时分类没有完成,刚刚形成的分支还需要继续形成分支,这就是决策树的第一个关键机制:节点分裂。在数据结构中,分支节点通常称为叶子节点,如果叶子节点再分裂形成节点,就称为子树(见图7-6)。有人也把这个过程称为递归生成子树。
在这里插入图片描述叶子节点可能不断分类形成子树,正如if-else语句可以不断嵌套if-else,利用这个机制,一次判别不能完全达到把数据集划分成正类和负类的效果,那就在判别结果中继续进行判别。决策树通过叶子节点不断分裂形成子树,或者说通过if-else不断嵌套if-else,每一次分裂都相当于一次对分类结果的“提纯”,不断重复这个过程,最终就达到分类目标了。

上述就是决策树算法进行分类的主要流程方式,也许你没有看出来,其实这里面包含了决策树的第二个问题:停止分裂问题。决策树能够通过节点分裂不断地对分类结果进行提纯,但“不断”总也是有“断”的时候,就如虽然if-else能无限嵌套if-else,但真正写出来的if-else总是相当有限的。那么,决策树该在什么时候停止节点分裂呢?停止条件有以下三种:

  • 到达终点。虽然我们在讨论节点分裂,但须记住节点分裂是手段而不是目的,目的是完成分类。当数据集已经完成了分类,也就是当前集合的样本都属于同一类时,节点分裂就停止了。
  • 自然停止。决策树依赖特征维度作为判别条件,如果特征维度已经全部用上了,自然也就无法继续进行节点分裂。可是如果分类还没有完成则怎么办呢?决策树的处理方法很简单,就以占比最大的类别作为当前节点的归属类别。
  • 选不出来。除了上述两种不难想到的停止条件,还有一种意料之外情理之中的停止条件,就是选不出来。决策树通过比较不同特征维度的提纯效果来进行判别条件的选择,但是同样可能发生的极端情况是,大家的提纯效果完全一样,这时就无法选择了,分裂也就到此为止。这时同样以占比最大的类别作为当前节点的归属类别。

2. 数学解析

(1) 信息熵

信息熵由信息论之父香农(Shannon)提出,是信息论中非常重要的一个概念,也是决策树分类算法中非常重要的一个概念,任何一套介绍决策树分类算法的教材都不会错过信息熵。
“熵”是热力学的概念,用来表示无序程度,而香农借用了这个概念,用来量化信息量。
要理解信息熵在决策树中的作用,关键就是抓住一点:度量纯度。度量纯度是一个环环相扣的问题,为什么决策树要度量纯度?因为希望决策条件能够一锤定音,使得数据集通过这个决策条件就能完成分类。那么怎样度量纯度?前面我们介绍了度量纯度的三点要求,作成图像就是半只鸡蛋,简单来说就是看占比最大的那个类的占比,占比越接近100%,纯度就越高,反之占比越小,纯度就越低。
信息熵正好能满足这个要求。信息熵原本是用于衡量不确定性的指标,也就是说情况越乱,信息熵越大。不确定性可以用发生概率来理解,如果一件事百分之百会发生,我们会把这事儿称为“板上钉钉”,即很确定的事儿,信息熵就无限接近于最小值。但如果一件事儿就像扔硬币一样,正反两面出现的概率参半,非得等到最后一刻才揭晓结果,这件事儿就很不确定,信息熵就无限接近于最大值。
这其实与决策树对纯度的要求不谋而合。在分类的场景中,各种事情发生的概率可以替换成各种类别的占比。占比不相上下的时候,信息熵就比较大,当一个类别能够一家独大,信息熵就比较小。
数学表达式:
H ( x ) = − ∑ k = 1 N p k l o g 2 ( p k ) H(x)=-\sum^N_{k=1}p_klog_2(p_k) H(x)=k=1Npklog2(pk)

这里的p就是概率的意思,这里的大写“X”不是常见的未知量符号,而是表示进行信息熵计算的集合。在分类中也可以按各个类别的占比来理解。信息熵的计算非常简单,就分三次四则运算,即相乘、求和最后取反。在Numpy中,可以使用函数“log2”来直接完成对数计算。

我们使用信息熵来计算两种极端情况。
在二元分类问题中,如果当前样本都属于一个类别a,也就是a类占比达到100%(同时也意味着另一个类别b占比为0),pa=1时,信息熵H为:
H ( 1 ) = − ( 1 ∗ l o g 2 ( 1 ) + 0 ) = − ( 1 ∗ 0 + 0 ) = 0 H(1) = - (1*log_2(1)+0)=-(1*0+0)=0 H(1)=(1log2(1)+0)=(10+0)=0

由于只有一种类别,p达到最大值,也就是情况非常确定,所以信息熵取得最小值0。那么什么情况下最不确定、最乱呢?当然是两种类别各占一半的情况,也就是类别占比都为50%(pa=pb=0.5),这时信息熵H为:
H ( x ) = − ( 0.5 ∗ l o g 2 ( 0.5 ) + 0.5 ∗ l o g 2 ( 0.5 ) ) = − ( 0.5 ∗ − 1 + 0.5 ∗ − 1 ) = 1 H(x)=-(0.5*log_2(0.5)+0.5*log_2(0.5))=-(0.5*-1+0.5*-1)=1 H(x)=(0.5log2(0.5)+0.5log2(0.5))=(0.51+0.51)=1

ID3

为什么决策树要使用信息熵,现在应该很清楚了。但信息熵是以整个集合作为计算对象,那么怎样利用信息熵从特征维度集中选择决策条件呢?不同的决策树算法有不同的方法,ID3算法使用了信息增益G

经过一次if-else判别后,原来的集合就会被分为两个子集合,既然分类的目的是希望把包含多种类别的集合,尽可能划分成只包含一种类别的多个子集,也就是希望通过划分能够达到“提纯”的效果。如果子集的纯度比原来的集合纯度要高,说明这次通过if-else的划分起到了正面作用,而纯度提升越多,当然说明选择的判别条件越合适,可以作为一种不同特征维度之间的比较方法。ID3中选择用信息熵来衡量样本集合的纯度,那么“提纯”效果的好坏就可以通过比较划分前后集合的信息熵来判断,也就是做一个减法,具体来说是用划分前集合的信息熵减去按特征属性a划分后集合的信息熵,就得到信息增益,公式如下:
G ( D , a ) = H ( x ) − ∑ v = 1 V D v D H ( D v ) (7-1) G(D,a)=H(x)-\sum^V_{v=1}\frac{D^v}{D}H(D^v)\tag{7-1} G(D,a)=H(x)v=1VDDvH(Dv)(7-1)

  • V:这是表示按特征维度a划分后有几个子集的意思,若加入划分后产生3个子集,那么此时V=3。小写字母v则表示划分后的某一个子集。
  • |D|:表示集合D,旁边加了两道竖线并不是求绝对值的意思,而是求集合D的元素个数。同样,|Dv|表示的是划分后的某个子集的元素个数。
  • 用原集合的信息增益,减去划分后产生的所有子集的信息熵的加权和,就得到按特征维度a进行划分的信息增益。比较不同特征属性的信息增益,增益越大,说明提纯效果越好,提纯效果最好的那个特征属性就当选为当前if-else的判别条件。
C4.5

C4.5算法可以认为是ID3算法的“plus”版本,唯一的区别就在于用信息增益比来替代信息增益。信息增益与谁比较呢?与特征维度的固有值(IntrinsicValue)比,具体数学表达式如下:
G r = G ( D , a ) I V ( a ) (7-2) G_r=\frac{G(D,a)}{IV(a)}\tag{7-2} Gr=IV(a)G(D,a)(7-2)
特征维度的固有值是针对ID3对多值特征的偏好所设计的,具体作用就是特征维度的值越多,固有值越大。信息增益比以固有值作为除数,就可以消除多值在选择特征维度时所产生的影响。固有值的数学表达式具体如下:
I V ( a ) = ∣ D v ∣ ∣ D ∣ l o g 2 ∣ D v ∣ ∣ D ∣ (7-3) IV(a)=\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|} \tag{7-3} IV(a)=DDvlog2DDv(7-3)

(2) 基尼指数

CART

CART算法是当前最为常用的决策树算法之一,在决策条件的选择上没有沿用信息熵,而是采用了新的方案——基尼指数,不过,基尼指数和信息熵的主要原理是非常相似的,数学表达式如下:
G i n i ( D ) = 1 − ∑ k = 1 N P k 2 (7-4) Gini(D) = 1-\sum^N_{k=1}P^2_k\tag{7-4} Gini(D)=1k=1NPk2(7-4)
这里的大写“D”同样是表示进行基尼指数计算的集合。基尼指数的计算过程也分为三次四则运算,即相乘、求和,最后用1减去求和的结果。相比信息熵,基尼指数的第一步采取了占比p直接相乘的方法,省略了对数运算,计算更为简单。
我们同样以基尼指数计算上面两种极端情况。首先还是二元分类问题,假设a类占比达到100%,也就是pa=1时,基尼指数为:
G i n i ( D ) = 1 − ( 1 × 1 + 0 × 0 ) = 1 − 1 = 0 Gini(D)=1-(1×1+0×0)=1-1=0 Gini(D)=1(1×1+0×0)=11=0
一个类别占比达最大,也就是情况最确定,所以这时的基尼指数取得最小值0。假设两个类别占比参半,也就是pa=pb=0.5,这时基尼指数为:
G i n i ( D ) = 1 − ( 0.5 × 0.5 + 0.5 × 0.5 ) = 1 − 0.5 = 0.5 Gini(D)=1-(0.5×0.5+0.5×0.5)=1-0.5=0.5 Gini(D)=1(0.5×0.5+0.5×0.5)=10.5=0.5
占比各半也是最不确定的情况,所以基尼指数取得最大值0.5。基尼指数取得最大值和最小值的情况与信息熵是非常相似的,最明显的区别在于基尼指数的最大值是0.5而不是1
使用基尼指数选择特征维度的过程与前面基本一致,首先还是计算选择某个特征维度作为判别条件的基尼指数,计算方法和计算信息增益非常类似,也是首先求得各个子集的元素占比,然后乘以该子集的基尼指数,最后全部加起来求和,公式如下:
G i n i a = ∑ v = 1 V ∣ D v ∣ ∣ D ∣ G i n i ( D v ) (7-5) Gini_a = \sum^V_{v=1}\frac{|D^v|}{|D|}Gini(D^v)\tag{7-5} Ginia=v=1VDDvGini(Dv)(7-5)

3. 决策树分类算法的具体步骤

在这里插入图片描述使用决策树算法与编写if-else很像,可以认为是自动抽取判断条件的if-else,具体需要四步:

  1. 选定纯度度量指标。
  2. 利用纯度度量指标,依次计算依据数据集中现有的各个特征得到的纯度,选取纯度能达到最大的那个特征作为该次的“条件判断”。
  3. 利用该特征作为“条件判断”切分数据集,同时将该特征从切分后的子集中剔除(也即不能再用该特征切分子集了)。
  4. 重复第二、第三步,直到再没有特征,或切分后的数据集均为同一类。

三、在python中使用决策树分类算法

在Scikit-Learn库中,基于决策树这一大类的算法模型的相关类库都在sklearn.tree包中。tree包中提供了7个类,但有3个类是用于导出和绘制决策树,实际的决策树算法只有4种,这4种又分为两类,分别用于解决分类问题和回归问题。

  • DecisionTreeClassifier类:经典的决策树分类算法,其中有一个名为“criterion”的参数,给这个参数传入字符串“gini”,将使用基尼指数;传入字符串“entropy”,则使用信息增益。默认使用的是基尼指数。余下3个决策树算法都有这个参数。
  • DecisionTreeRegressor类:用决策树算法解决反回归问题。
  • ExtraTreeClassifier类:这也是一款决策树分类算法,但与前面经典的决策树分类算法不同,该算法在决策条件选择环节加入了随机性,不是从全部的特征维度集中选取,而是首先随机抽取n个特征维度来构成新的集合,然后再在新集合中选取决策条件。n的值通过参数“max_features”设置,当max_features设置为1时,相当于决策条件完全通过随机抽取得到。
  • ExtraTreeRegressor类:与ExtraTreeClassifier类似,同样在决策条件选择环境加入随机性,用于解决回归问题。
from sklearn.datasets import load_iris
# 从库中导入决策树模型中的决策树分类算法
from sklearn.tree import DecisionTreeClassifier

# 载入鸢尾花数据集
X, y = load_iris(return_X_y=True)
# 训练模型
clf = DecisionTreeClassifier().fit(X, y)
# 使用模型进行分类预测
print(clf.predict(X))
# 使用默认的性能评估器评分
print(clf.score(X, y))
====================================
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
1.0

四、决策树分类算法的使用场景

在这里插入图片描述算法使用案例:
Kinect是微软公司在2010年6月发布的XBOX 360体感交互外设,这是一种较为新颖的人机交互显示技术,使用者在Kinect镜头前所做的动作将实时呈现在游戏的画面中。为了实现这一效果,Kinect正是利用决策树分类算法,对镜头捕获的玩家行为图像进行分类,从而实现了效果良好的人体行为识别功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZhShy23

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值