DL总结(三)---Optimization Algorithms优化算法

分割train/dev/test

对很多深度学习的应用来说,想猜准超参数是几乎不可能的,即使是经验非常丰富的深度学习从业者,所以目前,机器学习的应用是相当反复的 迭代的过程,只需要将这个循环进行许多次 就有希望能为你的应用中的网络找出好的参数,所以进行迭代过程时的效率决定了多快能获得好的参数 ,而恰当地将你的数据集分为训练集(train)开发集(dev)和测试集(test) ,就能让你的迭代效率更高, 假设这是你的训练数据,我们把它画成一个大矩形 那么传统的做法是你可能会从所有数据中,取出一部分用作训练集 然后再留出一部分作为交叉验证集
在这里插入图片描述
通常的分割法是,训练集和测试集分别占整体数据70%和30%,也就是人们说的,70/30训练测试分割,如果明确地设定了开发集(dev set) 那比例可能是60/20/20% 也就是测试集占60% 开发集20% 测试集20%。
但是在大数据的时代,对于有100万个样本的数据集,可能开发集只要1万个样本就足够了,足够用来评估两种算法中哪一种更好,与开发集相似 测试集的主要功能是,对训练好的分类器的性能,给出可信度较高的评估 同样如果你可能有100万个样本,但是只要1万个 就足够评估单个分类器的性能,能够对其性能给出比较准确的估计了,以此为例,如果有100万个样本 而只需要1万个用作开发集,1万个用作测试集 那么1万个只是100万个的百分之一,所以你的比例就是98/1/1%。
同时,还必须保证测试集与训练集的数据分布一致
建立好训练集、测试集、开发集可以迭代的更快,更高效的测量算法存在的方差和偏差。

bias/variance(方差与偏差)

在这里插入图片描述
如图所示,1图为欠拟合而导致高方差,3图为过拟合而导致高偏差,在这样的两个特征值的2D样例上可以通过绘制数据,把方差和偏差可视化,而对于高维的问题,则无法将数据绘制在图上并可视化决策边界,因此可通过训练集误差和开发集误差对方差与偏差进行判断。
在这里插入图片描述
如图,对于一个猫分类器:

训练集误差1%15%15%0.5%
开发集误差11%16%30%1%
性能高方差高偏差高方差、高偏差低方差、低偏差

由此,机器学习的基本原则为:

Created with Raphaël 2.2.0 Start 高偏差? (训练集集上 是否良好) 更大的网络 或训练更久 或改变NN结构 高方差? (一般化到开发集上 是否良好) 更多的数据 或正则化 或改变NN结构 好的模型 yes no yes no

initialization(参数初始化对模型的影响)

对于DL总结(一)中搭建的逻辑回归的神经网络,将所有的参数w和b初始化为0数组可以得到较好得结果,然而对于深层神经网络,所有得参数均初始化为0会导致神经网络得所有单元都做相同得运算,最后就相当于做了一次逻辑递归,因此在深层神经网络中不能对w参数初始化为0,可有以下初始化方案:

  • random初始化
np.random.seed(3)  
parameters = {}  #存储初始化的参数
L = len(layers_dims)       #NN网络的层数 
for l in range(1, L):
    parameters['W' + str(l)] = np.random.randn(layers_dims[l],layers_dims[l-1])*10
    parameters['b' + str(l)] = np.zeros((layers_dims[l],1))
  • He初始化

这种初始化也是防止梯度爆炸或消失的方法

在random初始化中,初始权重等于正态随机分布后乘以10 ,而He将对NN层数做一定的补偿将参数值改为 2 上 一 层 的 单 元 数 \sqrt{\frac{2}{上一层的单元数}} 2

