LightGBM原理详解

GBDT (Gradient Boosting Decision Tree)是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。GBDT在工业界应用广泛,通常被用于点击率预测,搜索排序等任务。GBDT也是各种数据挖掘竞赛的致命武器,据统计Kaggle上的比赛有一半以上的冠军方案都是基于GBDT。
LightGBM (Light Gradient Boosting Machine)是一个实现GBDT算法的框架,支持高效率的并行训练,并且具有以下优点:

  • 更快的训练速度
  • 更低的内存消耗
  • 更好的准确率
  • 分布式支持,可快速处理海量数据

实验数据结果表明,Higgs数据集上LightGBM比XGBoost快将近10倍,内存占用率大约为XGBoost的1/6,并且准确率有所提升。在其他数据集上也可以观察相似的结论。
准确率:

内存消耗:

训练速度:

XGBoost的工作原理
目前已有的GBDT工具基本都是基于预排序的方法(Pre-sorted)的决策树算法(如xgboost)。这种构建决策树的算法基本思想是:

  1. 对所有特征都按照特征的数值进行排序
  2. 再遍历分割点的时候用 O ( d a t a ) O(data) O(data)的代价找到一个特征上的最优分割点
  3. 找到一个特征的分割点后,将数据分裂成左右子节点

预排序算法的优点是能精确地找到分割点
缺点:

  • 空间消耗大,算法需要保存数据的特征值,还保存了特征排序后的结果(例如排序后的索引,为了后续快速的计算分割点),这里需要消耗训练数据两倍的内存。
  • 时间上有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价很大。
  • cache优化不友好。预排序后,特征对梯度的访问时一种随机访问,并且不同的特征访问顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个row_index到叶子索引的数组,并且不同特征访问顺序也不一样,也会造成较大的cache miss。

LightGBM

1. 基于Histogram的决策树算法

直方图算法的基本思想是先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了数据的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。

Histogram算法的具体实现细节

优点:

  • 降低内存消耗:直方图算法不仅不需要额外存储预排序的结果,而且可以只保存特征离散化后的bin值,而这个值一般用8位整型存储就足够了,内存消耗可以降低为原来的1/8。
  • 降低计算代价:Pre-sorted算法每遍历一个特征值就需要计算一次分裂的增益,而直方图只需要计算k次,时间复杂度从 O ( d a t a × f e a t u r e s ) O(data\times features) O(data×features)优化到 O ( k × f e a t u r e s ) O(k\times features) O(k×features)。构建直方图时仍然需要 O ( d a t a × f e a t u r e s ) O(data\times features) O(data×features)

Histogram算法并不是完美的。由于特征被离散化后,找到的分割点并不是很精确的分割点,所以会对结果产生影响。但在不同的数据集上的结果表明,离散化的分割点对最终的精度影响并不是很大,甚至有时候会更好一点。原因是决策树本来就是弱模型,分割点是不是精确并不是很重要;较粗的分割点也有正则化的效果,可以有效地防止过拟合;即使单颗树的训练误差比精确分割算法稍大,但在梯度提升(Gradient Boosting)的框架下没有太大的影响。

2. LightGBM的核心

Alg.1算法中,Histogram算法基于feature直方图寻找最优分割点。构建Histogram的复杂度为 O ( d a t a × f e a t u r e ) O(data\times feature) O(data×feature),而寻找最优分割点的复杂度为 O ( b i n × f e a t u r e ) O(bin\times feature) O(bin×feature)。因为bin远远小于data,所以整个过程的复杂度为 O ( d a t a × f e a t u r e ) O(data\times feature) O(data×feature)。通过减少data或者feature可以加速GBDT的训练过程。

2.1 Gradient-based One-Side Sampling(GOSS)

