Coursera | Andrew Ng (02-week1)—改善深层神经网络:深度学习的实用层面 正则化

在吴恩达深度学习视频以及大树先生的博客提炼笔记基础上添加个人理解,原大树先生博客可查看该链接地址大树先生的博客- ZJ

Coursera 课程 |deeplearning.ai |网易云课堂

CSDNhttp://blog.csdn.net/junjun_zhao/article/details/79229961


1.1 Train dev test sets (训练、开发、测试、数据集)

学习目标:如何有效运作神经网络,内容涉及超参数调优 如何构建数据,以及如何确保优化算法快速运行,从而使学习算法在合理时间内完成自我学习。

创建高效的神经网络,需要做出很多决策 ,如:神经网络分多少层,每层含有多少个隐藏单元,学习速率是多少,各层采用哪些激活函数?

应用型机器学习是一个高度迭代的过程。

这里写图片描述

循环该过程的效率是决定项目进展速度的一个关键因素,而创建高质量的训练数据集、验证集和测试集,也有助于提高循环效率。

1. 训练、验证、测试集

对于一个需要解决的问题的样本数据,在建立模型的过程中,我们会将问题的 data 划分为以下几个部分:

  • 训练集(train set):用训练集对算法或模型进行训练过程;

  • 验证集(development set):利用验证集或者又称为简单交叉验证集(hold-out cross validation set)进行交叉验证,选择出最好的模型

  • 测试集(test set):最后利用测试集对模型进行测试,获取模型运行的无偏估计

小数据时代

在小数据量的时代,如:100、1000、10000 的数据量大小,可以将 data 做以下划分:

  • 无验证集的情况:70% / 30%;
  • 有验证集的情况:60% / 20% / 20%;

通常在小数据量时代,以上比例的划分是非常合理的。

大数据时代

但是在如今的大数据时代,对于一个问题,我们拥有的 data 的数量可能是百万级别的,所以验证集和测试集所占的比重会趋向于变得更小。

验证集的目的是为了验证不同的算法哪种更加有效,所以验证集只要足够大能够验证大约 2-10 种算法哪种更好就足够了,不需要使用 20% 的数据作为验证集。如百万数据中抽取 1 万的数据作为验证集就可以了。

测试集的主要目的是评估模型的效果,如在单个分类器中,往往在百万级别的数据中,我们选择其中 1000 条数据足以评估单个模型的效果。

  • 100万数据量:98% / 1% / 1%;

  • 超百万数据量:99.5% / 0.25% / 0.25%(或者99.5% / 0.4% / 0.1%)

这里写图片描述

Notation

  • 建议验证集要和训练集来自于同一个分布,可以使得机器学习算法变得更快;
  • 如果不需要用无偏估计来评估模型的性能,则可以不需要测试集。

这里写图片描述


1.2 Bias_Variance (偏差_方差)

偏差、方差

对于下图中两个类别分类边界的分割:

这里写图片描述

理解偏差和方差两个关键数据是,训练集误差和验证集误差

从图中我们可以看出,在欠拟合(underfitting)的情况下,出现高偏差(high bias)的情况;在过拟合(overfitting)的情况下,出现高方差(high variance)的情况。

个人理解:

  • 高偏差:欠拟合导致,有很大一部分分类的不对
  • 高方差:过拟合导致,分类分的也太对了,直观上看是分割线不平滑。

在 bias-variance trade off (偏差-方差权衡) 的角度来讲,我们利用训练集对模型进行训练就是为了使得模型在 train(训练) 集上使 bias (偏差)最小化,避免出现 underfitting (欠拟合)的情况

个人理解:在 训练集上(几乎绝大部分数据都在这里)好不容易训练出来的模型,不能分类分错的太厉害,那就偏的太离谱了(欠拟合)。我们追求的就是错的少(偏差)最小化。

但是如果模型设置的太复杂,虽然在 train 集上 bias 的值非常小,模型甚至可以将所有的数据点正确分类,但是当将训练好的模型应用在 dev 集上的时候,却出现了较高的错误率。这是因为模型设置的太复杂则没有排除一些 train 集数据中的噪声,使得模型出现 overfitting 的情况,在 dev 集上出现高 variance 的现象。