#He initialization
def initialize_parameters_he(layers_dims):
    """
    Arguments:
    layer_dims -- NN包含的层数
    
    Returns:
    parameters -- 字典类型,包含 "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    
    np.random.seed(3)
    parameters = {}
    L = len(layers_dims) - 1 # integer representing the number of layers
     
    for l in range(1, L + 1):
        
        parameters['W' + str(l)] = np.random.randn(layers_dims[l],layers_dims[l-1])*np.sqrt(2./layers_dims[l-1])
        parameters['b' + str(l)] = np.zeros((layers_dims[l],1))
                
    return parameters

Cousera DL 作业中可以看到参数三种不同初始化所得到的结果:

ModelTrain accuracyProblem/Comment
3-layer NN with zeros initialization50%fails to break symmetry
3-layer NN with large random initialization83%too large weights
3-layer NN with He initialization99%recommended method

regularization(正则化解决模型过拟合问题)

如果你怀疑你的神经网络在数据上发生了过拟合,也就是存在高方差问题 你也许需要首先尝试使用正则化,获取更多数据也是解决高方差问题的 一个很可靠的方法,但你并不是总能获取到更多的训练数据,或者获取更多数据的代价太大,但使用正则化通常有助于防止过拟合并降低网络的误差。
以逻辑回归为例进行阐述 在逻辑回归中 你会尝试最小化代价函数J 该代价函数定义为 :每一个训练样本的预测的损失之和 ,其中w和b是逻辑回归的参数,因此w是一个x维的参数向量,b是一个实数,要为逻辑回归正则化 你需要 加上这个 λ \lambda λ 它称为正则化参数 ;如图:
在这里插入图片描述
L2正则化:
λ 2 m ∑ j = 1 n x w j 2 ( 欧 几 里 得 范 数 ) \frac{\lambda}{2m}\sum_{j=1}^{n_x}w_j^2 (欧几里得范数) 2mλj=1nxwj2()
L1正则化:
λ m ∑ j = 1 n x w j 2 \frac{\lambda}{m}\sum_{j=1}^{n_x}w_j^2 mλj=1nxwj2

一般情况下都会使用L2正则化,无论你在分母中使用m还是2m 它只是一个缩放常量,如果你使用L1正则化,w最后会变得稀疏,这意味着w矢量中有很多0,有些人认为这有助于压缩模型,因为有一部分参数是0 只需较少的内存来存储模型 ,然而在实践中,通过L1正则化让模型变得稀疏 带来的收效甚微 ,所以至少在压缩模型的目标上,它的作用不大 在训练网络时,L2正则化使用得频繁得多,那么 λ \lambda λ则为需要调节的另一个超参数。

正则化

那么在神经网络中又如何进行正则化呢???
如图:
在这里插入图片描述

pass
pass
pass
pass
pass
随机失活(drop out)

由于过拟合一般是由过多的特征所造成的,而一个复杂的神经网络在金国若干层隐藏层后又会产生许多新的特征,因而可以通过随机失活算法(灭霸算法)让一些单位失去作用:
在这里插入图片描述
如左图的复杂神经网络,通过在每一隐藏层设置一个概率值,如右图每层保留单元的概率均为0.5,则最后将会得到右图所示简化版的神经网络。
在这里插入图片描述
如图在l=3的层上演示这个技术,设置一个矢量d, d3表示层3的失活向量 ,d3将获得和a3一样的维度, 当d3中的某个元素小于某个值, 这个值命名为keep.prob ,即keep.prob是一个概率值, 之前我将它赋值为0.5 在这个样例中它赋值为0.8, 这是给定隐藏单元将被保留的概率,keep.prob=0.8 意味着这个隐藏单元有0.2的几率被丢弃, 因此它将生成一个随机矩阵。
这个方法也适用与矢量化运算 这种情况下d3将是一个矩阵 因此任意一个训练样例及隐藏单元的组合 其对应的d3中的元素都有0.8的几率取值为1 0.2的几率取值为0 这个表达式表示这个随机数有0.8的几率取值为1 或为真(True) 20%或0.2的几率取值为非(False) 或0 ,然后取层3的激活矩阵 用a3来表示 a3为刚才计算的激活矩阵 它是用原来的a3与d3相乘得到的矩阵 这里的相乘是逐元素相乘 ,也可以写成a3*=d3 这样做的作用是 对于d3中值为0的元素, 每个元素有20%的几率取值为0, 通过点乘将a3中0值对应位置的元素 一一清零, 如果用python实现,d3是一个值为True或False的布尔值数组 ,而不是1或0 ,但是用1和0表示True和False 做点乘运算也能达到效果
最后我们要放大a3 将a3除以0.8 ,实际上是除以keep.prob参数
解释:
假设层3有50个单元 或者说50个神经元 所以a3的维数是50x1, 如果做矢量化的运算,它的维数是50xm ,所以每个神经元有80%的几率被 20%的几率被丢弃 这意味着平均起来 将有10个单元失活或者被清零 现在再看看z4的值 z 4 = w 4 ∗ a 3 + b 4 z_4=w_4*a_3+b_4 z4=w4a3+b4,它的期望值将减少20%, 也就是说a3中20%的元素都被清零了 为了不减少z4的期望值 我们需要除以0.8 因为 它能提供你所需要的大约20%的校正值 这样a3的期望值就不会被改变 这就是所谓的反向随机失活技术(inverted dropout technique)

pass
pass
pass
pass
pass
pass
pass

除了L2正则化和 随机失活(dropout)正则化之外,还有一些其他方法 可以减少神经网络的过拟合,例如我们正在拟合猫的图片分类器,如果你过拟合了,可以增加训练数据 ,但扩大训练集代价很高, 而且有时候就是无法得到更多数据,此时可以通过对原图片水平翻转,放大剪裁等操作使得数据集扩大
在这里插入图片描述
这种技术即数据集扩增(data augmentation) 正则化技术
还可以采用早停止法(stop early)

normalizing input(归一化输入)

在神经网络中特征的尺度不一样,例如x1的范围是0到1000.而x2得范围为0~1,那就会导致, 神经网络参数w1和w2的比率,或说w1和w2取值的范围会有很大的不同 ,则所得到得代价函数图像会像下图一样是一个扁平的碗状,如果你把这个函数的等值线画出来 你就会有一个像这样的扁长的函数 ,而如果你将特征进行归一化后,代价函数通常就会看起来更对称,如果你对下图的那种代价函数使用梯度下降法,那可能必须使用非常小的学习率,因为假如从这里开始,梯度下降法需要经历许多步反复辗转,才能好不容易终于挪到这个最小值,而如果等值线更趋近于圆形,那无论从何开始,梯度下降法几乎都能直接朝向最小值而去,我们可以在梯度下降中采用更长的步长,而无需像左图那样来回摇摆缓慢挪动,当然在实践中,w是一个高维向量,把它画在二维空间中可能无法正确传递出高维中的感觉,但大体上的感觉是你的代价函数会更圆,优化过程更容易进行,因为各种特征的尺度会比较接近,不是有的从1到1000 有的却从0到1,而是大多数都处于-1和1之间,大家的方差范围都接近,这些会使得代价函数J优化起来更容易也更快。一般情况下可以采用
x : = x − μ x:=x-\mu x:=xμ( μ 为 输 入 层 的 均 值 \mu为输入层的均值 μ)
x : = x σ 2 x:=\frac{x}{\sigma^2} x:=σ2x( σ 2 \sigma^2 σ2为输入特征的方差)
来实现输入的归一化。
在这里插入图片描述
在这里插入图片描述

mini-batch(小批量梯度下降算法)

机器学习的应用是一个高度依赖经验的,不断重复的过程,需要训练很多模型才能找到一个确实好用的, 所以能够快速的训练模型的确是个优势,然而艰难的是,在大数据领域中深度学习表现得并不算完美,我们能够训练基于大量数据的神经网络,而用大量数据训练就会很慢,所以需要快速的优化算法、 好的优化算法效率, 那么让我们从小批量梯度下降算法(mini-batch gradient descent)开始,之前提过矢量化(vectorization)有效地计算所有m个样例,而不需要一个具体的for循环就能处理整个训练集,例如巨型的输入矩阵X[ x 1 , x 2 , x 3 . . . . . . . x m x_1, x_2 ,x_3 .......x_m x1,x2,x3.......xm],就是x1 x2直到xm的m个训练样例,Y也做类似处理 y1 y2 y3直到ym,所以X为nxm维矩阵,Y为1m维矩阵,矢量化运算能够相对快地处理m个样例, 但是如果m非常大,速度依然会慢,例如,如果m是5百万或者5千万或者更大,整个训练集运用梯度下降法必须先处理整个训练集, 才能在梯度下降中往前一小步,然后再处理一次,整个5百万的训练集 ,才能再往前一小步,为加快算法,可以这样做

首先将你的训练集拆分成更小的,微小的训练集,即小批量训练集(mini-batch) 比如说每一个微型训练集只有1000个训练样例,也就是说 ,x1至x1000作为第一个微训练集,也叫做小批量训练集 然后取接下来的1000个样例 ,x1001至x2000这1000个样例,依次继续,把这些样例的第一组表示为X{1} ,第二组表示为 x{2} ,现在,如果你总共有5百万个训练样例,每个小批量样例有1000个样例,则有5000个这样的小批量样例,即总共有5000个小批量样例,所以最后一个为X{5000} ,Y也作类似处理,做相应的拆分处理

在这里插入图片描述
我们来看看小批量梯度下降是怎么做的,在训练集上运行小批量梯度下降法的时候 t=1到5000都要运行一遍,因为我们有5000个子集,每个子集1000个样例,for循环里要做的基本上就是,用(X{t},Y{t})做一次梯度下降,就好像有一个规模为1000的训练集,而我们只是要实现你已熟知的算法,只不过现在m为1000的子训练集上,而不是为全部1000个样例写一个for循环,也就是说用矢量化的方法同时处理1000个样例。让我们先写出来 首先对输入值运用前向传播(Forward Prop)

for  t =1...5000 //t为拆分的训练集组
{
	Forward prop on x{t}:
		Z[1] = W[1]*x{t}+b[1]  //神经网络第一层
		A[1] = g(Z[1])
		.
		.
		.
		A[L] = g(Z[L])
	Comput cost J = 1/1000*sum(error function) + L2(正则项)
	Backporp //反向传播计算梯度
	//更新参数
	W[l] := W[l] - alpha*dW[l]
	b[l] := b[l] - alpha*db[l]
}

以上的伪代码也叫做训练集的一次遍历(epoch) 遍历,是指过一遍训练集 ,只不过在批量梯度下降法中对训练集的一轮处理只能得到一步梯度逼近 ,而小批量梯度下降法中对训练集的一轮处理,也就是一次遍历,可以得到5000步梯度逼近,当然对训练集进行多轮遍历可以用另一个for循环或者while循环实现,不断地训练训练集, 并希望它收敛在某个近似收敛值 ,当有一个大型训练集时 小批量梯度下降法比梯度下降法要快得多。
在这里插入图片描述

exponetially weighted average(指数加权平均)

指数加权平均,在统计学上也被称为指数加权滑动平均,,首先来看一下这个概念
在这里插入图片描述
使用伦敦每天的温度作为例子,比如在1月1日,温度是40华氏度,大约在5月末,温度大概是60华氏度,也就是15摄氏度,等等,得到这样一张图,这些数据看起来好像有些噪声,如果想要计算数据的趋势,即温度的局部平均或滑动平均,可以这样做,首先令V0=0 之后每一天,我们先用0.9乘以之前的值,再加上0.1乘以当天的温度,这里的 θ 1 \theta_1 θ1就是第一天的温度,对于第二天,我们仍然使用加权平均 ,即0.9乘以之前的值,再加上0.1乘以今天的温度,即 0.9* V 2 V_2 V2加上0.1* θ 3 \theta_3 θ3 以此类推

V t = β ∗ V t − 1 + ( 1 − β ) ∗ θ t V_t=\beta*V_{t-1} + (1-\beta)*\theta_t Vt=βVt1+(1β)θt

如果你这样计算,并把结果用红色画出来,会得到 一个滑动平均的图,称为每日温度的指数加权平均,如上公式,之前的权重是0.9,我们现在把它换成参数 β \beta β,计算这个公式的时候 你可以认为 V t V_t Vt近似于 1 ( 1 − β ) \frac{1}{(1-\beta)} (1β)1天温度的平均,举例来说,当 β \beta β=0.9的时候,可以认为它是前10天的气温平均值,这就是这条红线,如果 β \beta β设置为非常接近1的值,比如0.98,这时计算1/(1-0.98),就得到了50,这相当于粗略计算了,前50天气温的平均值,就会得到这条绿色的线, β \beta β的值很大的时候 ,得到的曲线会更平滑,因为对更多天数的温度做了平均处理,因此曲线就波动更小,更加平滑,但另一方面 这个曲线会右移,因为这个指数加权平均的公式,在温度变化时,适应地更加缓慢,这就造成了一些延迟,原因是 当 β \beta β=0.98的时候,之前的值具有更大的权重,而当前温度的权重就非常小,只有0.02,所以当温度变化的时候温度上升或者下降,在 β \beta β较大时,就会适应得更慢。

β \beta β的值取0.5,那么由右边的公式,这就变成了只对两天进行平均,如果画出来,就会得到黄色的线,由于仅仅平均两天的气温,即只在很小的样本内计算平均,得到结果中会有更多的噪声,更容易受到异常值的影响,但它可以更快地适应温度变化
在这里插入图片描述

使用上述公式就可以实现指数加权平均,通过调整这个参数,也就是学习算法中的一个超参数,就可以得到不同的收敛结果,通常取中间的某个值效果最好 也就是上图的红色的曲线。

bias correction(偏差修正)

上章对指数加权平均做了一定的介绍,还有一个技术细节,称为偏差修正,它能让我们更精确地计算平均值。
在这里插入图片描述

这张图表示β=0.9, 这张图表示β=0.98,可是 当你使用上章的公式代入β=0.98却无法得到图上的绿色曲线,实际上得到的是这条紫色曲线,可以发现 紫色曲线的起点非常低
当执行移动均值时 初始设定为 V 0 = 0 , V 1 = 0.98 ∗ V 0 + 0.02 ∗ θ 1 V_0=0, V_1=0.98*V_0+0.02*θ_1 V0=0,V1=0.98V0+0.02θ1 V 0 = 0 V_0=0 V0=0,所以这一项可移除,所以 V 1 = 0.02 ∗ θ 1 V_1=0.02*θ_1 V1=0.02θ1,这就是为什么,如果第一天的气温为华氏40° , V 1 = 0.02 ∗ 40 = 8 V_1=0.02*40=8 V1=0.0240=8,在这里将得到一个非常低的值,这并没有很好地预估第一天的气温 。

V 2 = 0.98 ∗ V 1 + 0.02 ∗ θ 2 V_2=0.98*V_1+0.02*θ_2 V2=0.98V1+0.02θ2,如果你代入V1,其实 V 2 = 0.98 ∗ 0.02 ∗ θ 1 + 0.02 ∗ θ 2 = 0.0196 ∗ θ 1 + 0.02 ∗ θ 2 V_2=0.98*0.02*θ_1+0.02*θ_2 =0.0196*θ1+0.02*θ2 V2=0.980.02θ1+0.02θ2=0.0196θ1+0.02θ2,假设θ1和θ2是正数,计算时V2将远小于θ1或θ2,因此V2并没对头两天的气温做出很好地估计,
有一种方式能更好地进行估计,使估计更精确,尤其在估计运算初期,即,用 V t 1 − β t \frac{V_t}{1-β^t} 1βtVt代替Vt,看一个具体的例子 当t=2,1-βt 等于1-0.98的平方,结果为0.0396 因此,第二天的气温估计值为 V 2 0.0396 = 0.0196 ∗ θ 1 + 0.02 ∗ θ 2 \frac{V_2}{0.0396} = 0.0196*θ1+0.02*θ2 0.0396V2=0.0196θ1+0.02θ2,这两个数值加上分母0.0396 ,就成为θ1和θ2的加权平均值, 消除了偏差。

而当t值变大 βt值将趋向于0,也就是当t值足够大时,偏差修正值对运算将基本没有影响,这也是为什么当t值增大 紫线和绿线基本重合。

momentum(动量梯度下降算法)

动量梯度下降算法的主要思想是:计算梯度的指数加权平均,然后使用这个梯度来更新权重。
在这里插入图片描述
假设现在要优化一个代价函数 如等高线图所示,红色的点表示最小值的位置 假设从如图位置开始执行梯度下降,可以发现梯度下降算法会计算很多步,向着最小值缓慢地振荡前进,如蓝线所示, 这种上下的振荡会减慢梯度下降的速度,同时也无法使用较大的学习率,如果你使用的学习率很大, 可能会超调 ,如紫线所示发散出去,为了避免振荡过大 你只能使用比较小的学习率,也就是说在纵轴上,学习慢一点 因为希望避免振荡 但是在横轴上,希望加快学习速度
实现动量梯度下降算法的步骤如下:
在这里插入图片描述
一般将 β \beta β设置为0.9,采用这样的算法可以减少迭代过程中的振荡。

RMSprop(均方根传递算法)

On itertation t:
	Compute dW,db on current mini_batch:
	S_dW = beta_2 * S_dW + (1 - beta_2)*dW^2
	S_db = beta_2 * S_db + (1 - beta_2)*db^2
	W := W - α * dW/(sqrt(dW)+e) //e为一个极小值,防止除0
	b := b - α * db/(sqrt(db)+e)

RMSprop算法既可以如上所章图所示减小垂直方向的振荡,还方便通过增大 α \alpha α的值以提高训练速度。 β \beta β

Adam算法

Adam算法融合了动量梯度下降算法与RMSprop算法,其实现如下:

V_dw = 0, S_dw = 0, V_db = 0, S_db = 0
On itertation t:
	Compute dW,db on current mini_batch:
	V_dw = β_1*V_dw + (1-β_1)*dW , V_db = β_1*V_db + (1-β_1)*db //Momentum
	S_dw = β_2 * S_dw + (1 - β_2)*dW^2,S_db = β_2 * S_db + (1 - β_2)*db^2 //RMSprop
	//偏差修正
	V_dw_corrected = V_dw/(1-β1^2), V_db_corrected = V_db/(1-β1^2)
	S_dw_corrected = S_dw/(1-β2^2), S_db_corrected = S_db/(1-β2^2)
	//更新权重
	W := W - α * V_dw_corrected/(sqrt(S_dw_corrected)+e) //e为一个极小值,防止除0
	b := b - α * V_db_corrected/(sqrt(S_db_corrected)+e)

以上有一些超参数,
对于学习率 α \alpha α,通常需要不断的调整
β 1 \beta_1 β1通常取0.9;
β 2 \beta_2 β2通常取0.999;
e的值通常取 1 0 − 8 10^{-8} 108

learning rate decay&hypeparameterstuning(学习率衰减与超参数调节)

逐渐地减小学习率,能学习算法运行更快,我们称之为学习率衰减
在这里插入图片描述
当使用适量的小样本进行小批次梯度下降法(mini-batch gradient descent)时,也许一个批次只有64个或128个样本,当迭代时,步长(steps)会有些浮动,它会逐步向最小值靠近 但不会完全收敛到这点(蓝线), 所以,算法会在最小值周围浮动,但是却永远不会真正收敛,因为学习率α取了固定值,且不同的批次(mini-batches)也可能产生些噪声,但是如果慢慢地降低你的学习率α,那么在初始阶段,因为学习率α取值还比较大,学习速度仍然可以比较快,但随着学习率降低α变小,步长也会渐渐变小,所以最终将围绕着离极小值点更近的区域摆动,即使继续训练下去也不会漂游远离(绿线)。

实现学习率衰减可以在每一次迭代训练集后采用如下公式:
α : = 1 1 + d e c a y _ r a t e ∗ e p o c h _ n u m α 0 \alpha:=\frac{1}{1+decay\_rate*epoch\_num}\alpha_0 α:=1+decay_rateepoch_num1α0
α : = 0.9 5 e p o c h _ n u m α 0 \alpha:=0.95^{epoch\_num}\alpha_0 α:=0.95epoch_numα0
α : = k e p o c h _ n u m α 0 \alpha:=\frac{k}{epoch\_num}\alpha_0 α:=epoch_numkα0
α : = k t α 0 \alpha:=\frac{k}{\sqrt{t}}\alpha_0 α:=t kα0

batch normalization(批量归一化)

softmax regression


============================ 框架 ==================================


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值