李宏毅《机器学习》笔记

1.机器学习基本概念

1.1 机器学习的任务

  • 回归任务(regression):输出一个数值
  • 分类任务(classification):输出一个类别
  • 结构式学习(structed learning):输出一个图片、音频等

1.2 机器学习的过程

  1. 定义模型—— y = b + w x y = b+wx y=b+wx
    • 与feature相乘的参数 w -> weight
    • 没有跟feature相乘的参数 b -> bias
    • 已知的东西 x -> feature
  2. 定义 loss —— 关于参数b和w的函数,反映 y 和 y ^ y和\hat y yy^的差距
    • Mean Absolute Error
    • Mean Square Error
    • Cross-entropy
  3. 最优化(Optimization)
    • 梯度下降(Gradient Descent)——存在只有局部最优解,找不到全局最优解问题
      1. 选择一个初始值
      2. 根据Loss函数计算梯度并更新参数
    • 参数变化速率影响因素
      • 学习率(learning rate)
        • 这种自己设置的参数就是超参数(hyperparameters)
      • 当前点梯度大小

1.3 激活函数(Activation Function)

1.3.1 sigmoid
  • 如果y跟x关系比较复杂,用线性模型(liner model)显然难以拟合,所以需要更复杂的模型

  • 可以将一个复杂的曲线,看做一个常数加上多个函数,这个函数可以是sigmoid function

请添加图片描述

在这里插入图片描述

  • 最后可以将这些参数拼成统一参数向量 θ \theta θ,梯度 g = ∇ L ( θ 0 ) g=\nabla L(\theta^0) g=L(θ0)
  • 除了增加sigmoid的数量,还可以增加sigmoid的次数,也就是增加层数(Layer),形成神经网络(Neural Network)
1.3.2 Rectified Linear Unit(ReLU)

在这里插入图片描述

1.4 过拟合(Overfitting)

  • 指在训练集上loss低,但测试集loss高

  • 比如一组满足二次函数的数据,网络层数很深(弹性大)时,只用几个点就能拟合,但这几个点之外的函数就没有限制,导致测试集上结果不好。

  • 虚线指的是数据的真实分布,实线指拟合的曲线

在这里插入图片描述

  • 解决方法

    • 增加训练集
    • 数据增强(Data Augumention)
      • 简单方法:翻转、旋转、尺度变换、随机抠取、色彩抖动、高斯噪声、随机模糊、随机擦除
      • 复杂方法:Fancy PCA、监督式抠取、GAN生成
    • 增加限制、减少参数——比如CNN
    • Regularization
    • Dropout

1.5 模型选择方法

  • 将训练集再分为训练集和验证集(Validation Set),提高模型泛化能力,避免模型过于拟合测试集
  • N-fold Cross Validation,将训练集分为N份,分别以第i份训练集为验证集,做N次训练,得到最低Loss

2. 优化

2.1 Optimization为什么会失败?

随着gradient更新次数的增加,gradient渐渐趋于0,training loss虽然在减小但不够小(下图蓝线);或者一开始gradient就在0附近震荡(下图黄线)。

在这里插入图片描述

gradient趋近于0说明在critical point(驻点)附近,此时包括2种情况:局部最小值(local minima)鞍点(saddle point),另一种local maxima的情况不影响gradient的更新。

2.2 批次(batch)和动量(momentum)

2.2.1 Batch

batch就是训练的最小单位,将一个batch中的所有样本的loss计算出来后,更新一次模型参数θ。把所有的batchs过一遍叫做一个epoch,每个epoch之后会将训练集全部打乱。

small batch训练出来的模型的泛化能力更好,可能的解释是:小的batch每次update的方向都不太一样,更有可能跳出sharp minima,被sharp minima困住的可能性更小,进入flat minima的可能性更大。

2.2.2 momentum

调整参数时不光根据当前梯度的方向,还根据上一步移动的方向。

2.3 自动调整学习率(Adaptive Learning Rate)

2.3.1 Adagrad
  • 不同的参数使用不同的learning rate

  • Root Mean Square (RMS),用于Adagrad中。

    • 该方法认为之前的每一个g都同等重要。
    • 坡度小时,g也小, σ i t \sigma_i^t σit就小, η σ i t \frac{\eta}{\sigma_i^t} σitη就大,更新步子就大;坡度大时,g也大, σ i t \sigma_i^t σit就大, η σ i t \frac{\eta}{\sigma_i^t} σitη就小,更新步子就小。

