深度学习基础模型算法原理及编程实现--04.改进神经网络的方法

文章列表
1.深度学习基础模型算法原理及编程实现–01.感知机.
2.深度学习基础模型算法原理及编程实现–02.线性单元 .
3.深度学习基础模型算法原理及编程实现–03.全链接 .
4.深度学习基础模型算法原理及编程实现–04.改进神经网络的方法 .
5.深度学习基础模型算法原理及编程实现–05.卷积神经网络.
6.深度学习基础模型算法原理及编程实现–06.循环神经网络.
9.深度学习基础模型算法原理及编程实现–09.自编码网络.
9.深度学习基础模型算法原理及编程实现–10.优化方法:从梯度下降到NAdam.

深度学习基础模型算法原理及编程实现–04.改进神经网络的方法

4.1 基本激活函数认知

神经网络对非线性问题优异的解决能力来自于非线性激活函数,可以想象,如果激活函数全是线性的,那么层数再深的数神经网络也仅仅等价于单层网络。常见的激活函数有:tf.nn.relu()、tf.nn.relu()、tf.nn.relu()、tf.nn.relu()、tf.nn.relu()、tf.nn.relu()、tf.nn.relu()、tf.nn.relu()。

4.1.1 sigmoid

sigmoid函数可表示为sigmoid(x) = 1/(1+exp(-x)),由于sigmoid函数输出在0-1之间,符合概率输出的定义,且其自身的非线性特性使其性能比线性激活函数y=x以及阶梯激活函数的功能要强很多。且求导也较为方便,但其缺点是在饱和区内梯度较小,使得网络层数较深时存在梯度消失问题。此外,由于sigmoid输出总是正数,对下图中的节点1和2求梯度,有:


这里写图片描述

式中x1和x2分别为节点1和2的输出,由于激活函数为sigmoid,所以x1和x2均大于0,结合上式知w1和w2的梯度同向,所以w1和w2的梯度向量只能在第1及第3象限,那么这时的优化路径就很容易出现zigzag现象【详参斯坦福2017季CS231n深度视觉识别课程视频】。

这里写图片描述

5.1.2 tf.tanh

tf.tanh的使用方法同上,只要将其中的sigmoid改成tanh即可。tanh的形状与sigmoid类似,两者之间可通过平移及缩放而相互转换,例如另sigmoid(x) = 1/(1+exp(-x)),则tanh(x)= 2/(1+exp(-x*2))-1,tanh(x)输出区间在-1到1之间,所以可以避免zigzag现象,且其收敛速度比sigmoid快,但在饱和区内同样存在梯度较小的问题。

5.1.3 ReLU

tf.relu的出现解决了梯度消失问题,其定义为f(x)=max(x,0),tf.relu的使用方法同上,只要将其中的sigmoid改成relu即可。Relu在输入小于0时硬饱和,在输入大于0时梯度为1,从而缓解了梯度消失问题。但其在输入小于0时的硬饱和可能导致对应的权重无法更新,从而进入“死忙区”。ReLU相对于sigmoid的好处主要有以下4点:
1.单侧抑制,ReLU有一半的可能性取值为0;2相对宽阔的兴奋边界(在输入大于0区间梯度都为1),减缓梯度消失现象;3梯度计算更容易。4稀疏激活性(神经科学家分析大脑发现神经元编码的工作方式是稀疏的,推测大脑同时被激活的神经元只有1-4%)。

5.1.4 Leaky ReLU

Leaky ReLU是为解决“ReLU死亡”问题的尝试。ReLU中当x<0时,函数值为0。而Leaky ReLU则是给出一个很小的负数梯度值,比如0.01。所以其函数公式为 有些研究者的论文指出这个激活函数表现很不错,但是其效果并不是很稳定。Kaiming He等人在2015年发布的论文Delving Deep into Rectifiers中介绍了一种新方法PReLU,把负区间上的斜率当做每个神经元中的一个参数。然而该激活函数在在不同任务中均有益处的一致性并没有特别清晰。

5.1.5 Softplus

Softplus是ReLU的近似,其函数表达式为y=log(1+exp(x)),虽然softplus有单侧抑制,但是却没有稀疏的激活性。

