基于 StyleGAN 的图像生成学习实践

借助于研一期间的图像处理课程,于两个月内接触了当时最先进的图像生成方法——StyleGAN。本报告记录了学习的理论内容和实验结果。

1 引言

图像处理一直是信息处理、计算机等专业研究的交叉领域,有很多不同门类的方法在不断的更新和创新。所以作为一名相关领域的学生,应当或多或少的了解其内容。在图像生成中,GAN 是一个很火热的研究方向。本文将注意力集中在利用 GAN 网络生成图像上,经过调研,StyleGAN 是该领域中当前 SOTA 的方法,所以,为了赶上时代潮流,研究最新的技术,本文以 StyleGAN 为切入 点进行了理论学习和实验操作。

本文正文分三部分,依次介绍 GAN 的基础原理、StyleGAN 的全部内容和 基于 StyleGAN 的实验部分。

2 GAN的背景及应用

2.1 对抗网络介绍

对抗网络的基础是生成模型和判别模型。判别模型比较好理解,就像分类一样, 有一个判别界限,通过这个判别界限去区分样本。从概率角度分析就是获得样本 x 属于类别 y 的概率,是一个条件概率 P(y|x)。而生成模型是需要在整个条件内去产生数据的分布, 就像高斯分布一样,需要去拟合整个分布,从概率角度分析就是样本 x 在整个分布中的产生的概率,即联合概率 P(xy)。

对抗网络只是提出了一种网络结构,总体来说,GANs 简单的想法就是用两个模型,一个生成模型,一个判别模型。判别模型用于判断一个给定的图片是不是真实的图片(从数据集里获取的图片),生成模型的任务是去创造一个看起来像真的图片一样的图片。而在开始的时候这两个模型都是没有经过训练的,这两个模型一起对抗训练,生成模型产生一张图片去欺骗判别模型,然后判别模型去判断这张图片是真是假,最终在这两个模型训练的过程中,两个模 型的能力越来越强, 最终达到稳态。

2.2 GANs原理

我们假设把每一个图片看作二维空间中的一个点,并且现有图片会满足于某个数据分布,我们记作𝑃 (𝑥)。以人脸举例,在很大的一个图像分布空间中, 实际上只有很小一部分的区域是人脸图像。今天我们需要做的,就是让机器去找到人脸的分布函数。具体来说,就是我们会有很多人脸图片数据,我们观测这些数据的分布,大致能猜测到哪些区域出现人脸图片数据的概率比较高,但是如果让我们找出一个具体的定义式,去给明这些人脸图片数据的分布规律, 我们是没有办法做到的。但是如今,我们有了机器学习,希望机器能够学习到这样一个分布规律,并能够给出一个极致贴合的表达式。

在 GANs 出现之前,人们采用的方法是 Maximum Likelihood Estimation。 简单来说,就是我们有一个生成器P_g和一组参数\theta,我们还有从真实分布𝑃 (𝑥) 中采样出的数据{𝑥},我们不知道数据的真实分布具体长什么样,但是我们希望不断地调整𝑃和θ,让𝑃 (𝑥;𝜃)越接近𝑃 (𝑥)越好。具体的做法是,对于每一组参数θ 和真实分布的抽样𝑥,我们能够计算出参数θ下的生成器生成该真实抽样𝑥的 likelihood,于是我们希望找到一个最佳的参数组θ,使得生成器的结果最接近𝑃 (𝑥),也就是对于每个真实抽样𝑥的 likelihood 都最大,这等价于所有真实抽样𝑥的 likelihood 的乘积最大,那原始问题就转换为如下这个最大似然问题:

下面我们需要求解这个 maximizing the likelihood 问题,我们先证明,它其实等价于求 minimize KL Divergence(KL Divergence 是一个衡量两个分布之间的差异的计算式)问题。

L=\prod_{i=1}^{m}P_G(x^i;\theta)

首先我们加上一个对数 log,将累乘转化为累加问题。然后再将累加转化为期望问题:

\theta^*=arg\underset{\theta}{max}\prod_{i=1}^{m}P_G(x^i;\theta)=arg\underset{\theta}{max} log \prod_{i=1}^{m}P_G(x^i;\theta) \\ =arg\underset{\theta}{max}\sum_{i=1}^{m}logP_G(x^i;\theta)\approx arg\underset{\theta}{max}E_{x\sim P_{data}}\left [ logP_G(x^i;\theta) \right ] \\ =arg\underset{\theta}{max}\int_x P_{data}(x)logP_G(x;\theta)dx

最终,经过补项化简,式子便刚好等价于P_{data}P_g的 KL Divergence 的最小化求解。 现在这个 KL Divergence 的最小化问题如何求解呢?下面考虑引用神经网络。

我们把这个神经网络称作生成网络。通过上述的分析我们知道,我们需要训练出这样的生成器,对于一个已知分布的数据 z,它可以把数据 z 转化成一个未知分布的数据 x,这个未知分布可以与 z 所在的分布完全不一样,我们把它称作𝑃 (𝑥),并且我们希望𝑃 (𝑥)与 Pdata(𝑥)之间的散度距离(Divergence,下称 Div)越小越好。如果能找到这样的𝑃g,那也就意味着我们找到了真实数据分布 𝑃data (𝑥)的近似解,也就意味着我们能够生成各种各样符合真实分布规律的数据。

现在一个最关键的问题是,这个 Div 要如何计算出来呢?理论上来说我们不知道𝑃 (𝑥)是什么,我们也不知道𝑃 (𝑥)是什么,因此 Div 是我们无法计算的。 但是,人无法计算的东西,交给神经网络或许就知道如何计算了。于是,我们新建了一个神经网络,专门来量𝑃 (𝑥)与𝑃 data(𝑥)之间的 Div,这个神经网络,就叫做判别器。

现在交给判别器去判别读入数据是来自𝑃data 还是𝑃g,实际上就是在衡量两者之间的 Div,因为如果二者之间的 Div 越大,判别器就会给𝑃g 的数据更低的分数,给𝑃data 的数据更高的分数;而如果二者之间的 Div 越小, 判别器就会给二者的分数越接近;当两者完全一致时,也就是 Div=0 时,判别器给二者的分数就都是 0.5 了。当然,上述只是我们直观上觉得说,判别器是与 Div 有关的,下面我们需要用数学方法证明:

判别器真的可以衡量 Div 的值。我们先来看一下判别器的目标式:

V(G, D)=E_{x\sim P_{data}}[logD(x)]+E_{x\sim P_{G}}[log(1-D(x))]

D^*=arg\underset{D}{max}V(D,G)

这个式子很好理解,如果来源 x~𝑃data,D(x)尽可能高;如果来源 x~𝑃g, D(x)尽可能低。下面我们求解一下这个目标式。首先将目标式转化为一个积分:

我们假设 D(x)可以是任意函数。那么现在这个表达式,对于所有的 x,计 算出一个表达式,使得所有表达式的积分求和最大,这等价于,如果对于每一 个表达式使得这个表达式的最大,那么最终所有表达式的积分求和也最大, 即: 

P_{data}(x)logD(x)+P_G(x)log(1-D(x))

方程求解得:

至此,我们证明了,最大化 V(G,D)问题的求解实际上就是在求解𝑃data 与 Pg 间 Div 的值(与前面提到的 KL Div 可以认为是等效的)。于是,我们可以再回到生成器要解决的问题上。生成器的目的,是让生成数据与真实数据之间的 Div 最小,本来 Div 是没有办法计算的,但是现在有了判别器之后,Div 变得可以计算了,于是生成器的新的目标表达式变为:

G^*=arg \ \underset{G}{min} \ \underset{D}{max}V(G,D)

接下来要求解这个表达式。在实际训练中,判别器与生成器是交替训练的,并且是先判别器后生成器。因此,当生成器需要求解时,判别器已经训练完成,公式可以写为:

G^*=arg\underset{G}{min}L(G)

该目标函数用梯度下降即可求解。

综上,我们求解出了判别器,然后我们也求解出了生成器,下面我们先用一个完整的算法来回顾一下这整套流程。