在这里插入图片描述

2.3.2 RMSProp
  • Adagrad方法还是存在缺陷,比如在经过很漫长的平滑区域积累了很大的learning rate,此时很可能突然遇到Loss曲线陡峭的情况,但因为learning rate很大而直接跳过。
  • RMS Prop办法不仅要考虑之前累计的信息,也要考虑当下的信息。其参数更新为:

在这里插入图片描述

  • 这样根据调整 α \alpha α的值就可以设定时更关注于当下还是之前
2.3.3 Learning Rate Scheduling
  • Learning Rate Decay
    • 随着参数不断更新,逐渐减小学习率 η \eta η
  • Warm Up
    • 学习率先变大,再变小
    • 这个方法实用的一个可能的解释是当我们在用Adam RMS Prop我们会需要计算σ,它是一个统计的结果。σ告诉我们,某一个方向它到底有多陡或者是多平滑,那这个统计的结果要看得够多笔数据以后才精确,所以一开始我们的统计是不精确的,所以我们一开始不要让我们的参数离初始的地方太远,先让它在初始的地方呢做一些像是探索这样,所以一开始learning rate比较小,是让它探索收集一些有关error surface的情报,等σ统计得比较精準以后,在让learning rate呢慢慢地爬升。

在这里插入图片描述

2.3.4 Adam: RMSProp + Momentum
  • θ i t + 1 = θ i t − η t σ i t m i t \theta_i^{t+1} = \theta_i^t-\frac{\eta^t}{\sigma_i^t}m_i^t θit+1=θitσitηtmit
  • m i t m_i^t mit:momentum是一个向量,反映过去所有gradient的总和
  • σ i t \sigma_i^t σit:是一个标量,也反映过去所有gradient的总和,但没有方向

2.4 Loss函数

2.4.1 分类问题
  • 某些情况下可以将分类问题,将类别1编号为1,类别2编号为2,类别3编号为3,但这意味着类别1和类别2更接近,和类别3差别更大。因此会将类别按独热码编码。(?猫、狗、苹果?)

  • 将回归问题输出的一个结果,改为3个结果即可(乘上不同的weight)

  • 通常会将结果做一个softmax,使 y , y^, y,介于0和1之间,同时加深值之间的差距

  • 最后计算 y , y^, y, y y y之间的差距

    • Mean Square Error

    • Cross-entropy(用于分类问题更合适)

    • 可發現當Loss很大的時候 Cross-entropy有斜率 Mean Square Error比較平坦→可能會卡住,雖然可以用Learning rate解決,但Traning起步仍是較困難的

在这里插入图片描述

2.5 批次标准化(Batch Normalization)

  • 当不同feature的尺度差距很大时,需要标准化让training更容易。
  • 对每一层的输入都要标准化。
  • 由于数据集很大,所以通常只对一个batch做标准化。
  • 在实际使用时,由于每次只有一个数据传入,因此需要将每个batch算出来的 μ 和 σ \mu和\sigma μσ记录供服务使用。

3 Convolutional Neural Network——常用于图像

  • 电脑中一张图片是一个三维的Tensor,长、宽、channel

在这里插入图片描述

3.1 为什么要不用fully connected network处理图像呢?

  • 全连接的神经网络弹性很大,每一个neuron都可以看到图片每个通道的每一个pixel(像素)。

    1. 首先出现的一个问题是计算量十分大。假设图片的size是(100, 100, 3),input就一共有3w个数字,经过几层的layer之后,计算量成指数级上升。

在这里插入图片描述

  1. 当我们在辨认物体时,只要抓住关键特征就可以确定这是什么物体,不需要看整张图片。

3.2 简化全连接网络的方法

3.2.1 感受野(receptive field)
  • 每个Neuron只需要关心一个感受野中的输入,就可以减少参数。

在这里插入图片描述

  • 最常用的感受野设置方法

    • 大小称为kernel size,通常使用3x3,但有的特征可能用3x3感受不出来

    • 一组神经元去守备一个感受野,如64个,128个

    • 每个感受野的距离称为stride,往往设置比kernel size小,因为希望感受野有重叠,从而可以感受到跨感受野的特征

    • 如果感受野超出图片范围后,需要增加数据,称为padding。