个人理解:好嘛,你训练集(train)几乎全都对,错的很少(偏差底)本身可能数据就有些问题的(噪声)你也分对了,然后到了验证集(dev set),错了一大堆。这就是 高方差。

所以对于 bias 和 variance 的权衡问题,对于模型来说是一个十分重要的问题。

例子:

几种不同的情况:

这里写图片描述

以上为在人眼判别误差在 0% 的情况下,该最优误差通常也称为“贝叶斯误差”(bayes error),如果“贝叶斯误差”大约为 15%,那么图中第二种情况就是一种比较好的情况。

个人理解:两两对比。

-训练集误差(train set error)和人类误差(human/bayes error)比较,差的太离谱就是高偏差。
- 验证集 (dev set error) 误差和训练集(train set error)误差对比,差的太离谱就是高方差。

High bias and high variance 的情况

上图中第三种 高 bias 和 高 variance 的情况出现的可能如下:

这里写图片描述

没有找到边界线,但却在部分数据点上出现了过拟合,则会导致这种高偏差和高方差的情况。(紫色那条线,边界一开始就画错(分类不好),然后个别数据对了,之后边界又错(分类不好))

虽然在这里二维的情况下可能看起来较为奇怪,出现的可能性比较低;但是在高维的情况下,出现这种情况就成为可能

学会如何通过分析训练集训练算法产生的误差,和验证集验证算法产生的误差,来诊断算法是否存在高偏差或高方差,是否两个值都高 或者两个值都不高,根据算法偏差和方差的具体情况,决定接下来你要做的工作。


1.3 Basic “recipe” for machine learning (机器学习基础)

Recap:

如何通过训练误差和验证误差,判断算法偏差或方差是否偏高,训练神经网络时用到的基本方法,初始模型训练完成后,首先要知道算法的偏差高不高。

机器学习的基本方法

在训练机器学习模型的过程中,解决 High biasHigh variance 的过程:

这里写图片描述

1.是否存在 High bias 高偏差 ?

  • 增加网络结构,如增加隐藏层数目;
  • 训练更长时间;
  • (寻找合适的网络架构,使用更大的 NN 结构);

个人理解:高偏差是出现训练集误差高的情况,所以目标要针对于神经网络深度,和训练时间方面,找原因。

2.是否存在 High variance 高方差?

  • 获取更多的数据;
  • 正则化( regularization);
  • (寻找合适的网络结构);

个人理解:通过查看验证集性能,验证集误差来评估是否存在高方差问题,可能是神经网络太深,隐藏单元神经元太多,对特征的影响过大,所以要适当的降低规模,“缩小”网络。

偏差方差权衡 bias variane trade off 的发展:

  1. 机器学习初期,对于偏差方差权衡问题,提出的解决方法效果并不是很好。很难做到降低一个误差,而不影响另一个。
  2. 现代深度学习和大数据时代,由上述方法则可以实现,减少某一个误差而不影响另一个。

1.4 Regularization (正则化)

利用正则化来解决 High variance 高方差的问题,正则化是在 Cost function 中加入一项正则化项,调整模型的复杂度。

Logistic regression

加入正则化项的代价函数:

J(w,b)=1mi=1mL(y^(i),y(i))+λ2m||w||22 J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) + λ 2 m | | w | | 2 2

上式为逻辑回归的 L2 L 2 正则化。

  • L2 正则化: λ2m||w||22=λ2mj=1nxw2j=λ2mwTw λ 2 m | | w | | 2 2 = λ 2 m ∑ j = 1 n x w j 2 = λ 2 m w T w

  • L1正则化: λ2m||w||1=λ2mj=1nx|wj| λ 2 m | | w | | 1 = λ 2 m ∑ j = 1 n x | w j |

||w||22 | | w | | 2 2 = wTw w T w :向量参数 w 的欧几里德范数平方。

其中 λ 为正则化因子(参数)

注意:lambda 在python中属于保留字,所以在编程的时候,用“lambd”代表这里的正则化因子 λ λ

Neural network 神经网络中加入 L2 正则

加入正则化项的代价函数:

J(w[1],b[1],,w[L],b[L])=1mi=1ml(y^(i),y(i))+λ2ml=1L||w[l]||F2