在AdaBoost算法中,样本权重作为数据实例重要性的指标。然而,GBDT没有这样的样本权重从而导致不能采用AdaBoost的取样方法。幸运的是,我们注意到GBDT中每个数据实例的梯度信息能为数据采样提供有价值的信息。换句话说,如果一个实例的梯度很小,说明这个实例得到很好的训练,其训练误差很小。一个简单的想法是丢弃这些小梯度的数据实例。然而,这样会改变数据分布从而降低训练模型的精确度。为了解决这个问题,我们提出了一个Gradient-based One-Side Sampling的新方法。以下Alg.2是GOSS算法的实现细节。

GOSS算法的描述

输入:训练数据,迭代次数d,大梯度数据的采样率a,小梯度数据的采样率b,损失函数和若干个弱学习器
输出:训练的强学习器

  1. 根据样本的绝对值梯度对其进行降序排序
  2. 对排序后的序列选取前 a × 100 % a\times100\% a×100%的样本作为大梯度样本的子集
  3. 对剩下的样本集合 ( 1 − a ) × 100 % (1-a)\times100\% (1a)×100%的样本,随机选取 b × ( 1 − a ) × 100 % b\times (1-a)\times100\% b×(1a)×100%个样本,作为小梯度样本的子集
  4. 合并大梯度样本和采样的小梯度样本
  5. 将小梯度样本乘以一个权重系数 1 − a b \frac{1-a}{b} b1a
  6. 利用上述的采样样本,学习一个新的弱学习器
  7. 不断重复(1)~(6)步骤,直到达到规定的迭代次数或者收敛为止

通过上述算法,在不过多的改变原数据分布的前提下,提高了模型的准确度和训练速度。

2.2 Exclusive Feature Bundling(EFB)

高维数据通常是非常稀疏的。由于特征空间的稀疏性,我们可以设计一个无损的方法减少特征的数量。在一个稀疏特征空间里,一些特征是相互独立的,我们可以把这些独立互斥特征组合成一个单一特征(exclusive feature bundle)。通过一个特征扫描算法,我们可以根据独立特征构建组合特征的特征直方图。构建的组合特征histogram复杂度从 O ( d a t a × f e a t u r e ) O(data\times feature) O(data×feature)降至 O ( d a t a × b u n d l e ) O(data\times bundle) O(data×bundle),而且bundle远远小于feature。这种方法在保证精确度的前提下加快了GBDT模型的训练速度。

首先,有两个需要解决的问题:

  • 确定哪一些特征可以被组合在一起;
  • 如何构建组合特征;
Greedy Bundling算法