第一个部分是训练判别器,先从真实数据分布中抽样,然后从先验分布中抽样,并通过生成器产生仿造数据𝑥̃,接着把𝑥和𝑥̃丢入判别器中训练,使得目标函数𝑉最大;第二个部分是训练生成器,从先验分布中抽样新的 z,接着把 z 丢 入生成器中训练,使得目标函数最小。这样循环交替,最终生成器产生的数据𝑥̃ 就会越来越接近真实数据𝑥。

以上只是最初级的 GAN 原理,针对其中的一些不完美的点,还有很多工作做了针对性的优化改进,例如基于 Div 改进的 fGAN、LSGAN、WGAN、 SNGAN 等;基于网络优化的 DCGAN、SAGAN、BigGAN 等;还有 RGAN、 EBGAN 等其他的改进。

3 StyleGAN

3.1 StyleGAN 前身:ProGAN

上图是 ProGAN 的网络结构图。这项技术首先通过学习即使在低分辨率图像中也可以显示的基本特征,来创建图像的基本部分,并且随着分辨率的提高和时间的推移,学习越来越多的细节。低分辨率图像的训练不仅简单、快速, 而且有助于更高级别的训练,因此,整体的训练也就更快。

特别值得注意的是,上图 Generator 中的网络结构不是指的从 4*4 网络连接 到 8*8 网络,再连接到 16*16 网络依次输出,而是指的从 4*4 网络变化到 8*8 网络,再变化到 16*16 网络。也就是说,Generator 内部的网络只有一个,但是在训练过程中网络的结构是在动态变化的。事实上,前面那种依次连接的网络模型叫做 StackGAN,但是 StackGAN 不适合用来做超清图片生成,因为会特别慢。

不过,ProGAN 网络结构的动态变化是如何做到的呢?因为如果从 4×4 的输出直接变为 8×8 的输出的话,网络层数的突变会造成 GANs 原有参数失效, 导致急剧不稳定,这会影响模型训练的效率。为了解决这一问题,ProGAN 提出了一种平滑过渡技术。

如上图所示,当把生成器和判别器的分辨率加倍时,会平滑地增大新的层。我们以从 16×16 像素的图片转换到 32×32 像素的图片为例。在转换(b)过程中,把在更高分辨率上操作的层视为一个残缺块,权重α从 0 到 1 线性增长。 当α为 0 的时候,相当于图(a),当α为 1 的时候,相当于图(c)。所以,在转换过程中,生成样本的像素,是从 16x16 到 32x32 转换的。同理,对真实样本也做了类似的平滑过渡。 

上图中的 2×和 0.5×指利用最近邻卷积和平均池化分别对图片分辨率加倍和折半。toRGB 表示将一个层中的特征向量投射到 RGB 颜色空间中, fromRGB 正好是相反的过程;这两个过程都是利用 1×1 卷积。当训练判别器时,插入下采样后的真实图片去匹配网络中的当前分辨率。在分辨率转换过程 中,会在两张真实图片的分辨率之间插值,类似于将两个分辨率结合到一起用生成器输出。由于 ProGAN 是逐级直接生成图片,我们没有对其增添控制,我们也就无法获知它在每一级上学到的特征是什么,这就导致了它控制所生成图像的特定特征的能力非常有限。换句话说,这些特性是互相关联的,因此尝试调整一下输入,即使是一点儿,通常也会同时影响多个特性。

我们希望有一种更好的模型,能让我们控制住输出的图片是长什么样的, 也就是在生成图片过程中每一级的特征,要能够特定决定生成图片某些方面的表象,并且相互间的影响尽可能小。于是,在 ProGAN 的基础上,StyleGAN 作出了进一步的改进与提升。

 3.2 StyleGAN 架构

StyleGAN 首先重点关注了 ProGAN 的生成器网络,它发现,渐进层的一个潜在的好处是,如果使用得当,它们能够控制图像的不同视觉特征。层和分辨率越低,它所影响的特征就越粗糙。简要将这些特征分为三种类型:

1、粗糙的——分辨率不超过 8^2,影响姿势、一般发型、面部形状等;

2、中等的——分辨率为 16^2 至 32^2,影响更精细的面部特征、发型、眼睛的睁开或是闭;

3、高质的——分辨率为 64^2 到 1024^2,影响颜色(眼睛、头发和皮肤)和微观特征;

然后,StyleGAN 就在 ProGAN 的生成器的基础上增添了很多附加模块以实现样式上更细微和精确的控制。