5.2 增加隐藏层数

对于非线性单隐层神经网络,只要隐藏层节点数足够多,该神经网络可以拟合任意函数。同时隐含层数越多,越容易拟合复杂函数。拟合同一复杂函数所需的隐含节点数随着隐藏层数的增多呈指数下降趋势。层数越多提取的特征就越抽象,浅层网络学习简单的微观特征,随着网络层级的加深,深层会不断提取组合浅层特征,相当于观察视野的放大,从而逐渐提取出复杂宏观特征

5.3 提升学习速率

5.3.1 梯度下降改为随机梯度下降

常用的梯度下降方法有以下3种:
(1).批量梯度下降法(Batch Gradient Descent)
批量梯度下降法,是梯度下降法最常用的形式,具体做法也就是在更新参数时使用所有的样本来进行更新,假设一共有N组样本,即每个迭代周期中进行N次前向预测、梯度计算及参数更新的迭代,也是上一小节中用到的方法。在训练数据比较多时,这个过程比较慢。
(2).随机梯度下降法(Stochastic Gradient Descent)
  随机梯度下降法与批量梯度下降法类似,只是在一个迭代周期中没有遍历所有训练数据,而是随机选取一个样本来计算,即每个迭代周期只进行一次前向预测、梯度计算及参数更新的迭代。
  通常而言,批量梯度下降法训练速度慢,但收敛速度快;而随机梯度下降法训练速度快,但收敛速度慢,且很不能不是最优解。而小批量梯度下降法是对这两个方法的折中。
(3).小批量梯度下降法(Mini-batch Gradient Descent)
小批量梯度下降法中,假设有N组样本,将其分为m个小批量样本,对每组小批量样本,有多少样本就进行多少次前向预测及梯度计算,但参数更新只进行一次。将上一下节中的批量梯度下降改为小批量梯度下降,小批量数据大小设为30,改进后的计算结果如下。
02/23/18 09:35:52 after epoch 1, precision ratio is 89.810000
02/23/18 09:36:32 after epoch 2, precision ratio is 91.190000
……
02/23/18 09:53:13 after epoch 26, precision ratio is 95.480000
02/23/18 09:53:53 after epoch 27, precision ratio is 95.450000
编程实现
python版本:https://pan.baidu.com/s/1qZLJ7Gg, 04ModifyFullConnectNeuralNetwork\Python gai02

5.3.2 输出层激活函数与目标函数的选择

考虑第2小节中的线性单元,对其进一步简化,设输入参数只有1,目标函数为二次代价函数,通过梯度下降训练激活函数为sigmoid函数的模型,使期望输出参数为0。设置学习速率为0.15,分别设置初始权重和初始偏置为0.6、0.9和2.0、2.0,下面分别考虑激活函数为线性函数及sigmoid函数的情况。

5.3.2.1 激活函数为sigmoid函数、损失函数为交叉项

统计其损失函数随迭代次数的变化绘于下图中,可以看出第1组参数下损失函数快速的下降,而第2组参数下损失函数变化较慢。通常而言,人在犯比较明显的错误时学习改正的速度最快,第1组例子学习过程与人的学习方式较为吻合,第2组例子则不吻合,说明学习速率与初始参数有关,

这里写图片描述

继续分析一下出现这种情况的原因,用链式法则求出损失函数对权重及偏置项的偏导数如下:


这里写图片描述

式中sigmoid为激活函数,为了理解这些表达式的行为,结合sigmoid函数的图像,发现该函数的输出值在[0,1]之间,而0对应了生物神经元的“抑制状态”,1对应了生物神经元的“兴奋状态”,当输入值绝对值大于5时输出值在0或1处,且变化趋势平缓,相应的梯度接近0,即出现梯度的“饱和效应”,这会导致当前的误差很难传递至前层,从而导致学习缓慢甚至网络无法训练。

这里写图片描述

那么如何才能避免这种现象,使得模型可以在输出误差较大时具有较大的学习速率,输出误差较小时,学习速率可以适当减小呢。不难知道,参数的学习速率与相应的偏导数相关,对于激活函数为sigmoid的函数,偏导数可表示为:


这里写图片描述