其中 ||w[l]||2F=i=1n[l1]j=1n[l](w[l]ij)2 | | w [ l ] | | F 2 = ∑ i = 1 n [ l − 1 ] ∑ j = 1 n [ l ] ( w i j [ l ] ) 2 ,因为 w 的大小为 (n[l1],n[l]) ( n [ l − 1 ] , n [ l ] ) ,该矩阵范数被称为“Frobenius norm” (弗罗贝尼乌斯范数),表示一个矩阵中所有元素的平方和。

权重衰减(Weight decay)

在加入正则化项后,梯度变为:

dW[l]=(form_backprop)+λmW[l] d W [ l ] = ( f o r m _ b a c k p r o p ) + λ m W [ l ]

则梯度更新公式变为:

W[l]:=W[l]αdW[l] W [ l ] := W [ l ] − α d W [ l ]

代入可得:

W[l]:=W[l]α[(form_backprop)+λmW[l]]=W[l]αλmW[l]α(form_backprop)=(1αλm)W[l]α(form_backprop) W [ l ] := W [ l ] − α [ ( f o r m _ b a c k p r o p ) + λ m W [ l ] ] = W [ l ] − α λ m W [ l ] − α ( f o r m _ b a c k p r o p ) = ( 1 − α λ m ) W [ l ] − α ( f o r m _ b a c k p r o p )

其中, (1αλm) ( 1 − α λ m ) 为一个<1的项,( α,λ,m α , λ , m 都是正数)会给原来的 W[l] W [ l ] 一个衰减的参数,所以 L2 范数正则化也被称为“权重衰减Weight decay)”。

这里写图片描述

这里写图片描述


1.5 Why regularization reduces overfitting? 为什么正则化可以减少过拟合?

假设下图的神经网络结构属于过拟合状态:

这里写图片描述

对于神经网络的 Cost function:

J(w[1],b[1],,w[L],b[L])=1mi=1mL(y^(i),y(i))+λ2ml=1L||w[l]||2F J ( w [ 1 ] , b [ 1 ] , ⋯ , w [ L ] , b [ L ] ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) + λ 2 m ∑ l = 1 L | | w [ l ] | | F 2

加入正则化项,直观上理解,正则化因子λ设置的足够大的情况下,为了使代价函数最小化,权重矩阵 W 就会被设置为接近于 0 的值。则相当于消除了很多神经元的影响,那么图中的大的神经网络就会变成一个较小的网络。

当然上面这种解释是一种直观上的理解,但是实际上隐藏层的神经元依然存在,但是他们的影响变小了,便不会导致过拟合

数学解释:

假设神经元中使用的激活函数为 g(z)=tanh(z) g ( z ) = t a n h ( z ) ,在加入正则化项后:

这里写图片描述

λ λ 增大,导致W[l]减小, Z[l]=W[l]a[l1]+b[l] Z [ l ] = W [ l ] a [ l − 1 ] + b [ l ] 便会减小,由上图可知,在 z 较小的区域里, tanh(z) t a n h ( z ) 这个曲线函数,会相对呈线性,所以每层的函数就近似线性函数,整个网络就成为一个简单的近似线性的网络,并不是一个极复杂的高度非线性函数,从而不会发生过拟合。


1.6 Dropout regularization (Dropout 正则化)

Dropout 正则化,正则化的一种,用来解决高方差问题。

个人理解:首先,回想高偏差问题,分类错的太多,就是训练的不好,那么加深网络,加长时间,但是,训练的足够好了,验证的错误太多,那就是之前网络有一些过深了,那么再适当的简化缩小网络。

Dropout(随机失活)就是在神经网络的 Dropout 层,为每个神经元结点设置一个随机消除的概率,对于保留下来的神经元,我们得到一个节点较少,规模较小的网络进行训练。

这里写图片描述

实现 Dropout的方法:反向随机失活(Inverted dropout)

首先假设对 layer 3 进行dropout:

keep_prob = 0.8  # 设置神经元保留概率
d3 = np.random.rand(a3.shape[0], a3.shape[1]) < keep_prob
a3 = np.multiply(a3, d3)
a3 /= keep_prob
keep_prob = 0.8
d3 = np.random.rand(3, 4) < keep_prob
print(d3)
>>>array([[ True,  True,  True,  True],
       [ True,  True, False, False],
       [ True,  True,  True,  True]])