映射网络

StyleGAN的第一点改进是,给Generator的输入加上了由 8 个全连接层组成的Mapping Network,并且 Mapping Network 的输出𝑊′与输入层(512×1)的大小相同。 

添加 Mapping Network 的目标是将输入向量编码为中间向量,并且中间向量后续会传给生成网络得到 18 个控制向量,使得该控制向量的不同元素能够控制不同的视觉特征。为何要加 Mapping Network 呢?因为如果不加这个 Mapping Network 的话,后续得到的 18 个控制向量之间会存在特征纠缠的现象——比如说我们想调节 8*8 分辨率上的控制向量(假设它能控制人脸生成的角度),但是 我们会发现 32*32 分辨率上的控制内容(譬如肤色)也被改变了,这个就叫做特征纠缠。所以 Mapping Network 的作用就是为输入向量的特征解缠提供一条学习的通路。

为何 Mapping Network 能够学习到特征解缠呢?简单来说,如果仅使用输入向量来控制视觉特征,能力是非常有限的,因此它必须遵循训练数据的概率密度。例如,如果黑头发的人的图像在数据集中更常见,那么更多的输入值将会被映射到该特征上。因此,该模型无法将部分输入(向量中的元素)映射到特征上,这就会造成特征纠缠。然而,通过使用另一个神经网络,该模型可以生成一个不必遵循训练数据分布的向量,并且可以减少特征之间的相关性。

映射网络由 8 个全连接层组成,它的输出𝑊′与输入层(512×1)的大小相同。

样式模块(AdaIN)

StyleGAN 的第二点改进是,将特征解缠后的中间向量𝑊′变换为样式控制向量,从而参与影响生成器的生成过程。生成器由于从 4*4,变换到 8*8,并 最终变换到 1024*1024,所以它由 9 个生成阶段组成,而每个阶段都会受两个控制向量(A)对其施加影响,其中一个控制向量在 Up sample 之后对其影响一 次,另外一个控制向量在 Convolution 之后对其影响一次,影响的方式都采用 AdaIN (自适应实例归一化)。因此,中间向量𝑊′总共被变换成 18 个控制向量 (A)传给生成器。

其中 AdaIN 的具体实现过程如上右图所示:将𝑊′通过一个可学习的仿射变换(A,实际上是一个全连接层)扩变为放缩因子 y𝑠,𝑖与偏差因子 y𝑏,𝑖,这两个 因子会与标准化之后的卷积输出做一个加权求和,就完成了一次𝑊 ′ 影响原始输出 x𝑖 的过程。而这种影响方式能够实现样式控制,主要是因为它让𝑊 ′ (即变换后的 y𝑠,𝑖 与 y𝑏,𝑖 )影响图片的全局信息(注意标准化抹去了对图片局部信息的可见性),而保留生成人脸的关键信息由上采样层和卷积层来决定,因此𝑊′只能够影响到图片的样式信息。

删除传统输入

既然 StyleGAN 生成图像的特征是由𝑊′和 AdaIN 控制的,那么生成器的初始输入可以被忽略,并用常量值替代。这样做的理由是,首先可以降低由于初始输入取值不当而生成出一些不正常的照片的概率(这在 GANs 中非常常见),另一个好处是它有助于减少特征纠缠,对于网络在只使用𝑊′不依赖于纠缠输入向量的情况下更容易学习。

随机变化

 

人们的脸上有许多小的特征,可以看作是随机的,例如:雀斑、发髻线的准确位置、皱纹、使图像更逼真的特征以及各种增加输出的变化。将这些小特征插入 GAN 图像的常用方法是在输入向量中添加随机噪声。为了控制噪声仅影响图片样式上细微的变化,StyleGAN 采用类似于 AdaIN 机制的方式添加噪声,即在 AdaIN 模块之前向每个通道添加一个缩放过的噪声,并稍微改变其操作的分辨率级别特征的视觉表达方式。加入噪声后的生成人脸往往更加逼真与多样。

样式混合