而我们期望对偏执项的偏导数与输出误差线性相关(损失函数对权重的偏导数与损失函数对偏置项的偏导数线性相关,所以只要考虑对偏置项的偏导数即可),这样的话在输出误差较大时有较大的梯度,输出误差较小时有较小的梯度,所以令:


这里写图片描述

式中为C1常数。将以上两式联立,有:

这里写图片描述

式中为C2常数。不失一般性,令C1=1,C2=0,就可得到常见的交叉熵代价函数:

这里写图片描述

将上一小节中的代价函数改为交叉熵代价函数,有

这里写图片描述

其对输出层节点的偏导数为:

这里写图片描述

其它公式都无需改变,改进后的程序在中可以看到,运行结果如下:
编程实现
python版本:https://pan.baidu.com/s/1qZLJ7Gg, 04ModifyFullConnectNeuralNetwork\Python gai03

5.3.2.2 激活函数为线性函数且损失函数为平方损失函数

对于激活函数为线性函数,偏导数可表示为:

这里写图片描述

结合式(4.4),联立可求得:

这里写图片描述

综上,如果输出层使用的是Sigmoid神经元时,损失函数选用交叉熵最好;如果输出层使用线性神经元时,损失函数使用二次代价函数最好

5.3.2.3 损失函数为交叉项的好处

如果将误差项采用交叉项:

这里写图片描述

如果将激活函数改为softmax且误差项不变,这样的组合称为softmax-loss,那么输出层的误差项为:

这里写图片描述

可见对分类问题,对任意激活函数,采用交叉熵损失函数只要算一个神经元节点的偏导数即可,计算量减小。
此外,由于激活函数为softmax,需对每层输出结果进行“归一化处理”,所以输出层初始权重及初始偏差对最终的结果影响不大。如下MLP所示【将输出层的权重系数及偏置项扩大100倍】:

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets("./../MNISTDat", one_hot=True)
sess = tf.InteractiveSession()
# Create the model
in_units = 784
h1_units = 300
o_units = 10
batch_size = 100
x = tf.placeholder(tf.float32, [None, in_units])
y_ = tf.placeholder(tf.float32, [None, o_units])
keep_prob = tf.placeholder(tf.float32)
W1 = tf.Variable(tf.truncated_normal([in_units, h1_units], stddev=0.1))
b1 = tf.Variable(tf.zeros([h1_units]))
hidden1 = tf.nn.relu(tf.matmul(x, W1) + b1)
hidden1_drop = tf.nn.dropout(hidden1, keep_prob)
#W2 = tf.Variable(tf.zeros([h1_units, o_units]))
W2 = tf.Variable(tf.ones([h1_units, o_units])*100)
#b2 = tf.Variable(tf.zeros([o_units]))
b2 = tf.Variable(tf.ones([o_units])*200)
y = tf.nn.softmax(tf.matmul(hidden1_drop, W2) + b2)
# Define loss and optimizer
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
train_step = tf.train.AdagradOptimizer(0.3).minimize(cross_entropy)
# Write Graphs
train_writer = tf.summary.FileWriter('./train', sess.graph)
train_writer.close()
# Train
tf.global_variables_initializer().run()
for i in range(3000):
  batch_xs, batch_ys = mnist.train.next_batch(batch_size)
  train_step.run({x: batch_xs, y_: batch_ys, keep_prob: 0.75})
# Test trained model
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
5.3.2.4 柔性最大值(softmax函数)

Softmax函数的好处就是导数计算特别容易。
对输出层有:

这里写图片描述

由上式不难发现,Softmax函数的导数仅需相减相乘运算即可,没有用到太复杂的计算。

5.3.2.5 Softmax-loss

由上面可以看到单纯地用softmax函数做激活函数好像没有太大的优势。对于损失函数为交叉项的情况,如果将激活函数改为softmax,这样的组合称为softmax-loss,那么输出层的误差项为:

这里写图片描述

可见对分类问题,如果采用交叉熵损失函数,并将激活函数取为softmax函数,那么误差项只要一个相减操作,计算量更小。【问题】如果将激活函数改为sigmoid,计算量同样小,唯一区别是输出层没有像softmax那样进行归一化,那么差别有哪些呢,难道仅仅是softmax层输出的结果满足概率分布,其和为1。??