在这里插入图片描述

3.2.2 共享参数
  • 一种关键特征(pattern)可能出现在图片的不同位置

  • 比如两个神经元都是用于检测鸟嘴这个特征,只是检测的感受野不同,因此可以让这两个神经元共享参数(parameter sharing),也就是两个神经元的weight完全一样

  • 虽然参数一样,但感受野不同,所以输出也不会一样。因此不会让守备同一个感受野的两个神经元共享参数。

  • 常用共享参数方法

    • 每一个感受野都有一组神经元守备。

    • 每一个感受野的某个神经元都和别的感受野某一个神经元共享参数。

在这里插入图片描述

  • 实际上就是每个filter依次去与每个感受野做卷积。
3.2.3 池化(pooling)
  • 将filter卷积后产生的feature map,每个池化区域只取一个值,减小图片大小,减少运算量。

在这里插入图片描述

4 Self-Attention

4.1 Word Embedding

4.1.1 独热编码
  • 使用独热码编码每一个单词,但是这样导致单词之间没有任何关系,因此会使用Word Embedding方法,让类似语义的单词在图上比较接近。

在这里插入图片描述

4.1.2 Word Embedding
  • 无监督的训练方法。
  • 根据上下文。

4.2 Self-attention 内部是怎么运作的?

  • self-attention是将初始输入转化成带有整个sequence的信息的输出
  • 假设 a i a_i ai是输入层或某一个hidden layer的输入向量组(或input seq), b i b_i bi 是经过self-attention机制后考虑了整个seq信息的向量组,以如何得到 b 1 b_1 b1为例解释self-attention机制。
  • a1要考虑seq中的a2, a3, a4,找到与判断a1标签这件事相关的vector,每一个向量跟a1的相关性用α衡量,α越大关联越大。
4.2.1 α \alpha α的计算方法
  • 有三个矩阵 W q W_q Wq W k W_k Wk W v W_v Wv,分别用于计算Query向量、Key向量和Value向量

在这里插入图片描述

  • 通常 a 1 a^1 a1也需要计算和自己的关联程度,再做一个softmax(或者别的激活函数)来标准化

在这里插入图片描述

  • 再用 W v W_v Wv点乘每个向量得到 v i v^i vi v i v^i vi依次与softmax得到的 α 1 , i ′ \alpha^\prime_{1,i} α1,i点乘后相加得到 b 1 b^1 b1

在这里插入图片描述

  • 矩阵形式

在这里插入图片描述

4.3 Multi-head Self-attention

  • 计算时会产生两组q,k,v,表示需要找到两个不同的相关性。

在这里插入图片描述

4.4 Positional Encoding是什么?

  • 目前为止network中还没有位置信息,训练中加入向量所处位置的信息,就叫Positional Encoding
    • 为每一个位置设定一个唯一的向量 e i e^i ei
    • a i + e i a^i+e^i ai+ei就可以了

5 Transformer

  • seq2seq的模型通常具有encoder,decoder两个结构

5.1 Transformer Encoder

Encoder的input sequence长度为N,out seq的长度也为N

  1. 首先,将inputs加上Positional Encoding,得到新的inputs

  2. 将上一步的结果作为多头注意力机制的输入,得到相应的output

  3. Residual:将输出a,加上原来的输入b,得到residual

    • 这一步称为残差连接(Residual Connection),通常用于解决梯度消失和梯度爆炸的问题,帮助模型更快收敛。
  4. norm:将residual经过layer normalization(不是batch normalization)

    • 对同一个example里面不同的dimension做标准化

在这里插入图片描述

  1. 经过FC:将norm之后的输出送入fully connected network(原论文中写的是Feed Forward)

  2. Residual:再做一次残差连接,将第4步标准化后的输出加上第5步的输出

  3. norm:再进行一次norm最终得到一个block的输出

    在这里插入图片描述