True 的留下,False 删除

# 假设初始化的 a3 
a3 = np.mat([[2,3,4],
            [3,4,5],
            [4,5,5]])
...
a3 = np.multiply(a3, d3)
matrix([[2, 3, 0],
        [0, 4, 5],
        [4, 5, 5]])

a3 /= keep_prob
matrix([[2.5 , 3.75, 0.  ],
        [0.  , 5.  , 6.25],
        [5.  , 6.25, 6.25]])

这里解释下为什么要有最后一步:a3 /= keep_prob

依照例子中的 keep_prob = 0.8,那么就有大约 20% 的神经元被删除了,也就是说 a[3] a [ 3 ] 中有 20% 的元素被归零了,在下一层的计算中有 Z[4]=W[4]a[3]+b[4] Z [ 4 ] = W [ 4 ] ⋅ a [ 3 ] + b [ 4 ] ,所以为了不影响 Z[4] Z [ 4 ] 的期望值,所以需要 W[4]a[3] W [ 4 ] ⋅ a [ 3 ] 的部分除以一个 keep_prob。也就是保留下的值,在某种程度上增大了。

Inverted dropout 是通过对“a3 /= keep_prob”,则保证无论 keep_prob 设置为多少,都不会对 Z[4] Z [ 4 ] 的期望值产生影响。

Notation:在测试阶段不要用 dropout,因为那样会使得预测结果变得随机。

这里写图片描述


1.7 Understanding dropout (理解 dropout )

这里写图片描述

另外一种对于 Dropout 的理解。

这里我们以单个神经元入手,单个神经元的工作就是接收输入,并产生一些有意义的输出,但是加入了 Dropout 以后,输入的特征都是有可能会被随机清除的,所以该神经元不会再特别依赖于任何一个输入特征,也就是说不会给任何一个输入设置太大的权重。要选择的参数是 keep-prob ,它代表每一层上保留单元的概率,每一层不同。

所以通过传播过程,dropout 将产生和 L2 范数相同的收缩权重的效果。

对于不同的层,设置的keep_prob也不同,一般来说神经元较少的层,会设 keep_prob
=1.0,神经元多的层,则会将keep_prob设置的较小。

  • 计算视觉中的输入量非常大,输入了太多像素 ,没有足够的数据,所以 dropout 在计算机视觉中应用得比较频繁,有些计算机视觉研究人员非常喜欢用它
  • dropout 是一种正则化方法,它有助于预防过拟合,除非算法过拟合,否则不要使用 dropout 。

缺点:

dropout 的一大缺点就是其使得 Cost function不能再被明确的定义,以为每次迭代都会随机消除一些神经元结点,所以我们无法绘制出每次迭代 J(W,b) J ( W , b ) 下降的图,如下:

这里写图片描述

使用 Dropout:

关闭 dropout 功能,即设置 keep_prob = 1.0
运行代码,确保 J(Wb) J ( W , b ) 函数单调递减;
再打开 dropout 函数。


1.8 Other regularization method (其他正则化方法)

其他正则化方法

  • 数据扩增(Data augmentation):通过图片的一些变换,得到更多的训练集和验证集;

这里写图片描述

  • Early stopping:在交叉验证集(dev set)的误差上升之前的点停止迭代,避免过拟合。这种方法的缺点是无法同时解决 bias 和 variance 之间的最优。
    • 运行梯度下降时,可以绘制训练误差,或只绘制代价函数 J 的优化过程,在训练集上用 0-1 记录分类误差次数,呈单调下降趋势。(train set)
    • 还可以绘制验证集误差,它可以是验证集上的分类误差,或验证集上的代价函数 逻辑损失和对数损失等.(dev set)

这里写图片描述


1.9 Normalizing inputs (归一化输入)

训练神经网络 其中一个加速训练的方法就是 归一化输入

对数据集特征 x1,x2 归一化的过程(两步):

这里写图片描述

  • 1.计算每个特征所有样本数据的均值: μ=1mi=1mx(i) μ = 1 m ∑ i = 1 m x ( i )
  • 减去均值得到对称的分布: x:=xμ x := x − μ
    • 意思是移动训练集 直到它完成零均值化 (subtract 减去 mean 均值)
  • 2.归一化方差: σ2=1mi=1mx(i)2 σ 2 = 1 m ∑ i = 1 m x ( i ) 2 , x=x/σ2 x = x / σ 2