5.3.3 中间层激活函数及其算法提升技巧
5.3.3.1 标准化处理Batch Normalization(激活函数为sigmoid及tanh时)

当输入落入sigmoid函数或tanh函数的饱和区时会发生梯度消失现象,通过对加权输入进行标准化处理可以避免这一现象。标准化处理通过规范化使得加权输入的均值为0、方差为1,从而使得加权输入落入sigmoid/tanh函数的线性区间,避免了梯度消失,并可获得较大的梯度,从而加速收敛;梯度变大也使得模型更容易跳出局部最小值;此外,标准化处理还破坏了原来的数据分布,一定程度上缓解过拟合,不过现在许多激活函数为relu的模型中也有用到。下面给出了利用tensorflow实现对加权输入Wx_plus_b的标准化。【注意】正常的normalization是在数据进入模型前的预处理,而batch normalization是对层与层之间的数据进行标准化,然后再输入激活函数。

#利用tf.nn.moments(Wx_plus_b, axes=[0])计算Wx_plus_b的均值和方差,其中axes=[0]表示想要标准化的维度。
f_mean,f_var = tf.nn.moments(Wx_plus_b, axes=[0])
scale = tf.Variable(tf.ones([out_size]))#缩放比例
shift = tf.Variable(tf.zeros([out_size]))#平移项
eps = 0.001
Wx_plus_b = tf.nn.batch_normalization(Wx_plus_b, f_mean, f_var, shift, scale, eps)

等价于:

这里写图片描述

5.3.3.2 LRN(Local Response Normalization)

VGGNet中已经发现LRN层作用不大,不过还是简单描述一下。LRN最早见于Alex那篇用CNN参加ImageNet比赛的论文,Alex在论文中解释了LRN层模仿了生物神经系统的“侧抑制”机制,对局部神经元的活动创建竞争环境,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。Alex在ImageNet数据集上的实验表明,使用LRN后CNN在Top1的错误率可以降低1.4%,因此在其经典的AlexNet中使用了LRN层,LRN对ReLU这种没有上限边界的激活函数会比较有用,因为它会从附近的多个卷积核的响应中挑选比较大的反馈,但不适合Sigmoid这种有固定边界并能抑制过大值得激活函数。不过LRN层的作用不大,在其他模型中几乎没有用到。 LRN层是按下述公式计算的:
这里写图片描述

5.4 过度拟合和正则化

5.4.1 过度拟合

有时神经网络对训练数据的准确率很高,可能几乎是100%,但对测试数据准确率很低。即泛化能力较差。可以理解成神经网络在单纯记忆训练集合,而没有对数字本质进行理解,从而使其很难较好的泛化到测试数据集上。
检测过度拟合的一个方法是:跟踪测试数据集合上的准确率随训练的变化情况。如果我们看到测试数据上的准确率不再提升,那么我们就停止训练。严格地说,这其实并非是过度拟合的必要现象,因为测试集和训练集上的准确率可能会同时停止提升。当然,采用这样的策略是可以阻止过度拟合的。过度拟合是神经网络的主要问题,避免过度拟合的方法如下:
1. 提前停止。可避免过度拟合现象,但对于泛化能力没有提升。
2. 大量训练数据。数据较难获得。
3. 降低网络规模。可行但模型性能可能降低。
4. 正则化。比较可行
5. 交叉验证。比较可行

5.4.1 正则化

没有添加正则项之前,损失函数都是经验风险函数式,经验风险最小化策略认为经验风险最小的模型就是最优模型。但是当样本容量较小时(相比较于参数规模而言),经验风险最小化学习的效果就不一定很好,会产生过拟合现象。通过增加一个表示模型复杂度的项到代价函数上来防止过拟合,这个项称为正则化项,一般正则化项都是模型复杂度的递增函数,添加正则项的策略也称为结构风险最小化,并用正则项系数权衡经验风险和模型复杂度。贝叶斯最大后验概率估计就是结构风险最小化的一个例子,而极大似然估计是经验风险最小化的一个例子,当模型是条件概率分布,损失函数式对数损失函数时,经验风险最小化就等价于极大似然函数。从贝叶斯估计的角度来看,正则化项可以理解为模型的先验概率,模型复杂度小的有较大的先验概率,模型复杂度大的有较小的先验概率。