StyleGAN 生成器在合成网络的每个级别中使用了中间向量,这有可能导致网络学习到这些级别是相关的。为了降低相关性,模型随机选择两个输入向量,并为它们生成了中间向量𝑊’。然后,它用第一个输入向量来训练一些网络级别,然后(在一个随机点中)切换到另一个输入向量来训练其余的级别。随机的切换确保了网络不会学习并依赖于一个合成网络级别之间的相关性。

虽然它并不会提高所有数据集上的模型性能,但是这个概念有一个非常有趣的副作用——它能够以一种连贯的方式来组合多个图像(视频请查看原文)。 该模型生成了两个图像 A 和 B,然后通过从 A 中提取低级别的特征并从 B 中提取其余特征再组合这两个图像,这样能生成出混合了 A 和 B 的样式特征的新人脸。

在 W 中的截断技巧

 

在生成模型中的一个挑战,是处理在训练数据中表现不佳的地方。这导致了生成器无法学习和创建与它们类似的图像(相反,它会创建效果不好的图像)。为了避免生成较差的图像,StyleGAN 截断了中间向量𝑊’,迫使它保持接近“平均”的中间向量(上图左 4)。

对模型进行训练之后,通过选择多个随机的输入,用映射网络生成它们的中间向量,并计算这些向量的平均值,从而生成“中间向量”的平均值。当生成新的图像时,不用直接使用映射网络的输出,而是将值𝑊′转换为𝑊′𝑛𝑒𝑤 = 𝑊′𝑎𝑣𝑔 +𝞧(𝑊′− 𝑊′𝑎𝑣𝑔),其中𝞧的值定义了图像与“平均”图像的差异量(以 及输出的多样性)。有趣的是,在仿射转换块之前,通过对每个级别使用不同的 𝞧,模型可以控制每个级别上的特征值与平均特征值的差异量。

微调超参数

StyleGAN 的另外一个改进措施是更新几个网络超参数,例如训练持续时间和损失函数,并将图片最接近尺度的缩放方式替换为双线性采样。

总结

综上所述,加入了一系列附加模块后得到的 StyleGAN 最终网络模型结构图如下:

3.3 StyleGAN-encoder

这是一个用于图像风格迁移的开源项目,核心是找到所需风格的特征码。 有论文对这样的应用做了归纳,他们把解决办法称之为“stochastic clipping”(随机剪裁),其基本原理大致是:

特征码(特征向量)中每个数值通常处于一个有 限的空间内,论文指出通常分布在[-1.0, 1.0]这个变动区间内,因此可以从某个特征向量开始(甚至于从全零向量开始),先把超出[-1.0, 1.0]范围的数值剪裁到[-1.0, 1.0]区间,然后以这个特征向量为基准值(平均值),在[-1.0, 1.0]这个变动区间内,按正态分布的规律随机取得新向量,计算新向量通过 GAN 生成的新图片与原图片之间的损失函数,然后用梯度下降的方法寻找使损失函数最小的最优解。该论文指出,他们可以使损失函数降低到 0,这样就找到了真实人脸对应的“相当精确”的特征码。

StyleGAN-encoder 就是基于这样的原理进行实现的。其工作流程如下:

使用原始的预训练 StyleGAN 生成器生成图像;

1. 预训练的 VGG16 网络用于将参考图像和生成的图像转换为高级特征空间;

2. 损失计算为要素空间中它们之间的差异;

3. 仅针对我们想要获得的潜在表示执行优化;

4. 优化完成后,根据需要转换潜矢量。例如,在潜在空间中找到一个“微笑方向”,在该方向上移动潜在矢量,然后使用生成器将其转换回图像。

该方法效果如下:

3.4 StyleGAN 2

StyleGAN 是目前最先进的高分辨率图像合成方法。它已经被证明可以在各种数据集上可靠地工作,除了写实的肖像,StyleGAN 还可以用来制作其他动物、汽车甚至房间,但是它最明显的缺陷是生成的图像有时包含斑点状的伪影。NVIDIA 的研究人员一年后发布了升级版 StyleGAN 2,它着重于修复特征伪影,并进一步提高了生成图像的质量。除此之外的进步还有:提出一种新的方法来代替逐渐生长、更加完美的牙齿、眼睛等细节、改进了混合模式、更平滑的插值(额外正则化)和更快的训练速度。方法演变如上图。

4 实验