5.2 Transformer Decoder

  • decoder分为autoregressive(AT), non-autoregressive(NAT) 两种

    • autoregressive(AT)

      • 在Encoder完成之后,将其输出作为一个输入喂到Decoder中。

      • 同时,输入一个special token:START表示开始工作。

      • Decoder结合这两个输入,输出一个经过softmax处理后的长度为Vocabulary Size的输出向量,该向量中每一个中文字都对应一个数值,数值最大的中文字为最终输出的中文字,下图中,输出的结果是“机”。

      • 接下来,将“机”对应的向量作为Decoder的输入,做下一步计算。这样Decoder会考虑下边两个输入

        • START对应的向量
        • “机”对应的向量
      • 接下来,将“器”对应的向量也作为Decoder的输入,这样Decoder会考虑下边三个输入

        • START对应的向量
        • “机”对应的向量
        • “器”对应的向量

在这里插入图片描述

  • non-autoregressive(NAT)

    • NAT decoder一次性产生一整个句子
  • Decoder相较Encoder新增了一个Masked Multi-Head attention

在这里插入图片描述

5.2.1 Masked Multi-Head attention
  • 不能再每一个输出都得到所有seq的信息,只能使用已经使用过的input的信息。比如 b 1 b^1 b1只能使用 a 1 a^1 a1,而 b 2 b^2 b2可以使用 a 1 a^1 a1 a 2 a^2 a2.
  • 因为transformer decoder是序贯产生输出了,看了右边的input vectors也没用。和Decoder的串行计算顺序相对应的
5.2.2 encoder和decoder传递信息
  • encoder和decoder进行交流的模块是cross attention它有三个输入,两个输入来自encoder,一个输入是masked attention的输出。
5.2.2.1 Cross attention的运行过程
  • 左边是encoder及其输出,右边是decoder的self-att (masked)部分,self att (masked)输入begin token之后产生了第一个输出,该输出经过linear transform生成一个query q,再和左边encoder的k, v做cross attention。所以可以理解为query q去抽取encoder的信息,作为decoder中fully connected network的输入。

在这里插入图片描述

5.3 Transformer的训练

5.3.1 copy mechanism
  • 有时候不需要创造输出,需要复制一些重要字词。如聊天机器人,文本摘要。
5.3.2 guided attention
  • 模型有时候会犯低级错误(如漏字),强迫模型把输入的每一个字都看一遍,对语音辨识、合成比较重要。

6 生成式对抗网络(Generative Adversarial Network, GAN)

6.1 概率生成模型

  • 概率生成模型(Probabilistic Generative Model)简称生成模型,指一系列用于随机生成可观测数据的模型。
  • output不光与input有关,还与随机分布distribution有关。
  • 生成模型的两个基本功能:概率密度估计生成样本(即采样)。
6.1.1 什么时候需要生成模型?
  • 同样的输入有多种不同的输出,而这些输出都是对的。需要模型有一些创造力
    • 绘画
    • 对话

6.2 GAN

  • 生成对抗网络(Generative Adversarial Networks,GAN)是一种隐式密度模型,包括判别网络(Discriminator Network)和生成网络(Generator Network)两个部分,通过对抗训练的方式来使得生成网络产生的样本服从真实数据分布。
    • 判别网络:目标是尽量准确地判断一个样本是来自于真实数据还是由生成网络产生。
    • 生成网络:目标是尽量生成判别网络无法区分来源的样本。
6.2.1 GAN的训练过程
  1. 初始化generator G和discriminator D的参数

  2. 固定生成网络,训练判别网络

    • 将从分布 p ( z ) p(z) p(z)中采样出来的向量输入到生成网络中,得到对应的输出(图片)
    • 将输出结果与真实的database进行比较,让判别网络学习它们之间的差异
    • 具体来说,我们可以将真实数据标为1,生成网络的输出结果标为0
    • 接下来,我们既可以将此看作一个分类问题,也可以看作一个回归问题:
      • 分类问题:将真正的数据看作类别1,生成网络产生的数据看作类别2,然后利用这些数据训练一个分类器。
      • 回归问题:训练判别网络看到真实数据时输出1,看到生成网络生成的数据时输出0,来学习两者之间的差异。

在这里插入图片描述

  1. 固定判别网络,训练生成网络

在这里插入图片描述

6.3 理论介绍