4.2.2.1 L2正则化

L2正则化又称为权重衰减,L2正则化是在未正则化的损失函数上添加权重的二范数, 下面给出了正则化后的损失函数:

这里写图片描述

式中 称为正则化系数。由于节点加权不会影响到L2正则项,误差项不变,其对权重及偏置项的偏导数可表示为:


这里写图片描述

所以偏置项的梯度下降学习规则不会改变,权重的梯度下降规则变为:

这里写图片描述

L2正则化后的权重系数变小了,那为什么L2正则化可以避免过拟合现象呢。过度拟合的一个重要原因就是因为噪声的影响太大。而更大的权重意味着网络可能会因为输入的微小改变而产生比较大的输出改变。更小的权重意味着网络的输出不会因为输入的随便改变而变化太大,这会减小噪声对网络学习的影响,从而能够根据已经学到的知识更好地进⾏泛化。
此外,拥有100个隐藏元的网络会有接近80000个参数,而训练集仅仅有60000幅图像,这好像是用一个80000阶的多项式来拟合50000个数据点,网络肯定会过度拟合,但是网络实际上却泛化的很好,目前对这一点并没有很好的解释,但有一个猜想:梯度下降自身就带有正则化效应
输入的噪声只会通过权重影响输出,与偏置项几乎无关,同时,大的偏置项能让神经元更加灵活,所以通常不会对偏置项进行规范化。
编程实现
python版本:https://pan.baidu.com/s/1qZLJ7Gg, 04ModifyFullConnectNeuralNetwork\Python gai04

4.2.2.2 L1正则化

L1正则化是在未正则化的损失函数上添加权重的绝对值和和, 下面给出了正则化后的损失函数:


这里写图片描述

其对权重及偏置项的偏导数可表示为:

这里写图片描述

所以偏置的梯度下降学习规则不会改变,权重的梯度下降规则变为:


这里写图片描述

L1正则化后的权重系数变小了,与上面的式(4.12)对比发现,L1及L2正则化后权重系数的绝对值都变小了,只是L1正则化是成比例的减小,而L2正则化是直接减去一个值。所以,当一个特定的权重绝对值很小时,L1正则化的权重缩小得远比L2正则化要小得多;相反,当某个特定的权重绝对值很大时,L1正则化的权重缩小得要比L2正则化大得多。此外对于权重W1=[1,0,0,0]以及权重W2=[1/4, 1/4, 1/4, 1/4], 如果输入x=[1,1,1,1],那么W1Tx=W2Tx=1,但是W1T的L2惩罚是1、W2T的L2惩罚是0.25,因此从L2惩罚的角度来看,W2更好,因此可以从经验上发现,L2惩罚更倾向于使得权重值小且分散,使得所有的特征维度都被用到;而L1惩罚更倾向于使得权重小且集中,即依赖于少数几个维度不应该因为害怕出现过拟合而使用小网络。相反,应该进尽可能使用大网络,然后使用正则化技巧来控制过拟合。

5.4.2.3 更深的网络和更小的卷积核

更深的网络和更小的卷积核使得模型具有隐式的正则化效果。网络层数的增加以及小卷积核的结合使用,可以使得更少的参数可以学习得到同样的视野,以下图为例,2个3*3的卷积核连续堆叠在一起,其视野为5*5,与单独用一个5*5的卷积核观察到的视野一样,但参数却少了(1-3*3*2/5*5)=28%,当然左边的模型得对特征的学习能力更强,左边可做两次非线性变换,而右边只能做一次。
这里写图片描述

5.4.3 交叉验证