将高维特征划分为更小的独立特征组合是一个NP难问题。为了寻找一个近似算法,首先把特征优化组合问题看作是图着色问题,将特征看做顶点,如果两个特征不相互排斥,在图中为两个特征增加一条边,最后,通过贪婪算法来生成组合特征。允许特征之间存在少量的样本点不是互斥的(存在某些对应的样本点不同时为非零),如果算法允许存在一小部分冲突那么可以得到更小的特征组合,而且进一步地提高了计算效率。由于算法中存在一小部分的冲突特征,导致影响了训练模型的精确度,最多可达 O ( [ ( 1 − γ ) n ] − 2 / 3 O([(1-\gamma)n]^{-2/3} O([(1γ)n]2/3,这里 γ \gamma γ是每个组合的最大冲突率。因此,如果我们选择一个相当小的 γ \gamma γ
我们能够在精确度和效率之间达到一个很好的平衡。Greedy Bundling算法如下:

Greedy Bundling算法描述
  1. 构建一个带有权重边的图,每一个权重对应着两个特征的总冲突数;
  2. 根据每个特征的度对特征进行降序排序
  3. 按照排序后的序列检查每一个特征,将其分配到一个小冲突的组合(由 γ \gamma γ控制)或者创建新的组合。

上述算法的时间复杂度是 O ( f e a t u r e 2 ) O(feature^2) O(feature2),在训练之间仅仅被处理一次。当特征数量不是很大的时候,这个复杂度是可接受的,但是如果有百万个特征,这个复杂度太大。为了进一步的提高效率,我们提出了一个没有构建图的更有效的排序方法:根据特征的非零值数量排序,这和按照度来进行排序十分相似,因为特征的非零值越多,越有可能产生冲突。
对于第二个问题,为了减少相应的训练复杂度,需要一个将特征组合在一起的好方法。问题关键是通过特征组合能够识别出原始特征的值。因为Histogram算法存储了离散的bins,而不是连续特征值,我们可以通过将互斥特征驻留在不同的bins中构建一个特征组合,这可以通过增加特征原始值的偏移量来实现。例如,假如特征组合有两个特征,特征A的取值范围为[0,10),特征B的取值范围为[0,20),特征B的值增加偏移量10,使得特征B的取值范围为[10,30)。最后合并A和B,生成一个取值范围为[0,30]的特征组合来代替原始特征A和B,以下是算法4的实现细节:

3. 带深度限制的Leaf-wise的叶子生长策略

在Histogram算法之上,LightGBM进行进一步的优化。首先它抛弃了大多数GBDT工具使用的按层生长(level-wise)的决策树生长策略,而使用带深度限制的按叶子生长(leaf-wise)算法

Level-wise遍历一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际中Level-wise是一种低效的算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。

Leaf-wise是一种更为高效的策略,每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后进行分裂,如此循环。同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出较深的决策树,产生过拟合。因此,LightGBM在Leaf-wise之上增加了max_depth的限制,在保证高效率的同时防止过拟合。

4. 直方图做差加速

LightGBM另一个优化是Histogram做差加速。一个叶子的直方图可以由父亲节点的直方图与它兄弟的直方图做差得到。通常构造直方图,需要遍历该叶子上的所有数据,但直方图做差仅需遍历直方图的k个桶。利用这个方法,LightGBM考虑先构造 O ( d a t a ) O(data) O(data)较小的叶节点的直方图后,再用非常微小的代价O(#bin)做差法计算兄弟叶子的直方图,在速度上可以提升一倍。

5. 支持类别特征

实际上大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,转化到多维的0/1特征,降低了空间和时间的效率。而类别特征的使用是在实践中很常用的。基于这个考虑,LightGBM优化了对类别特征的支持,可以直接输入类别特征,不需要额外的0/1展开。并在决策树算法上增加了类别特征的决策规则。在Expo数据集上的实验,相比0/1展开的方法,训练速度可以加速8倍,并且精度一致。据我们所知,LightGBM是第一个直接支持类别特征的GBDT工具。

6. 支持类别特征(Categorical Feature)的最优分割

通常将类别特征转化为one-hot编码。然而,对于学习树来说这不是一个很好的解决方案。原因是对于一个基数较大的类别特征,学习树会生长的非常不平衡,并且需要非常深的深度才能达到较好的准确率。
相比较one-hot编码,最好的解决方案是将类别特征划分为两个子集。如果某特征有k个离散值,一共有 2 ( k − 1 ) − 1 2^{(k-1)}-1 2(k1)1个可能的划分。但是对于回归树有一个有效的解决方案。为了寻找最优的划分需要大约 k ∗ l o g ( k ) k*log(k) klog(k)。其基本思想是根据训练目标的相关性对类别进行重排序。具体地说是根据累加值(sum_gradient/sum_hessian)重新对(类别特征)直方图进行排序,然后在排好序的直方图中寻找最好的分割点。

7. Cache命中率优化

Pre-sorted算法导致cache miss的因素有以下两种:

  • 随机访问梯度 O ( d a t a × f e a t u r e ) O(data\times feature) O(data×feature):当计算增益 G a i n Gain Gain时,需要用到数据的梯度信息来进行计算。但同一特征对梯度访问的顺序是随机的,而且,不同的特征访问梯度的顺序也是不同的,并且是随机的。
  • 随机访问row_idx_to_node_idx索引表 O ( d a t a × f e a t u r e ) O(data\times feature) O(data×feature):Pre-sorted算法需要访问row_idx_to_node_idx索引表,防止数据切分时对所有的特征进行切分。和访问梯度方式相同,所有的特征都需要访问这个索引表来进行索引。

这两个操作都是随机访问,会大幅度降低系统性能。当数据量很大时,顺序访问的速度比随机访问快4倍以上。

Histogram算法提高了cache命中率

  • 连续访问梯度:对梯度的访问,由于没有对特征进行排序,因此,所有的特征都用相同的方式来访问。而且,只需要对梯度访问的顺序进行一次重新排序,所有的特征都能连续的访问梯度。
  • 直方图算法不需要把数据id到叶子节点的索引表(不需要索引表,不存在缓存消失问题)
8. 并行学习的优化
8.1 特征并行

传统算法

传统的特征并行算法旨在于并行哈决策树的"Find Best Split",主要流程如下:

  1. 垂直划分数据(不同的worker有不同的特征集)

  2. 在本地特征集寻找最佳划分点{特征,阈值}

  3. 将各个本地的最佳划分点通信整合,得到最终的最优划分

  4. 以最佳划分方法对数据进行划分,并将数据划分结果广播传递给其他worker

  5. 其他worker对接收到的数据做进一步的划分

传统的特征并行方法主要不足:

  • 存在计算上的局限,传统特征并行无法加速"split"(时间复杂度为 O ( d a t a ) O(data) O(data))。因此,当数据量很大的时候,难以加速。
  • 需要对划分的结果进行通信整合,其额外的时间复杂度为 O ( d a t a / 8 ) O(data/8) O(data/8)(一个数据一个字节)

LightGBM的特征并行

当数据量很大时,传统数据并行方法无法有效的加速,LightGBM做一些改变:不再垂直划分数据,即每个worker都持有全部数据。因此,LightGBM没有数据划分结果之间的通信开销,各个worker都知道如何划分数据。而且,data不会变的更大,所以,使每个worker都持有全部数据是合理的。

LightGBM中特征并行的流程如下:

  1. 每个worker都在本地数据集上寻找最佳划分点{特征,阈值}
  2. 将各个本地的划分结果通信整合得到最优划分
  3. 在每个worker上执行最优划分

然而,该特征并行算法在数据量很大时仍然存在计算上的局限。因此,建议数据量很大时使用数据并行。

8.2 数据并行

传统算法

数据并行旨在并行化整个决策学习过程。数据并行的主要流程如下:

  1. 水平划分数据
  2. worker以本地数据构建本地直方图
  3. 将本地直方图通信整合成全局直方图
  4. 在全局直方图中寻找最优划分,然后执行此划分

传统数据划分的不足:

高通讯开销。如果使用点对点的通讯算法,一个机器的通讯开销大约为 O ( m a c h i n e × f e a t u r e × b i n ) O(machine\times feature\times bin) O(machine×feature×bin);如果使用集成通讯算法(All Reduce),通讯开销大约为 O ( 2 × f e a t u r e × b i n ) O(2\times feature\times bin) O(2×feature×bin)

LightGBM中的数据并行

LightGBM中采用以下方法减少数据并行的通讯开销:

  1. 不同于“整合所有的本地直方图以形成全局直方图”的方式,LightGBM使用分散规约(Reduce scatter)的方式对不同worker的不同特征(不重叠)进行整合,把直方图合并的任务分摊到不同的worker,降低了通讯和计算,然后worker从本地整合的直方图中寻找最佳划分并同步到全局的最佳划分。
  2. LightGBM通过直方图做差法加速训练,基于此,我们可以通讯其中一个叶节点的直方图,通过做差法得到它相邻的直方图。
  3. 基于以上方法,LightGBM将数据并行中的通讯开销减少到 O ( 0.5 × f e a t u r e × b i n ) O(0.5\times feature\times bin) O(0.5×feature×bin)
8.3 投票并行

基于投票机制的并行算法,在每个worker中投票选出top k个分裂特征,然后将每个worker选出的k个局部特征进行汇总再进行全局投票,选出全局的分裂特征,最终对全局投票的直方图进行整合并找到最优划分点。投票并行将数据并行的通讯开销减少至常数级别。在数据量很大的时候,使用投票并行可以得到非常好的加速效果。

9. LightGBM的优点
9.1 提升学习速度
9.2 提高模型的精确度
10. LightGBM的参数及其调优
lightgbm参数
Core Parameters含义用法
objective目标函数回归:regression_l1,regression_l2,huber;二分类:binary;多分类:multiclass;排序:lambdarank
boosting提升器的类型gbdt:梯度提升决策树;rf:Random Forest;dart:Dropouts meet Multiple Additive Regression Trees;goss: Gradient-based One-Side Sampling
data训练数据LightGBM使用这个数据进行训练模型
valid验证/测试数据LightGBM将输出这些数据的度量
num_boost_roundboosting的迭代次数默认100
learning_rate学习率默认0.1
num_leaves树的叶子树默认31
tree_learner树学习器的学习类型serial:单台机器的tree_learner;feature:特征并行的tree_learn;data:数据并行的tree_learner;voting:投票并行的tree_learner
num_threadsLightGBM的线程数为了更快的速度, 将此设置为真正的 CPU 内核数, 而不是线程的数量;对于并行学习, 不应该使用全部的 CPU 内核, 因为这会导致网络性能不佳
device树学习设备类型默认cpu,可选cpu/gpu;使用 GPU 来获得更快的学习速度
Model Parameters含义用途
max_depth树模型的最大深度默认为-1,限制max_depth,可以在#data 小的情况下防止过拟合. 树仍然可以通过 leaf-wise 生长.
min_data_in_leaf一个叶子上数据的最小数量默认为21,用来处理过拟合
min_sum_hessian_in_leaf一个叶子上的最小hessian和默认为1e-3,用于处理过拟合
feature_fraction特征采样比例默认为1.0,如果 feature_fraction 小于 1.0, LightGBM 将会在每次迭代中随机选择部分特征. 例如, 如果设置为 0.8, 将会在每棵树训练之前选择 80% 的特征,可以用来加速训练和处理过拟合
bagging_fraction数据采样比例默认为1.0,类似于 feature_fraction, 但是它将在不进行重采样的情况下随机选择部分数据,可以用来加速训练和处理过拟合;Note: 为了启用 bagging, bagging_freq 应该设置为非零值
bagging_freqbagging的频率默认为0,0 意味着禁用 bagging. k 意味着每 k 次迭代执行bagging
early_stopping_round迭代提前终止如果一个验证集的度量在 early_stopping_round 循环中没有提升, 将停止训练
lambda_l1L1正则系数用于处理过拟合
lambda_l2L2正则系数用于处理过拟合
min_split_gain执行切分的最小增益默认为0
IO Parameters含义用途
max_bin特征值 bins的最大数量默认255,LightGBM 将根据 max_bin 自动压缩内存。 例如, 如果 maxbin=255, 那么 LightGBM 将使用 uint8t 的特性值
categorical_feature指定类别特征用数字做索引, e.g. categorical_feature=0,1,2 意味着 column_0, column_1 和 column_2 是分类特征
Objective Parameters含义用途
sigmoidsigmoid函数的参数默认为1.0,用于binary分类和lambdarank
alphaHuber loss 和 Quantile regression 的参数默认0.9,用于regression任务
scale_pos_weight正值的权重默认1.0,用于binary分类任务
num_class类别数量默认为1,只用于multiclass分类
参数优化

leaf_wise树的参数优化

  • num_leaves:GBDT和XGBoost等的叶节点生长策略都是level-wise,一般控制tree的复杂度采用max_depth;而LightGBM是leaf-wise策略生长,通常用num_leaves参数控制模型的复杂度。下图是树的深度和叶节点的对应关系表,设置num_leaves参数时可作为参考。
  • min_data_in_leaf:这是处理Leaf-wise树过拟合的一个非常重要的参数,它的值取决于训练数据的样本个数和num_leaves,较大的min_data_in_leaf值可避免生成一个很深的树,但有可能导致欠拟合。在实际应用中,对于大数据集,设置几百或者几千足够。
  • max_depth:利用max_depth显式地限制树的深度

提升训练速度

  • 设置bagging_fraction和bagging_freq参数来使用数据采样
  • 设置feature_fraction参数达到特征采样
  • 设置较小的max_bin
  • 设置save_binary来加速模型数据的加载
  • 使用并行学习

提升准确率

  • 设置较大的max_bin(学习速度可能变慢)
  • 设置较小的learning_rate和较大的num_iterations
  • 设置较大的num_leaves(可能导致过拟合)
  • 使用更大的数据集
  • 使用dart

处理过拟合

lightgbm实战
# -*- coding="utf-8" -*-

import lightgbm as lgb
import pandas as pd
from sklearn.metrics import mean_squared_error

print("Loading data...")
# load data
df_train = pd.read_csv('../regression/regression.train', sep='\t', header=None)
df_test = pd.read_csv('../regression/regression.test', sep='\t', header=None)

y_train = df_train[0]
X_train = df_train.drop(0, axis=1)
y_test = df_test[0]
X_test = df_test.drop(0, axis=1)

# convert dataset for lightgbm
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

# set the configurations for lightgbm
params = {'boosting_type': 'gbdt',
          'objective': 'regression',
          'metric': {'l1', 'l2'},
          'learning_rate': 0.05,
          'num_leaves': 31,
          'feature_fraction': 0.9,
          'bagging_fraction': 0.8,
          'bagging_freq': 5,
          'verbose': 0}

print('Starting train...')
# train
gbt = lgb.train(params,
                lgb_train,
                num_boost_round=20,
                valid_sets=lgb_eval,
                early_stopping_rounds=5)

print('Saving model...')
# save model to file
gbt.save_model('model.txt', num_iteration=gbt.best_iteration)

print('Starting predict... ')
# predict
y_pred = gbt.predict(X_test, num_iteration=gbt.best_iteration)

print('Starting evaluation...')
# eval
print('The rmse of prediction is:', mean_squared_error(y_test, y_pred) ** 0.5)

实验结果

Loading data...
Starting train...
[1]	valid_0's l1: 0.492841	valid_0's l2: 0.243898
Training until validation scores don't improve for 5 rounds.
[2]	valid_0's l1: 0.489327	valid_0's l2: 0.240605
[3]	valid_0's l1: 0.484931	valid_0's l2: 0.236472
[4]	valid_0's l1: 0.480567	valid_0's l2: 0.232586
[5]	valid_0's l1: 0.475965	valid_0's l2: 0.22865
[6]	valid_0's l1: 0.472861	valid_0's l2: 0.226187
[7]	valid_0's l1: 0.469847	valid_0's l2: 0.223738
[8]	valid_0's l1: 0.466258	valid_0's l2: 0.221012
[9]	valid_0's l1: 0.462751	valid_0's l2: 0.218429
[10]	valid_0's l1: 0.458755	valid_0's l2: 0.215505
[11]	valid_0's l1: 0.455252	valid_0's l2: 0.213027
[12]	valid_0's l1: 0.452051	valid_0's l2: 0.210809
[13]	valid_0's l1: 0.448764	valid_0's l2: 0.208612
[14]	valid_0's l1: 0.446667	valid_0's l2: 0.207468
[15]	valid_0's l1: 0.444211	valid_0's l2: 0.206009
[16]	valid_0's l1: 0.44186	valid_0's l2: 0.20465
[17]	valid_0's l1: 0.438508	valid_0's l2: 0.202489
[18]	valid_0's l1: 0.435919	valid_0's l2: 0.200668
[19]	valid_0's l1: 0.433348	valid_0's l2: 0.19925
[20]	valid_0's l1: 0.431211	valid_0's l2: 0.198136
Did not meet early stopping. Best iteration is:
[20]	valid_0's l1: 0.431211	valid_0's l2: 0.198136
Saving model...
Starting predict... 
Starting evaluation...
The rmse of prediction is: 0.445124349108
参考文献
  • 15
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值