集成学习(2):boosting(GBDT算法)

树模型本身具有蛮多优点的:
可解释性强
可处理混合类型的特征
不用归一化处理
由特征组合的作用
可自然的处理缺失值
对异常点鲁棒性较强
有特征选择的作用
可扩展性强,容易并行

缺点是:
缺乏平滑性
不适合处理高维度稀疏的数据

那么回归树中的可划分点包含了所有特征的所有可取的值。在分类树中最佳划分点的判别标准是熵或者基尼系数,都是用纯度来衡量的,
但是在回归树中的样本标签是连续数值,所以再使用熵之类的指标不再合适,取而代之的是平方误差,它能很好的评判拟合程度。

GBDT全称Gradient Boosting Decison Tree

GBDT别名(GBT)

Boosting算法的学习机制:先从初始训练集训练出一个基学习器,再根据基学习器的表现对训练样本分布进行调整,使得先前基学习器做错的的训练样本在后续受到更多关注,然后基于调整后的样本分布训练下一个基学习器;重复进行,直到基学习器数目达到事先指定的值T,最终将这T个基学习器加权结合。

GBDT属于Boosting算法。它的思想是希望通过构建弱分类器得到表现较好的强分类器。

提升树 (又叫残差提升树)(是GBDT损失函数取平方损失的一个特例)

这里我把它交做残差提升树,为了和梯度提升树(GBDT)做区分

残差提升树的计算流程

令初始预测值为y0,初始值可以是为0(也可以是所有真实值的平均值),样本的真实值为y

就会得到残差L(y,y0), 新建树模型T学习残差L(y,y0)的值,输出数模型预测值T(1),此时预测值就为y0+wT(1) #如果这个模型能够完美预测出残差,y0+T(1)就直接为真实值了,w表示学习率下面的所有w都一样

又会得到残差L(y,y0+T(1)), 再新建一个树模型T学习新的残差L的值,输出树模型预测值T(2),此时预测值就为y0+wT(1)+wT(2)

又会得到残差L(y,y0+T(1)+T(2)), 再新建一个树模型T学习新的残差L的值,输出树模型预测值T(3),此时预测值就为y0+wT(1)+wT(2)+wT(3)

又会得到残差L(y,y0+T(1)+T(2)+T(3)), 再新建一个树模型T学习残差L的值,输出树模型预测值T(4),此时预测值就为y0+wT(1)+wT(2)+wT(3)+wT(4)

又会得到残差L(y,y0+T(1)+T(2)+T(3)+T(4)), 再新建一个树模型T学习残差L的值,输出树模型预测值T(4),此时预测值就为y0+wT(1)+wT(2)+wT(3)+wT(4)+wT(5)

。。。。。。

新建的树模型的个数越多,一直拟合残差下去就会得到非常逼近真实值y的值,也就会效果越好。

为什么要加一个学习率,主要是为了防止一步到位,如果T(1)能够预测出来第一次残差,我们就直接得到真实值y=y0+T(1)了,这样的话一个树就直接拟合了对于新样本来说很不友好。所以加个小于1的权重w后y0+wT(1)就不是真实值了,又会产生残差让下一个很多分类器学习,每个分类器都能学习到不同的作用。

残差提升树举例子说明:

下面说明如何构建一个树模型(一般好像只能选择cart树)已经如何使用树来预测/拟合残差,假设数据如下,残差为5
在这里插入图片描述
所以样本1的拟合数据的预测值就是6* λ \lambda λ,样本4预测值是3* λ \lambda λ。样本2和样本3在同一节点。所以样本3和样本2的预测值是他们的平均值。其中 λ \lambda λ表示学习率。上图也就是cart树根据特征数据构建和预测。

残差提升树举例子说明:
在这里插入图片描述

上图中红线的节点内数值是拟合残差数值的树,节点内存储的是分类的残差值,ave是节点内所有数据的平均值。上面我没有用到学习率。只是作为展示用。拟合残差数据和拟合真实数据一样。只不过值用残差表示罢了。上面我们选择初始值是所有数据的平均值,当然也能选择初始值为0.