个人理解:(目前分布都在第一象限)先零均值化,得到第二张图,使数值靠近都在 (0,0)周围分布。(现在 x 轴 跨度比 y 轴跨度大)再进行归一化方差,得到第三张图。使得数据均匀分布在(0,0)周围。

使用归一化的原因:

这里写图片描述

由图可以看出不使用归一化和使用归一化前后 Cost function 的函数形状会有很大的区别。

在不使用归一化的代价函数中,如果我们设置一个较小的学习率,那么很可能我们需要很多次迭代才能到达代价函数全局最优解;

如果使用了归一化,那么无论从哪个位置开始迭代,我们都能以相对很少的迭代次数找到全局最优解。代价函数平均起来看更对称。

  • 所以如果输入特征处于不同范围内,可能有些特征值从 0 到 1 有些从 1 到 1000,那么归一化特征值就非常重要。
  • 执行这类归一化并不会产生什么危害,Andrew Ng 经常会做归一化处理,即使不确定它能否提高训练或算法速度。

1.10 vanishing/exploding gradients (梯度消失与梯度爆炸)

Question:训练神经网络尤其是深度神经网络所面临的一个问题是,梯度消失或梯度爆炸,也就是说 当你训练深度网络时,导数或坡度有时会变得非常大,或非常小 甚至以指数方式变小, 加大了训练难度。

Solution:了解梯度消失或爆炸问题的真正含义,以及如何更明智地选择随机初始化权重,从而避免这个问题

如下图所示的神经网络结构,以两个输入为例:

这里写图片描述

这里我们首先假定 g(z)=z g ( z ) = z b[l]=0 b [ l ] = 0 ,所以对于目标输出有:

y^=W[L]W[L1]W[2]W[1]X y ^ = W [ L ] W [ L − 1 ] ⋯ W [ 2 ] W [ 1 ] X

  • W[l] W [ l ] 的值大于1的情况:

    如: W[l]=[1.5001.5] W [ l ] = [ 1.5 0 0 1.5 ] ,那么最终, y^=W[L][1.5 001.5]L1X y ^ = W [ L ] [ 1.5 0   0 1.5 ] L − 1 X ,激活函数的值将以指数级递增;

  • W[l] W [ l ] 的值小于1的情况:

    如: W[l]=[0.5000.5] W [ l ] = [ 0.5 0 0 0.5 ] ,那么最终, y^=W[L][0.5 000.5]L1X y ^ = W [ L ] [ 0.5 0   0 0.5 ] L − 1 X ,激活函数的值将以指数级递减。

上面的情况对于导数也是同样的道理,所以在计算梯度时,根据情况的不同,梯度函数会以指数级递增或者递减,导致训练导数难度上升,梯度下降算法的步长会变得非常非常小,需要训练的时间将会非常长。

在梯度函数上出现的以指数级递增或者递减的情况就分别称为梯度爆炸或者梯度消失。

那怎么解决呢?看下面。


1.11 Weight initialization for deep networks (神经网络的权重初始化)

利用初始化缓解梯度消失和爆炸问题

以一个单个神经元为例子:

这里写图片描述

由上图可知,当输入的数量 n 较大时,我们希望每个 wi w i 的值都小一些,这样它们的和得到的 z 也较小。

这里为了得到较小的 wi w i ,设置 Var(wi)=1n V a r ( w i ) = 1 n ,这里称为 Xavier initialization。 [‘zeiviə; ‘zæ-; ‘zeivjə]

对参数进行初始化:

WL = np.random.randn(WL.shape[0],WL.shape[1])* np.sqrt(1/n)

这么做是因为,如果激活函数的输入 x 近似设置成均值为 0,标准方差 1 的情况,输出 z 也会调整到相似的范围内。虽然没有解决梯度消失和爆炸的问题,但其在一定程度上确实减缓了梯度消失和爆炸的速度

不同激活函数的 Xavier initialization:

激活函数使用 Relu: Var(wi)=2n V a r ( w i ) = 2 n
激活函数使用 tanh: Var(wi)=1n V a r ( w i ) = 1 n