利用 StyleGAN 的开源代码,和第三方训练的人脸生成模型,本文进行了两组实验,分别为黄种人人脸生成和动漫人脸生成。

黄种人人脸生成结果如下:

 

 动漫人脸生成结果如下:

由于本文使用的是第一代 StyleGAN,所以,在黄种人人脸生成结果中可以发现会有牙齿与面部中线对应不齐的问题,且在图像中会有残影。由于训练需要的计算资源过大,本文选择的是第三方的预训练模型。黄种人人脸生成的图片分辨率是 10248*1024,是目前该方法可支持的最高分辨率;动漫人脸生成的分辨率是 512*512。可以从结果中看出,黄种人生成中的人像与背景都有较高质量;动漫人脸的生成质量较差,会较大程度的出现生成画面混乱、不协调的情况,推测与训练中的样本数量较少有关。 由于第二代的计算量需求更大,以现有条件无法进行实验。官方给出的所需计算力(上:一代;下:二代)如下:

5 总结

本文是在图像处理课程的指导下进行的以学习为目的的实践项目。在学习理解了图像处理的基本知识后,本文将注意力集中在了目前很火的图像生成领域。以了解目前最优性能的 StyleGAN 为切入点,大致的学习了 GAN 网络的基础内容,并利用开源的 StyleGAN 模型进行了图像生成实验,在清楚代码逻辑后,将理论框架与代码结构进行了对应,并得以实现对开源模型中各个部分的调用。实验结果证明了其论文中所说的问题和优点。

从实验和理论总结部分看来,本项目达到了最初的设计目标,达到了一定的学习成果要求。在过程中无论是理论知识还是代码经验都有了一定程度的提升。

参考文献

[1] https://towardsdatascience.com/explained-a-style-based-generator-architecture-for-gans-generating-and-tuning-realistic-6cb2be0f431

[2] Karras, T., Laine, S., & Aila, T. (2019). A style-based generator architecture for generative adversarial networks. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 4401-4410).

[3] Karras, T., Laine, S., Aittala, M., Hellsten, J., Lehtinen, J., & Aila, T. (2020). Analyzing and improving the image quality of stylegan. In Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (pp. 8110-8119).

 

 

 

 

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
StyleGAN2的生成器是基于StyleGAN生成器进行改进的,其核心代码通常包括以下几个部分: 1. 网络结构定义:生成器通常由多个分层的Style Block和ToRGB层组成。Style Block是StyleGAN2中引入的新概念,用于将噪声向量和Style信息融合在一起,实现更加灵活的样本生成。ToRGB层用于将生成器输出的图像转换为RGB格式。在实现中,需要定义每个Block的结构和参数,并且按照一定的顺序连接在一起。 2. 噪声向量生成生成器需要接收一个噪声向量作为输入,通常可以使用高斯分布或均匀分布生成噪声向量。在实现中,需要定义噪声向量的维度和生成方式,并且将其输入到生成器中。 3. Style信息计算:StyleGAN2中的Style信息指的是每个Block的Style向量,用于控制生成器的输出样式。Style信息通过将噪声向量进行一系列的线性变换和归一化得到。在实现中,需要定义Style信息的计算方式和参数,并且将其与噪声向量一起输入到生成器中。 4. 生成器输出:生成器输出的结果是一张图像,通常是一个三维张量。在实现中,需要将生成器的最后一层输出转换为图像格式,并且进行必要的后处理,比如进行像素值归一化、裁剪等操作。 以下是一个简单的StyleGAN2生成器核心代码示例,供参考: ```python import torch import torch.nn as nn class StyleGAN2Generator(nn.Module): def __init__(self, latent_dim, channels): super(StyleGAN2Generator, self).__init__() self.latent_dim = latent_dim self.channels = channels self.mapping_network = MappingNetwork(self.latent_dim) self.synthesis_network = SynthesisNetwork(self.channels) def forward(self, input): style = self.mapping_network(input) image = self.synthesis_network(style) return image ``` 以上代码定义了一个StyleGAN2生成器的基本结构,包括了噪声向量生成Style信息计算和生成器输出等步骤。具体的网络结构和参数在MappingNetwork和SynthesisNetwork中定义,可以根据实际需求进行调整和改进。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值