问题?是否所有拟合残差的树的根据特征构建的树结构都是一样的?。
梯度提升树:和残差提升树类似,但是模型拟合的是梯度不再是残差了,而是负的梯度(但是当损失函数取平方损失时,梯度提升树就变成残差提升树了,提升树是梯度提升树的一个特例)

梯度提升树:(GBDT)

1.加法模型
也就是: Θ t = Θ t − 1 + △ Θ \Theta^{t}=\Theta^{t-1}+\triangle \Theta Θt=Θt1+Θ

另一个解释:
首先给定一个初始值 Θ 0 = 0 \Theta^{0}=0 Θ0=0 Θ 0 \Theta^{0} Θ0表示的是初始给定的(预测)值, Θ t \Theta^{t} Θt是不断迭代拟合的预测值。在提升树中, △ Θ \triangle \Theta Θ表示的是残差。
Θ t = Θ t − 1 + △ Θ \Theta^{t}=\Theta^{t-1}+\triangle \Theta Θt=Θt1+Θ 这句话的意思是,不停的向预测值加一个值后,这个模型会越来越逼近真实值。

我们的目标是最小化 L ( y ^ , Θ ) L(\hat {y},\Theta) L(y^,Θ),其中 y ^ \hat y y^表示的是某一个样本的真实标签数据值,是一个常数,求导时没有影响。
根据一阶泰勒公式,损失L又可近似表示为
L ( y ^ , Θ t ) = L ( y ^ , Θ t − 1 + △ Θ ) ≈ L ( y ^ , Θ t − 1 ) + L ′ ( y ^ , Θ t − 1 ) △ Θ L(\hat {y},\Theta^{t})=L(\hat y,\Theta^{t-1}+\triangle \Theta)\approx L(\hat y,\Theta^{t-1})+L'(\hat y,\Theta^{t-1})\triangle \Theta L(y^,Θt)=L(y^,Θt1+Θ)L(y^,Θt1)+L(y^,Θt1)Θ 使用泰勒一阶展开式对其进行一阶近似
△ Θ = − α L ′ ( y ^ , Θ t − 1 ) \triangle \Theta=-\alpha L'(\hat y,\Theta^{t-1}) Θ=αL(y^,Θt1)时,上式又可写成:

L ( y ^ , Θ t ) ≈ L ( y ^ , Θ t − 1 ) − ( α L ′ ( y ^ , Θ t − 1 ) ) 2 L(\hat {y},\Theta^{t})\approx L(\hat y,\Theta^{t-1})-({\alpha L'(\hat y,\Theta^{t-1})})^{2} L(y^,Θt)L(y^,Θt1)(αL(y^,Θt1))2
因为 ( L ′ ( y ^ , Θ t − 1 ) ) 2 > = 0 ({L'(\hat y,\Theta^{t-1})})^{2}>=0 (L(y^,Θt1))2>=0
∴ L ( y ^ , Θ t ) \therefore L(\hat {y},\Theta^{t}) L(y^,Θt)的值是上一次迭代( L ( y ^ , Θ t − 1 ) L(\hat y,\Theta^{t-1}) L(y^,Θt1))一直不断减小的形成的,也就意味着下一次迭代后的损失会变小,这样就达到了我们要使函数 L L L最小化的目标了

梯度提升树:和残差提升树类似,但是模型拟合的是梯度不再是残差了,而是负的梯度(但是当损失函数取平方损失时,梯度提升树就变成残差提升树了,提升树是梯度提升树的一个特例)

########梯度提升树的计算流程:
令初始预测值为y0,初始值可以是为0(也可以是所有真实值的平均值),样本的真实值为y

就会得到梯度L(y,y0)/y0, 新建树模型T学习负的梯度的值,输出数模型预测值T(1),此时预测值就为y0+wT(1) #如果这个模型能够完美预测出残差,y0+T(1)就直接为真实值了,w表示学习率下面的所有w都一样

又会得到残差L(y,y0+T(1)), 再新建一个树模型T学习新的残差L的值,输出数模型预测值T(2),此时预测值就为y0+wT(1)+wT(2)

又会得到残差L(y,y0+T(1)+T(2)), 再新建一个树模型T学习新的残差L的值,输出数模型预测值T(3),此时预测值就为y0+wT(1)+wT(2)+wT(3)

又会得到残差L(y,y0+T(1)+T(2)+T(3)), 再新建一个树模型T学习残差L的值,输出数模型预测值T(4),此时预测值就为y0+wT(1)+wT(2)+wT(3)+wT(4)

又会得到残差L(y,y0+T(1)+T(2)+T(3)+T(4)), 再新建一个树模型T学习残差L的值,输出数模型预测值T(4),此时预测值就为y0+wT(1)+wT(2)+wT(3)+wT(4)+wT(5)

GBDT损失函数

这里我们再对常用的GBDT损失函数做一个总结。
1.对于分类算法,其损失函数一般有对数损失函数和指数损失函数两种:
(1)如果是指数损失函数。 如果用指数损失函数,此时GBDT退化为Adaboost算法[1]
(2)如果是对数损失函数, 分为二元分类和多元分类两种
2.对于回归算法,常用损失函数有如下4种:
(1)均方差,这个是最常见的回归损失函数了 。 如果损失函数是均方损失的时候,梯度就变成了残差,此时梯度提升树就变成了提升树的一个特例了
(2)绝对损失,这个损失函数也很常见
(3)Huber损失,它是均方差和绝对损失的折衷产物,对于远离中心的异常点,采用绝对损失,而中心附近的点采用均方差。这个界限一般用分位数点度量。
(4)分位数损失。它对应的是分位数回归的损失函数。

提升树和梯度提升树的区别是什么??

就是提升树将残差作为下一个树的输入,梯度提升树是将负梯度值作为下棵树的输入。

提升树仅计算残差并不能有较好的收敛速度,计算梯度来近似效率更高,收敛更快。

GBDT的优缺点:

GBD的优点:
(1)可以灵活处理各种类型的数据,包括连续值和离散值。
(2)在相对少的调参时间情况下,预测的准备率也可以比较高。这个是相对SVM来说的。
(3)使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。

GBDT的缺点有:
(1)由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。

XGBoost算法:是GBDT的改进算法,使用二阶梯度进行拟合

XGBoost是陈天奇等人开发的一个开源机器学习项目,高效地实现了GBDT算法并进行了算法和工程上的许多改进,被广泛应用在Kaggle竞赛及其他许多机器学习竞赛中并取得了不错的成绩。

说到XGBoost,不得不提GBDT(Gradient Boosting Decision Tree)。因为XGBoost本质上还是一个GBDT,但是力争把速度和效率发挥到极致,所以叫X (Extreme) GBoosted。包括前面说过,两者都是boosting方法

为什么XGBoost要用二阶泰勒展开,相比于GBDT优势在哪里?
XGBoost使用了一阶和二阶偏导, 二阶导数有利于梯度下降的更快更准. 使用泰勒展开取得函数做自变量的二阶导数形式, 可以在不选定损失函数具体形式的情况下, 仅仅依靠输入数据的值就可以进行叶子分裂优化计算, 本质上也就把损失函数的选取和模型算法优化/参数选择分开了. 这种去耦合增加了XGBoost的适用性, 使得它按需选取损失函数, 可以用于分类, 也可以用于回归。

O b j t = ∑ i = 1 n l ( y i , y ^ i t − 1 + f t ( x i ) ) + Ω ( f t ) + c o n s t a n t Obj^{t}=\sum_{i=1}^{n}l(y_{i},\hat y_{i}^{t-1}+f_{t}(x_{i}))+\Omega(f_{t})+constant Objt=i=1nl(yi,y^it1+ft(xi))+Ω(ft)+constant
式中 y ^ i t − 1 = ∑ j = 0 t − 1 f j ( x i ) = f 0 ( x i ) + f 1 ( x i ) + f 2 ( x i ) . . . f t − 1 ( x i ) \hat y_{i}^{t-1}=\sum_{j=0}^{t-1} f_{j}(x_{i})=f_0(x_i)+f_1(x_i)+f_2(x_i)...f_{t-1}(x_i) y^it1=j=0t1fj(xi)=f0(xi)+f1(xi)+f2(xi)...ft1(xi) ,n表示样本数量,也就是使每一个样本( x i x_{i} xi)的误差总和达到最小。
一个样本的误差为 O b j t = l ( y 1 , y ^ 1 t − 1 + f t ( x 1 ) ) + Ω ( f t ) + c o n s t a n t Obj^{t}=l(y_{1},\hat y_{1}^{t-1}+f_{t}(x_{1}))+\Omega(f_{t})+constant Objt=l(y1,y^1t1+ft(x1))+Ω(ft)+constant
一个样本的预测值是 y ^ 1 t − 1 = ∑ j = 0 t − 1 f j ( x 1 ) = f 0 ( x 1 ) + f 1 ( x 1 ) + f 2 ( x 1 ) . . . f t − 1 ( x 1 ) \hat y_{1}^{t-1}=\sum_{j=0}^{t-1} f_{j}(x_{1})=f_0(x_1)+f_1(x_1)+f_2(x_1)...f_{t-1}(x_1) y^1t1=j=0t1fj(x1)=f0(x1)+f1(x1)+f2(x1)...ft1(x1),x1表示每次树的建模都是针对于原始一样的数据建模的,但是每次训练的目标是不同的。也就是输入相同,输出不同(输出的是残差)。

为什么不将 y ^ t − 1 \hat y^{t-1} y^t1写成 y ^ t − 1 ( x ) \hat y^{t-1}(x) y^t1(x),因为 y ^ \hat y y^是个复杂的符合函数, y ^ t − 1 = y ^ t − 2 + x = y ^ t − 1 ( y ^ t − 2 , x ) \hat y^{t-1}=\hat y^{t-2}+x=\hat y^{t-1}(\hat y^{t-2},x) y^t1=y^t2+x=y^t1(y^t2,x)而且 y ^ t − 2 \hat y^{t-2} y^t2也是关于 y ^ t − 3 \hat y^{t-3} y^t3和x的函数,这样就无法表示了,所以就只能直接省略了,如果将其写成 y ^ t − 1 ( x ) \hat y^{t-1}(x) y^t1(x)那这样含义就变化了,变成了只是和x的函数了,里面的复合函数就被省略了,求导时就没法写出来了。如果不写的话,求导就可以直接写成,这样就导致里面的符合函数被忽略了,符合函数对于x也会求导,就像 f ( g ( x ) , x ) 和 f ( x ) 求 导 结 果 分 别 是 f f(g(x),x)和f(x)求导结果分别是f f(g(x),x)f(x)f。为什么 f t ( x i ) f_{t}(x_{i}) ft(xi)不省略,原因是如果省略了你会以为 f t ( x i ) f_t(x_{i}) ft(xi)会和之前的所有 ∑ i = 0 n x ( i ) \sum_{i=0}^{n}x(_{i}) i=0nx(i)有关了,实际上之和最后个 x i x_{i} xi有关。

还有一个疑问是? f t ( x i ) f_{t}(x_{i}) ft(xi)里面的x0,x1,x2难道不是一样的吗?应该每颗输入的树都是一样的输入吧,也就是x0 = = == ==x1 = = == ==xi吗?

在这里插入图片描述
从图中可以看出 f t ( x ) = w q ( x ) f_t(x)=w_{q(x)} ft(x)=wq(x),也就是说 w q ( x ) w_{q(x)} wq(x)的预测值就是t时刻树的预测值 f t ( x ) f_{t}(x) ft(x), x x x表示某一个样本, q ( x ) q(x) q(x)表示的是样本 x x x在树的节点位置,最左边为1,最右边位置为T。树总共有T个节点

式中, y ^ i t − 1 \hat y_{i}^{t-1} y^it1表示第i个样本样本在t-1时刻的预测值(预测值是之前所有数拟合残差之和 y ^ i t − 1 = ∑ j = 0 t − 1 f j ( x i ) \hat y_{i}^{t-1}=\sum_{j=0}^{t-1}f_{j}(x_{i}) y^it1=j=0t1fj(xi)

在这里插入图片描述

变量的含义
下面其中
T T T表示的是树的叶子节点的个数,
w w w表示叶子节点的分数。 γ \gamma γ
n n n表示样本数,t时刻拟合的最好结果是使所有样本总和的误差最小。
t t t表示时刻,相当于序列数据的第几轮拟合。

Ω ( f t ) \Omega(f_{t}) Ω(ft)表示的是第t颗树的相关信息,包括第T颗树的叶子节点截止节点上的w值。如果多个样本被分到一个树的节点节点中,那么w是什么值???
从图中可以看出 f t ( x ) = w q ( x ) f_t(x)=w_{q(x)} ft(x)=wq(x),也就是说 w q ( x ) w_{q(x)} wq(x)的预测值就是t时刻树的预测值 f t ( x ) f_{t}(x) ft(x)

从上图可以看出 w j w_{j} wj可以看出是w3=-1包括两个人,w3到底是什么值,怎么得到的。

在这里插入图片描述
**第一步转化为第二步是根据 f t ( x i ) = w q ( x i ) f_t(x_{i})=w_{q(x_{i})} ft(xi)=wq(xi) ,q表示一个函数,寻找x的坐标位置
j j j取值为1的时候, I j = { i ∣ q ( x i ) = 1 } I_j=\{i|q(x_i)=1\} Ij={iq(xi)=1}的样本的集合,i就表示第i个样本,也就是 x i x_i xi的缩写
也就是当 j j j取值为1的时候,也就是只计算第一个数的节点的obj
O b j ( t ) = ∑ i ∈ I 1 g i w 1 + 1 2 ( ∑ i ∈ I j h i + λ ) w 1 2 ] + ∑ j = 2 T ( ∑ i ∈ I j g i w 1 + 1 2 ( ∑ i ∈ I j h i + λ ) w j 2 ] + γ T Obj^{(t)}=\sum_{i\in I_{1}}g_{i}w_{1}+\frac{1}{2} (\sum_{i\in I_{j}} h_{i}+\lambda)w_1^2]+\sum_{j=2}^{T}({\sum_{i \in I_{j}}}g_{i}w_{1}+\frac{1}{2} (\sum_{i\in I_{j}} h_{i}+\lambda )w_j^2]+ \gamma T Obj(t)=iI1giw1+21(iIjhi+λ)w12]+j=2T(iIjgiw1+21(iIjhi+λ)wj2]+γT

如果数据只有3个样本,即为x1,x2,x3。x1和x2在第一个节点,其分数为(w1),x3在第二个节点,其分数为(w2)。也就是树只有2个节点(T=2)。

O b t t = [ ( g x 1 + g x 2 ) w 1 + 1 2 ( h x 1 + h x 2 + λ ) w 1 2 ] + ∑ j = 2 T = 2 ( ∑ i ∈ I j g i w j + 1 2 ( ∑ i ∈ I j h i + l a m ) w j 2 ] + γ T Obt^{t}=[(g_{x1}+g_{x2})w_{1}+\frac{1}{2} ( h_{x1}+h_{x2}+\lambda)w_1^2]+\sum_{j=2}^{T=2}({\sum_{i \in I_{j}}}g_{i}w_{j}+\frac{1}{2} (\sum_{i\in I_{j}} h_{i}+lam)w_j^2]+ \gamma T Obtt=[(gx1+gx2)w1+21(hx1+hx2+λ)w12]+j=2T=2(iIjgiwj+21(iIjhi+lam)wj2]+γT

∴ \therefore O b t t = [ ( g x 1 + g x 2 ) w 1 + 1 2 ( h x 1 + h x 2 + λ ) w 1 2 ] + ( g x 3 w 2 + 1 2 ( h x 3 + λ ) w 2 2 ] + γ T Obt^{t}=[(g_{x1}+g_{x2})w_{1}+\frac{1}{2} ( h_{x1}+h_{x2}+\lambda)w_1^2]+(g_{x3}w_{2}+\frac{1}{2} ( h_{x3}+\lambda)w_2^2]+ \gamma T Obtt=[(gx1+gx2)w1+21(hx1+hx2+λ)w12]+(gx3w2+21(hx3+λ)w22]+γT

式子中的第一项计算数据就是计算第一个t时刻构建的树的第一个节点的分数。所有树节点计算的总和构成t时刻的最终 o b j ( j ) obj^{(j)} obj(j)

g i g_i gi表示第 x i x_i xi这个样本的梯度,其中 i ∈ I 1 i \in I_1 iI1表示所有在树的第一个节点上的标签**

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
为什么导数为0的点就是最优解呀?应该开口向上的二次函数有最小值,但是如何确定1/2(Hj+ λ \lambda λ的值大于0呢?

打分函数计算

Obj代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少。我们可以把它叫做结构分数(structure score)

在这里插入图片描述
上图中的g,h表示不同样本中的梯度分数。 I 1 I_{1} I1表示样本所有样本编号(样本编号就代表某一样本数据)的集合。

很有意思的一个事是,我们从头到尾了解了xgboost如何优化、如何计算,但树到底长啥样,我们却一直没看到。很显然,一棵树的生成是由一个节点一分为二,然后不断分裂最终形成为整棵树。那么树怎么分裂的就成为了接下来我们要探讨的关键。

分裂节点

现在的情况是只要知道树的结构,就能得到一个该结构下的最好分数,那如何确定树的结构呢?

方法(1)

一个想当然的方法[2]是:不断地枚举不同树的结构,然后利用打分函数来寻找出一个最优结构的树,接着加入到模型中,不断重复这样的操作。当然,简单的方法一般都不会使用。

LightBGM

我们继续学习一下GBDT模型的另一个进化版本:LightGBM。LigthGBM是boosting集合模型中的新进成员,由微软提供,它和XGBoost一样是对GBDT的高效实现,原理上它和GBDT及XGBoost类似,都采用损失函数的负梯度作为当前决策树的残差近似值,去拟合新的决策树。

LightGBM在很多方面会比XGBoost表现的更为优秀。它有以下优势:

更快的训练效率
低内存使用
更高的准确率
支持并行化学习
可处理大规模数据
支持直接使用category特征
从下图实验数据可以看出, LightGBM比XGBoost快将近10倍,内存占用率大约为XGBoost的1/6,并且准确率也有提升。

常用的机器学习算法,例如神经网络等算法,都可以以mini-batch的方式训练,训练数据的大小不会受到内存限制。而GBDT在每一次迭代的时候,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。尤其面对工业级海量的数据,普通的GBDT算法是不能满足其需求的。

LightGBM提出的主要原因就是为了解决GBDT在海量数据遇到的问题,让GBDT可以更好更快地用于工业实践。

XGBoost的优缺点
精确贪心算法

每轮迭代时,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。

优点:

可以找到精确的划分条件
缺点:

计算量巨大
内存占用巨大
易产生过拟合
Level-wise迭代方式

预排序方法(pre-sorted):首先,空间消耗大。这样的算法需要保存数据的特征值,还保存了特征排序的结果(例如排序后的索引,为了后续快速的计算分割点),这里需要消耗训练数据两倍的内存。其次时间上也有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。

优点:

可以使用多线程
可以加速精确贪心算法
缺点:

效率低下,可能产生不必要的叶结点
对cache优化不友好

在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。

[1]https://blog.csdn.net/akirameiao/article/details/80009155
[2]https://blog.csdn.net/v_JULY_v/article/details/81410574

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值