其中 n 是输入的神经元个数,也就是 n[l1] n [ l − 1 ] W[l] W [ l ] 的 shape (n[l1]n[l]) ( n [ l − 1 ] , n [ l ] )

有时调优该超级参数效果一般,这并不是我想调优的首要超级参数,当发现调优过程中产生的问题,虽然调优该参数能起到一定作用,但考虑到相比调优其它超级参数的重要性,我通常把它的优先级放得比较低。

设置的权重矩阵 既不要增长过快 也不要太快下降到 0,从而训练出一个,权重或梯度不会增长或消失过快的深度网络,在训练深度网络时,这也是一个加快训练速度的技巧。


1.12 Numerical approximation of gradients(梯度的数值逼近)

在实施 backprop 时,有一个测试叫作梯度检验,它的作用是确保 backprop 正确实施,为了逐渐实现梯度检验,我们首先说说如何对计算梯度做数值逼近

使用双边误差的方法去逼近导数/梯度

这里写图片描述

由图可以看出,双边误差逼近的误差是0.0001,先比单边逼近的误差0.03,其精度要高了很多。

涉及的公式:

  • 双边导数:

f(θ)=limε0=f(θ+ε)f(θε)2ε f ′ ( θ ) = lim ε → 0 = f ( θ + ε ) − f ( θ − ε ) 2 ε

误差: O(ε2) O ( ε 2 )

  • 单边导数:

f(θ)=limε0=f(θ+ε)f(θ)ε f ′ ( θ ) = lim ε → 0 = f ( θ + ε ) − f ( θ ) ε

误差: O(ε) O ( ε )

重点是要记住双边误差 (two sided difference)公式的结果更准确


1.13 Gradient Checking (梯度检验)

优点:梯度检验可以节省很多时间,发现 backprop 实施过程中的 bug。

下面用前面一节的方法来进行梯度检验。

连接参数

因为我们的神经网络中含有大量的参数: W[1],b[1],,W[L],b[L] W [ 1 ] , b [ 1 ] , ⋯ , W [ L ] , b [ L ] ,为了做梯度检验,需要将这些参数全部连接起来,reshape成一个大的向量 θ。

同时对 dW[1],db[1],,dW[L],db[L] d W [ 1 ] , d b [ 1 ] , ⋯ , d W [ L ] , d b [ L ] 执行同样的操作。

这里写图片描述

进行梯度检验

进行如下图的梯度检验:

这里写图片描述

判断 dθapproxdθ d θ a p p r o x ≈ d θ 是否接近。

判断公式:

||dθapproxdθ||2||dθapprox||2+||dθ||2 | | d θ a p p r o x − d θ | | 2 | | d θ a p p r o x | | 2 + | | d θ | | 2

其中,“ ||||2 | | ⋅ | | 2 ”表示欧几里得范数,它是误差平方之和,然后求平方根,得到的欧氏距离。

注意: dθ d θ 它与 θ θ 具有相同维度, dW[1] W1 W 1 具有相同维度, db1 d b 1 b1 b 1 具有相同维度


1.14 Gradient Checking implementation notes (关于梯度检验实现的注记)

实现梯度检验 Notes

  • 不要在训练过程中使用梯度检验,只在 debug 的时候使用,使用完毕关闭梯度检验的功能;
  • 如果算法的梯度检验出现了错误,要检查每一项,找出错误,也就是说要找出哪个 dθ[i]approx d θ a p p r o x [ i ] dθ d θ 的值相差比较大;
  • 不要忘记了正则化项;
  • 梯度检验不能与 dropout 同时使用。因为每次迭代的过程中,dropout 会随机消除隐层单元的不同神经元,这时是难以计算 dropout 在梯度下降上的代价函数 J J <script type="math/tex" id="MathJax-Element-71">J</script>;
  • 在随机初始化的时候运行梯度检验,或许在训练几次后再进行。

这里写图片描述

参考文献:

[1]. 大树先生.吴恩达Coursera深度学习课程 DeepLearning.ai 提炼笔记(1-4)– 浅层神经网络


PS: 欢迎扫码关注公众号:「SelfImprovementLab」!专注「深度学习」,「机器学习」,「人工智能」。以及 「早起」,「阅读」,「运动」,「英语 」「其他」不定期建群 打卡互助活动。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值