交叉验证通过将给定的数据切分为不同的部分,再对其进行不同的组合得到训练数据和测试数据,在此基础上反复用不同的模型进行训练测试,从而得到泛化能力较好的模型。常用的交叉验证方法可分为3种:
(1)简单交叉验证
将数据分为训练集和测试集这两部分,针对不同的模型在训练集及测试集上进行训练和测试,然后再选择测试误差最小的模型。简单交叉验证很简单,但其有2大缺点:
1.模型的选择依赖于数据的划分。不同的划分方式可能会导致最后选择出的最优模型不同。
2.无论针对训练及测试,其并没有遍历所有数据。
(2) S折交叉验证
将数据切分为S个大小一样但不想交的数据集,然后选择其中的S-1个数据集的数据进行训练,再用剩下的1个数据集进行测试,针对不同的模型,在S种训练及测试数据上进行计算,最后选择平均测试误差最小的模型。
(3)留1交叉验证
其实是S折交叉验证的一个特例,如果数据量为N,令S=N就是留1交叉验证。但留1交叉验证计算量很大。

5.4.4 Dropout

Dropout不依赖于对权值的改变,而是随机的使某些节点不工作

5.4.5 数据扩展

获取更多的训练数据往往比较麻烦,但由于图片的冗余信息比较大,我们可以对原有数据进行扩展训练,比方说进行旋转、扭曲、平移、扭曲、提取局部图像或添加噪声等操作,在这些变换之后我们仍能够识别出原本图像中的含义,进而获得更多的训练数据。所以数据扩展的好处主要有如下两点:1添加训练实例,防止过拟合;2扩展后的图像都存在噪声扰动,如果模型能够克服噪声并正确学习特征,那么该模型的泛化能力就大大提升了。
此外,AlexNet模型中还对图形的RGB数据进行PCA处理,并对主成分做了一个标准差为0.1的高斯扰动,增加了一些噪声,这样可以让错误率下降1%。

5.5 初始权重的选择5.5 初始权重的选择

5.5.1 均值为0、标准差为sqrt(1/n) 的高斯分布

在前面讲到的提升学习速率那一节中,问题的原因主要是由于输出神经元激活函数与损失函数不匹配,神经元在错误的值上饱和导致学习速率下降,通过选择合理的损失函数可以解决这类问题。但这种改变仅对输出神经元有效,对隐藏层神经元在错误的值上的饱和却一点作用也没有。
这里写图片描述

5.5.2 Xavier初始化

Xavier初始化常用在自编码器中,其实它的思想与上面方法的思想一致。权重初始化得太小会导致数据流在层间传递时逐渐缩小而难以产生作用,权重初始化得太大会导致数据流在层间传递时逐渐变大而导致发散和失效,而Xavier初始化的目的就是使得权重初始化值大小合适,其初始化得权重满足0均值,方差为2/(n_in + n_out)范围内的均匀分布或高斯分布

5.5.3 无监督逐层训练

对于较深的网络,可以使用无监督自编码技术的逐层训练来提取特征,从而将网络的权重初始化到一个比较合理的值,加快监督训练的收敛。【问题 但是对卷积层和池化层怎么操作??】

5.6 优化方法的选择

深度学习基础模型算法原理及编程实现–10.优化方法:从梯度下降到NAdam.

5.7 池化层处理方式的选择

平均池化可以避免噪声的扰动
最大池化可以避免平均池化的模糊化效果
令池化层中采样步长小于池化核尺寸,提升特征的丰富性

5.8超参数的选择

网络层数、节点数、激活函数、损失函数、学习速率、正则化参数、小批量数据大小等等都是需要设定的超参数,超参数是如此之多,如果训练数据也很多,本来在一组超参数下跑一个结果可能就很费时,那么不停的调整超参数将会特别费时。对不同的问题,调整超参数的方法是不同的,也没有什么公式性的东西可言,但有一些经验性的启发性的方法可供大家参考。

5.8.1 网络层数、节点数的选择

先简化问题,比方说0到9的数字分类改为0和1的分类,这样可以省去1/5的运算时间,然后可以先训练[784,10]的网络结构看看效果,然后在尝试[784,30,10]的网络结构,再尝试[784,100,10]的网络结构,……
在目标函数中,常用1/n来改变整个代价函数的大小,n为训练样本的总数,即对所有误差取平均值,这会影响到各个参数的梯度的大小,进而影响参数更新速度,等价于改变了学习速率,实际应用时需注意这一点。……【未完待续】

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值