6.3.1 训练目标
  • 在GAN中,对于真实的训练样本的分布,记作 P d a t a ( x ) P_{data}(x) Pdata(x),这个分布也就是GAN试图去拟合、逼近的分布。另外有一个由参数控制的分布记作 P G ( x ; θ ) P_G(x;\theta) PG(x;θ),其实也就是GAN或者说Generator生成的对象的分布。简单来说我们的目标就是 P G ( x ; θ ) P_G(x;\theta) PG(x;θ)和越 P d a t a ( x ) P_{data}(x) Pdata(x)接近越好。
    G ⋆ = a r g min ⁡ G D i v ( P G , P d a t a ) G^{\star} = arg \min_G Div(P_G,P_{data}) G=argGminDiv(PG,Pdata)

  • 但是我们往往不知道 P G ( x ; θ ) P_G(x;\theta) PG(x;θ) P d a t a ( x ) P_{data}(x) Pdata(x)的分布,很难算出两分布之间的KL散度,而GAN的优势就在于它解决了这个问题,这就是GAN最大的贡献。

6.3.2 GAN的目标函数

G ⋆ = a r g min ⁡ G max ⁡ D V ( G , D ) G^{\star} = arg \min_G \max_DV(G,D) G=argGminDmaxV(G,D)

  • 其中 V V V可以使用以下式子,而 max ⁡ D V ( G , D ) \max_DV(G,D) maxDV(G,D)是与JS散度有关的。
    V = E y ∼ P d a t a [ l o g D ( y ) ] + E y ∼ P G [ l o g ( 1 − D ( y ) ) ] V =E_{y\sim P_{data}}[logD(y)]+E_{y\sim P_G}[log(1-D(y))] V=EyPdata[logD(y)]+EyPG[log(1D(y))]

6.4 实践时的不足

  • GAN是很难训练好的,因为哪怕真正使用JS散度来衡量两分布依然不适合,计算出来的JS散度也会非常小。

  • 原因1——数据本身的特性

    • 图片实际上是高维空间的低维流形(manifold),以二次元头像为例,头像实际上只是二维空间中一个很小的子集,二维空间中多数的元素都不会形成二次元头像。

    • 因此从这个角度来看, P G P_G PG P d a t a P_{data} Pdata的交集本身就很小,这样数据的JS散度就会很小。

在这里插入图片描述

  • 原因2——通过采样(sample)的方式训练

    • 由于训练时是通过采样的方法,对于采样出的样本,如果判别网络够强,它仍然有能力将两份样本分开(类似过拟合)。

在这里插入图片描述

6.5 Wasserstein GAN

6.5.1 Wasserstein Distance
  • 把两个分布比作两堆土,将其中一堆铲成另一堆土所需移动的土的最小平均距离就是wasserstein distance。

  • 举例来说,在下图中的一维空间中,两个分布都集中在一个点上,距离为 d d d,将 P P P移动到 Q Q Q上的wasserstein distance就是 W ( P , Q ) = d W(P,Q)=d W(P,Q)=d.

在这里插入图片描述

6.5.2 WGAN的做法

K W ( P d a t a , P G ) = max ⁡ D ∈ K − L i p s c h i t z { E y ∼ P d a t a [ D ( y ) ] − E y ∼ P G [ D ( y ) ] } KW(P_{data},P_G)=\max_{D\in K-Lipschitz}\{E_{y\sim P_{data}}[D(y)]-E_{y\sim P_G}[D(y)]\} KW(Pdata,PG)=DKLipschitzmax{EyPdata[D(y)]EyPG[D(y)]}

  • 要求D(Discriminator)是一个足够平滑的function(K-Lipschitz),变化不至于太剧烈,这样可以避免判别网络得到的值不会为无穷,导致网络无法收敛。
  • 然而在WGAN这篇论文中,并没有提出很好的让曲线平滑的方法。它只是强制参数在c到-c之间,避免产生无穷大的结果。
6.5.3 Improved WGAN
  • Gradient Penalty
  • SN GAN——Spectral Normalization

6.6 生成网络效能评估与条件式生成

6.6.1 评估方法
6.6.1.1 为特定任务再训练一个网络
  • 比如生成人脸的任务,就再训练一个人脸识别的网络。
6.6.1.2 存在的问题——Mode Collapse
  • 生成网络在得到一个比较好的结果后,就只会生成类似的结果,多样性很低

!在这里插入图片描述

  • 评估方法——使用分类器进行分类,看类别数量或分布

    • Frechet Inception Distance——FID
6.6.2 条件式生成
  • 同时输入两个对象
    • 从先验分布中采样出来的样本 z z z
    • 当前的条件 c c c
  • 这样的Discriminator的训练样本也要有所变化
    • 对于正样本,必须是正确的文字描述和对应的真实的图片
    • 而负样本分两种
      • 文字描述配上不真实的图片
      • 真实的图片随机匹配错误的文字描述

6.7 GAN+Unsupervised Learning

  • Cycle GAN
    • 增加一个generator,让输出的东西经过转换后接近输入的东西

7 自监督学习(Self-supervised Learning)

  • self-supervised learning没有直接的标签 y ^ \hat y y^ ,是把输入数据 x x x分为 x ′ x\prime x x ′ ′ x\prime \prime x′′两部分, x ′ ′ x \prime \prime x′′作为标签进行训练。

7.1 BERT的训练(pre-train)

  • BERT模型一共做了两件事:
    • ① Masking input(aka, 填空)
    • ② Next Sentence Prediction
7.1.1 Masking Input
  • BERT模型本身就是Transformer的Encoder

  • BERT会对输入随机mask一些token,然后让机器来填空

    • 标记为一个特殊的符号
    • 随机填上一个文字
  • 训练目标就是最小化预测结果与已知的ground truth之间的交叉熵。

在这里插入图片描述

7.1.2 Next Sentence Prediction
  • 首先在开始位置加上一个特殊的classification token**[CLS],在两个句子之间加上一个分隔符[SEP]**。
  • 只取出[CLS]token对应的输出向量,进行线性变换和softmax,来判断Sentence1的下一句是否为Sentence2
    • 因为CLS token 无明显语义无明显语义 ,可以作为 整句话的语义表示整句话的语义表示 ,从而用于下游任务。
7.1.3 Bert的微调(fine-tune)
  • BERT做填空题和NSP对于我们很关心的其他任务来说非常有用,并且这些任务只有少量带标签的数据,我们称训练BERT的过程称为预训练(pre-train),称我们关心的其他任务为下游任务(downstream tasks)。
  • BERT模型预训练之后,经过**微调(fine-tune)**之后,可以应用于各式各样的下游任务中。

7.2 Bert的下游任务

7.2.1 情感分析(sentiment analysis)
  • 将[CLS] token对应的输出向量表示进行线性变换和softmax,得到sequence的类别。

在这里插入图片描述

7.2.2 词性标注(POS tagging)
  • 将每个单词对应的输出向量表示进行线性变换和softmax
7.2.3 自然语言推理(NLI)
  • NLI任务是在某个前提(premise)条件下,某个假设(hypothesis)与前提的关系是什么?是矛盾(contradiction)、蕴涵(entailment),还是中立(neutral)
7.2.4 基于抽取的问答Extraction-based Question Answering (QA)
  • 基于抽取的问答的限制是答案必须来自于文章

  • 输入的document中有N个单词,输入的query或question中有M个单词,最后输出两个整数(s, e),分别表示答案在document中的起始(start)位置和结束(end)位置

在这里插入图片描述

  • 具体训练方式

    • 随机初始化两组向量

    • 第一组与document对应的输出向量表示做内积(inner product),其结果进行线性变换和softmax作为起始位置。

在这里插入图片描述

  • 第二组做同样的操作,作为结束位置。

在这里插入图片描述

7.3 BERT为何这么腻害

  • BERT通过做填空题可以得到能够表示输入单词含义(meaning)的embedding,举例来说它可以判断”红富士苹果“和”苹果手机“中的”苹果 “不一样。
  • **BERT结构中encoder里面的注意力机制可以获取到上下文的信息,**所以输出的embedding是包含上下文的信息的,我们称之为contextualized word embedding,即考虑上下文的词嵌入。

7.4 Multi-BERT,多语言BERT

  • 使用多种语言对BERT进行预训练
  • Multi-BERT在一种语言上做微调,在另一种语言上做测试(都是同样的任务,只有语言不同),表现也不错

7.5 GPT系列(Generative Pre-trained Transformer)

  • GPT做的事其实是Predict Next Token

在这里插入图片描述

在这里插入图片描述

8 Auto-Encoder

  • 左边是编码器,将高维度图像数据转化为低维度向量。
  • 右边是解码器,将低维度向量,解码还原为高维度图片。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值