【通俗易懂说模型】生成对抗网络·GAN

发布于:2025-04-07 ⋅ 阅读:(17) ⋅ 点赞:(0)

🌈 个人主页:十二月的猫-CSDN博客
🔥 系列专栏: 🏀《深度学习理论直觉三十讲》_十二月的猫的博客-CSDN博客

💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光

目录

1. 前言

2. 深度学习两大法宝

2.1 模型学习三步骤

2.2 代码编写八流程

3. 生成对抗网络

3.1 从直觉看生成对抗网络(GAN)

3.2 生成对抗网络模型

3.3 生成对抗网络损失函数

3.4 生成对抗网络的训练 

3.5 生成对抗网络的使用

4. 损失函数的本质 

4.1 KL散度和JS散度

4.2 GAN生成器的最优值

4.3 损失函数代表什么

5. 生成对抗网络存在的问题

5.1 难以实现纳什均衡

5.2 消失梯度

5.3 模式崩溃

6. 总结


1. 前言

        长期看猫猫文章的友友一定对 卷积神经网络、循环神经网络、注意力机制 等经典深度学习模型有了较为深入的了解。因为反向传播、梯度下降等技术的发展使得深度神经网络的训练成为可能,因此涌现出了前面提出的很多经典模型。同时由于ResNet、Dropout等网络模型结构的出现,使得深度神经网络在模型实现上也同样成为可能(缓解深层模型带来的梯度消失和梯度爆炸问题)。从某个角度来说,深度学习已经得到了长足的发展,但是仅仅是在判别式模型上

        或许你是第一次听到判别式模型这个词语,但是不要紧,听猫猫细细道来。判别式模型顾名思义就是做判断的模型:1. 也就是你给她一张图片,它判断是什么动物;2. 你给她一张图片,它判断动物在哪里;3. 你给她一句话,它挖掘话中的含义并理解。

        上面的三个例子分别对应:图片分类、目标检测、自然语言处理。也就是说判别式模型的特点是判别式模型并不生成东西,仅仅尝试理解东西并作出自己的判断。与之相对的是生成式模型,生成式模型并不对你给它的东西进行分析理解然后做出自己的判断,相反它们是直接根据你的需要生成新的东西

判别式模型:理解事物并做出自己的判断。

生成式模型:根据要求理解要求并生成新事物。

        但是生成式模型的发展一直非常慢,毕竟无中生有肯定比理解要困难。直到生成对抗网络GAN的横空出世,这一领域才焕发生机。生成式对抗网络自lan Goodfellow等人提出后,就越来越
受到学术界和工业界的重视。而随着GAN在理论与模型上的高速发展,它在计算机视觉、自然语言处理、人机交互等领域有着越来越深入的应用,并不断向着其它领域继续延伸。

2. 深度学习两大法宝

        授人以鱼不如授人以渔,下面介绍的两大法宝将贯彻您学习深度学习的全过程。猫猫总结了三步骤对应模型学习,八流程对应代码编写。只要完全掌握两大法宝,我相信你的学习将不再是无头苍蝇,将彻底明白如何学习深度学习。我的文章或者其他作者的文章/书可能并不是按照这两个方法去展开的,但是猫猫建议你在学习后都用这两大法宝自己去总结一下,一定会豁然开朗的

2.1 模型学习三步骤

想要快速理解一个模型,可以采用三步骤法:

  1. 模型结构与损失函数
  2. 模型如何训练(反向传播)
  3. 模型如何使用(前向传播)

        也许经常关注猫猫前面文章的猫友们会问,那为什么猫猫你自己的文章不采用这三步骤去介绍模型呢?

        其实一开始猫猫也打算直接在【通俗易懂说模型】系列中直接采用三步骤来讲解模型,带大家入门。三步骤法绝对是逻辑线最清晰,学习速度最快的。但是存在的问题就是猫猫发现很多初学者对一些基础的知识并不了解,直接采用三步骤法会导致大家的理解并不够深刻

        因此最后决定在【通俗易懂说模型】系列中采用灌输知识为主,以扩大初学者的知识量为目的。同时也是真实考虑到初学者什么都不懂的情况下去做全方位的介绍,真的是把肉吃进去,嚼成肉泥再给大家。大家参考下上面三篇文章的结构,可以发现很多时候猫猫是讲到一些点就会照顾到初学者,去额外做一些拓展(比如RNN为什么存在梯度消失/梯度爆炸问题;CNN比起直接用全连接层提取特征存在的优势等等)。

        关于模型学习三步骤法,将在后续【10分钟学模型】系列中带大家展开,该系列重点在于快速学习,便于大家在有一定基础后,用来整理、比较、复习各模型的结构、框架。不过【通俗易懂说模型】除去特别展开部分,主体也将按照三步骤的脉络展开。

2.2 代码编写八流程

想要快速根据一个模型写出其对应的PyTorch代码,可以采用八流程法:

  1. 准备训练数据集(定义使用设备、数据集长度)
  2. 加载训练数据集
  3. 建立模型(模型包括网络模型和tensorboard模型)
  4. 确定损失函数
  5. 确定优化器
  6. 设置训练网络的一些参数
  7. 训练函数(每一轮包括 训练步骤 和 测试步骤)
  8. 主函数(调用训练函数,并做一些可视化的交互性输出操作)

        专栏《深度学习理论直觉三十讲》中的【玩转深度学习】系列的项目都会按照上面的八流程法去编写代码。

        特别说一下上面的八流程不是每一个流程每次都必须要涉及,有些时候有些流程是可以省略或者和前后步骤合并处理。例如如果数据集已经在本地下载好了,那么就不需要第一步·准备数据集,直接加载数据集即可;如果网络非常简单,那么训练网络的参数也会毕竟简单,因此也就不需要特意设置,可以在主函数中直接写上(但是在早期学习中还是建议按照八流程去写)。

3. 生成对抗网络

        前面关于深度学习两大法宝,可能有的友友会觉得猫猫在唠叨了吧。仅仅是最近有感总结了一下然后将放到下面这篇文章中,不会影响猫友们学习深度学习知识,同时也给有需要的猫友们回头去看,废话少说,马上开始今天的核心内容。【人工智能入门必看,避免走弯路】十二月的猫切身经历,从理论到实践的全指南-CSDN博客

        生成对抗网络 (GAN) 在许多生成任务中表现出色,可以复制现实世界的丰富内容,例如图像、人类语言和音乐。它受到博弈论的启发:两个模型(生成器和评论家)相互竞争,同时使彼此变得更强大。然而,训练 GAN 模型相当具有挑战性,因为人们面临着训练不稳定或无法收敛等问题。在这里我想从直觉角度先解释一下生成对抗网络框架背后的原理,然后再从数学角度去看生成对抗网络框架,以及为什么它很难训练。 

3.1 从直觉看生成对抗网络(GAN)

         GAN最核心的创新点就是引入零和博弈思想二人零和博弈思想(two-player game)博弈双方的利益之和是一个常数,比如两个人手腕,假设总的空间是一定的,你的力气大一点,那你就得到的空间多一点,相应的我的空间就少一点,相反我力气大我就得到的多一点,但有一点是确定的就是,我两的总空间是一定的,这就是二人博弈,但是呢总利益是一定的。其实就像石头剪刀布,只要有一个人赢就会有一个人输。

        GAN网络中存在两个部分——生成器 和 判别器。他们两个就处于零和博弈的两方。生成器不断提高自己生成图片的水平,让判别器看不出来自己是生成的图片(以为是真实的猫猫狗狗图片);判别器不断提高自己判断图片的水平,能够分辨图片是真实的还是生成的。因此GAN模型也叫对抗模型,两个部分在彼此对抗。

注意:生成器只要伪装自己让判别器看不出来就行了,所以有时候生成的图片在我们人看来都是噪声。

        现在再来看前面提到的零和博弈的一个特点:总利益是一定的。也就是说两个部分来回拉扯去给自己争夺更多的利益,这也就会导致训练过程非常的慢,很容易出现震荡现象,难以收敛。简单点说,两个国家在博弈,如果是中国和美国博弈,由于两者都很强,谁也奈何不了谁,此时可能这10年你厉害一点占上风,后10年我厉害一点占上风,谁也不会服谁,此时就能促进两国进步;但是如果是中国和一个小国家博弈,由于中国很强,那么一下子就打赢了小国家,模型马上就收敛了,但是并不能促进小国家进步,相反小国家还会因此无法发展(当然,我们崇尚和平,不打仗🥰)。

3.2 生成对抗网络模型

小问题:之前一直有看猫猫文章或者看其他书入门深度学习各类模型的猫友们,我不知道大家学习模型的时候,会不会思考模型为什么那么设计,损失函数又为什么那么设计?其实猫猫一开也不会深入思考这个问题,但是如果未来你想从事科研或者成为高级的人工智能算法工程师,我觉得大家是需要去思考的,而不是直接接受。

GAN模型启发于:博弈。引入博弈思想,从而让生成模型、判断模型彼此激励提高模型效果。 


 GAN 由两个模型组成:

  • 判别器D估计给定样本来自真实数据集的概率。它充当评论家的角色,并经过优化以区分假样本和真实样本。
  • 给定噪声变量输入Z(必须要有潜在的输出多样性)。生成器G 输出合成样本。它经过训练可以捕捉真实数据分布,以便其生成样本尽可能真实,换句话说,可以欺骗鉴别器提供高概率。

        这两个模型在训练过程中相互竞争:生成器 G 努力欺骗鉴别器,而评论模型 D 则努力不被欺骗。两个模型之间这种有趣的零和博弈激励着两者改进其功能。最终GAN将会得到一个效果不错的生成模型,能够生成你所想要得到的图,这个图可以伪装成真实的图。如下图:

3.3 生成对抗网络损失函数

小问题:之前一直有看猫猫文章或者看其他书入门深度学习各类模型的猫友们,我不知道大家学习模型的时候,会不会思考模型为什么那么设计,损失函数又为什么那么设计?其实猫猫一开也不会深入思考这个问题,但是如果未来你想从事科研或者成为高级的人工智能算法工程师,我觉得大家是需要去思考的,而不是直接接受。

        损失函数设计:两个部分(生成模型、判别模型)都达到自己所要达到得最好效果(如果是判别器就要判断结果和真实结果无限接近;如果是生成器就要生成结果和真实结果在判别器中判断结果无限接近)。结果无限接近可以用最大化交叉熵来体现结果无限远离可以用取反操作后最大化交叉熵来体现。具体见下面的分析:


定义一些变量如下:

        判断模型方面,达到最好效果包括两个方面:1、判断真实数据很准;2、判断假数据很准。我们希望通过最大化 Ex∼pr(x)[log⁡D(x)] 来确保鉴别器 D 对真实数据的决策是准确的。同时,给定一个假样本 G(z),z∼pz(z) ,预计鉴别器将通过最小化 Ez∼pz(z)[log⁡(D(G(z)))] (取反就是最大化 Ez∼pz(z)[log⁡(1−D(G(z)))] 输出)来确保鉴别器D对假数据的判断是准确的(也就是每次都识别出你这是假样本,所以给出和噪声输入相反的数据分布z。例如噪声输入是正确1,鉴别器识别出是假数据给出判断错误0)

        生成模型方面,达到最好效果包括:1、生成器的结果能够欺骗鉴别器。数学角度也就是噪声输入的数据分布和鉴别器判断的数据分布尽可能接近。也就是最大化 Ez∼pz(z)[log⁡(D(G(z)))] (取反就是最小化 Ez∼pz(z)[log⁡(1−D(G(z)))] );2、同时保证判别模型

        当将这两个方面结合在一起时, D 和 G 正在进行一场极小极大博弈,其中我们应该优化以下损失函数:

\begin{aligned} \min _{G} \max _{D} L(D, G) & =\mathbb{E}_{x \sim p_{r}(x)}[\log D(x)]+\mathbb{E}_{z \sim p_{z}(z)}[\log (1-D(G(z)))] \\ & =\mathbb{E}_{z \sim p_{z}(z)}[\log (1-D(G(z)))] \end{aligned}

        当然,我们可以认为G(x)会将噪声的数据分布转化为生成器的数据分布,然后将损失函数改为(你一定会好奇,为什么多了一个部分,不要着急往下看):

\begin{aligned}\min_{G}\max_{D}L(D,G)&=\mathbb{E}_{x\sim p_r(x)}[\log D(x)]+\mathbb{E}_{z\sim p_z(z)}[\log(1-D(G(z)))]\\&=\mathbb{E}_{x\sim p_r(x)}[\log D(x)]+\mathbb{E}_{x\sim p_g(x)}[\log(1-D(x)]\end{aligned}

必看!!!

        这里有一个点困扰了我很久!为什么优化生成器模型的损失函数中有下面这个生成器的部分\mathbb{E}_{x\sim p_r(x)}[\log D(x)]?前面的分析中,我们分析的生成器损失函数中不需要这个部分呀?甚至加上这个部分梯度下降时让判别器对真实数据判断更加不准确,这对我们模型的训练没有任何好处。查阅了网上几乎所有写GAN的文章,要么根本不解释损失函数为什么是这个,要么只解释前面三个,并不解释这一部分。最后猫猫苦思冥想得到了一个可能性的解释,希望能帮助大家,也希望大家在评论区讨论。

        按理来说,只需要最小化Ez∼pz(z)[log⁡(1−D(G(z)))]即可,不应该有前面这个部分。甚至最小化前面这个部分\mathbb{E}_{x\sim p_r(x)}[\log D(x)]是错误的,因为会让判别器和真实数据分布越来越远,这不是我们要的训练效果。接下来我的解释将从两个角度:1、为什么加上这个部分不是错误的;2、加上这个部分的作用。

        先回答第一个问题:为什么不是错误的?问题本质在于:GAN交替训练流程:

  • 固定生成器G,更新D
  • 固定判别器D,更新G

        也就是说在优化生成器G的时候,判别器D的参数必须要冻结,此时前面第一项部分\mathbb{E}_{x\sim p_r(x)}[\log D(x)]是一个常数,并不参与梯度下降!!因此不会对生成器G的更新不会影响到判别器D,因此可以说加上这个部分不会是错误的。

        第二个问题:加上这个部分的作用。这个问题更有意思,可以说猫翻遍资料都没有找到有人分析这个的。首先通过前面第一个问题的分析,我们可以知道前面第一项是一个常数。这个常数和判别器D分布与真实数据分布相似度有关,如果判别器训练的很好(即与真实数据分布相似)那么这个数就会非常大,反之则比较小。因此我们可以知道这个部分是用来动态调节判别器D和生成器G的训练速度的。减缓判别器D训练过快导致生成器G来不及训练,出现模式坍塌等现象(具体可以看下面的 5.生成对抗网络存在的问题)。

3.4 生成对抗网络的训练 

        从前面的损失函数中,我们也看到了损失函数有两个,所以训练的步骤应该也有两个,一个训练生成器,一个训练判别器。由于生成模型与对抗模型是完全独立的两个模型,他们之间没有什么联系,因此训练采用的是单独交替迭代训练

训练判别器:

  1. 在噪声数据分布中随机采样,输入生成模型,得到一组假数据,记为D(Z)
  2. 在真实数据分布中随机采样,作为真实数据,记做x;
  3. 将前两步中产生的数据作为输入,分别得到GAN模型的输出(输出也就是属于真实数据的概率,real为1,false为0)。
  4. 根据前面两个输入输出,使用判别模型的损失函数,然后反向传播+梯度下降训练判别器参数,最大化这个损失函数。

训练生成器:

  1. 在噪声数据分布中随机采样,输入生成模型,得到一组假数据,记为D(Z)。
  2. 将假数据输入,得到GAN模型的输出。
  3. 根据这一个输入输出,使用生成模型的损失函数,然后反向传播+梯度下降训练生成器参数,最小化这个损失函数。

具体训练流程图如下:

3.5 生成对抗网络的使用

在训练结束后,我们接下来看看如何使用GAN模型(前向传播),具体步骤如下:

  1. 加载训练好的GAN中的生成器。
  2. 输入噪声(和训练分布一致),前向传播生成新样本
  3. 得到新样本

画出流程图如下:

注意:训练时候用的是什么数据,生成的新样本也就是训练的数据。比如若用狗的图片训练,且训练充分:生成器会学习到狗的特征(如毛发、耳朵形状等);生成的新样本应为不同姿态/品种的狗(而非猫或其他类别)。

4. 损失函数的本质 

        这一部分的内容属于是拓展知识,主要用来加深对GAN模型损失函数的理解。如果不想很深入了解GAN模型,可以跳过,因为涉及数学知识比较多,可能难以理解(猫猫看CSDN上其他文章都不涉及这一部分,但是我认为为了简单而降低模型本身的难度不是可取的)。

4.1 KL散度和JS散度

        在我们开始仔细研究 GAN 损失函数之前,让我们首先回顾一下量化两个概率分布之间相似度的两个指标。

  • KL(Kullback-Leibler)散度度量一个概率分布 p 与第二个预期概率分布 q 的偏离程度。

D_{K L}(p \| q)=\int_{x} p(x) \log \frac{p(x)}{q(x)} d x

        当 p(x) == q(x) 时, DKL 在任何位置都达到最小零。 根据公式可以明显看出,KL 散度是不对称的。在 p(x) 接近于零,但 q(x) 明显不为零的情况下, q 的影响将被忽略。当我们只想衡量两个同等重要的分布之间的相似性时,这可能会导致错误的结果。

  •  Jensen-Shannon 散度是衡量两个概率分布相似度的另一种方法,其边界为 [0,1] 。JS 散度是对称的,而且更平滑。

D_{JS}(p\|q)=\frac{1}{2}D_{KL}(p\|\frac{p+q}{2})+\frac{1}{2}D_{KL}(q\|\frac{p+q}{2})

        一些人认为(Huszar,2015),GAN 取得巨大成功的原因之一是将损失函数从传统最大似然方法中的非对称 KL 散度转换为对称 JS 散度。我们将在下一部分中进一步讨论这一点。

4.2 GAN生成器的最优值

        通过前面的学习,我们知道GAN生成对抗网络训练生成器G质量的好坏和判别器D的质量有直接的关系(D质量好则指导的G质量也就好)。由于两个模块D和G是相互独立的,我们现在先让D达到最佳状态,然后再去计算生成器G的最优值。现在我们有了一个定义明确的损失函数。我们首先来检查一下 D 的最佳值是多少。

L(G,D)=\int_{x}\left(p_{r}(x)\log(D(x))+p_{g}(x)\log(1-D(x))\right)dx

        由于我们感兴趣的是,最大化 L(G,D) 的最佳值(看看当D(X)等于多少时前面的判别器的损失函数能够最大化)是多少,因此我们将其标记为

\tilde{x}=D(x),A=p_r(x),B=p_g(x)

然后积分里面的内容(我们可以放心地忽略积分,因为 x 是对所有可能的值进行采样的):

 \begin{gathered}f(\tilde{x})=Alog\tilde{x}+Blog(1-\tilde{x})\\\frac{df(\tilde{x})}{d\tilde{x}}=A\frac{1}{ln10}\frac{1}{\tilde{x}}-B\frac{1}{ln10}\frac{1}{1-\tilde{x}}\\=\frac{1}{ln10}(\frac{A}{\tilde{x}}-\frac{B}{1-\tilde{x}})\\=\frac{1}{ln10}\frac{A-(A+B)\tilde{x}}{\tilde{x}(1-\tilde{x})}\end{gathered}

设置\frac{df(\bar{x})}{d\bar{x}}=0 ,我们得到鉴别器的最佳值:

D^{*}(x)=\tilde{x}^{*}=\frac{A}{A+B}=\frac{p_{r}(x)}{p_{r}(x)+p_{g}(x)}\in[0,1]

        也就是说只有D(x)取上面的值,才可以满足最大化判别器的损失函数。代入D(x)值让f(x)取最大时p_g(x)=p_r(x),此时D(x)=1/2。当 G 和 D 都处于最佳值时,我们有 p_g(x)=p_r(x) 和 D(x)=1/2 ,损失函数变为:

\begin{aligned}L(G,D^{*})&=\int_{x}\left(p_{r}(x)\log(D^{*}(x))+p_{g}(x)\log(1-D^{*}(x))\right)dx\\&=\log\frac{1}{2}\int_xp_r(x)dx+\log\frac{1}{2}\int_xp_g(x)dx\\&=-2\log2\end{aligned}

4.3 损失函数代表什么

根据前一小节中列出的公式, pr 和 pg 之间的 JS 散度可以计算为:

\begin{aligned}D_{JS}(p_r\|p_g)=&\frac{1}{2}D_{KL}(p_r||\frac{p_r+p_g}{2})+\frac{1}{2}D_{KL}(p_g||\frac{p_r+p_g}{2})\\=&\frac{1}{2}{\left(\log2+\int_xp_r(x)\log\frac{p_r(x)}{p_r+p_g(x)}dx\right)}+\\&\frac{1}{2}{\left(\log2+\int_xp_g(x)\log\frac{p_g(x)}{p_r+p_g(x)}dx\right)}\\=&\frac{1}{2}\left(\log4+L(G,D^*)\right)\end{aligned}

Thus,  因此, 

L(G,D^*)=2D_{JS}(p_r\|p_g)-2\log2

        由此可以得出当判别器最优时,G的损失函数本质上是由 JS 散度量化生成数据分布 pg 与真实样本分布 pr 之间的相似度决定的。因此通过第一部分我们知道,这也就是GAN模型的生成器效果比起其他模型生成器更好的潜在原因(JS散度比起KL散度有诸多好处。。。。)

5. 生成对抗网络存在的问题

5.1 难以实现纳什均衡

        Salimans 等人 (2016) 讨论了 GAN 基于梯度下降的训练程序的问题。同时训练两个模型以找到双人非合作游戏的纳什均衡。然而,每个模型都会独立更新其成本,而不考虑游戏中的另一个玩家。同时更新两个模型的梯度不能保证收敛。

        让我们看一个简单的例子来更好地理解为什么在非合作游戏中很难找到纳什均衡。假设一个玩家控制 x 以最小化 f1(x)=xy ,而与此同时,另一个玩家不断更新 y 以最小化 f2(y)=−xy 。

        因为 \frac{\partial f_1}{\partial x}=y和 \frac{\partial f_2}{\partial y}=-x ,我们在一次迭代中同时用 x−η⋅y 更新 x ,用 y+η⋅x 更新 y ,其中 η 是学习率。一旦 x 和 y 具有不同的符号,则每次后续梯度更新都会引起巨大的振荡,并且不稳定性会随着时间的推移而恶化,如下图所示。

5.2 消失梯度

        当判别器完美时,我们可以保证 D(x)=1,∀x∈pr 和 D(x)=0,∀x∈pg 。因此,损失函数 L 降至零,最终在学习迭代期间没有梯度来更新损失。下图演示了一个实验,当判别器变得更好时,梯度很快消失。

因此,训练 GAN 面临一个困境:

  • 如果鉴别器表现不佳,生成器就没有准确的反馈,损失函数就不能代表现实。
  • 如果鉴别器表现出色,损失函数的梯度就会下降到接近零,学习就会变得非常慢甚至陷入停滞。

理解:我们的目标是得到一个好的生成器,但是这会受到鉴别器质量的影响。举前面直观理解中的例子就是两个国家实力均衡时,训练会很慢,但是生成器(中国)能真正学习到东西,促进中国发展;如果两国实力不均衡,生成器(中国)并不能好好发展,会被强国夺取资源,或者根本学习不到东西。

5.3 模式崩溃

        在训练过程中,生成器可能会崩溃到始终产生相同输出的设置。这是 GAN 的常见失败案例,通常称为模式崩溃。即使生成器可能能够欺骗相应的鉴别器,它也无法学会表示复杂的现实世界数据分布,并陷入多样性极低的小空间中。

出现模式崩溃可能的原因:

  1. 对抗失衡:生成器与判别器的博弈失败。例如判别器过强,导致梯度消失生成器根本没有训练好;生成器过强,找到了判别器的绝对漏洞,然后也就不需要去训练自己了。
  2. 梯度下降的局部收敛:例如传统优化器(如Adam)容易陷入局部最优解,生成器收敛到少数模式后不再更新,即使存在更好的全局解。
  3. 噪声输入的敏感性:例如噪声维度不足,若隐空间维度远小于数据复杂度,生成多样性受限。

6. 总结

        本篇文章带大家深入了解了GAN生成对抗网络,同时也作为猫猫【通俗易懂学模型】系列的第一篇生成式模型文章,后续还将更新DiffusionModel等单下热门的生成式模型。欢迎大家继续支持猫猫呀!!

 【如果想学习更多深度学习文章,可以订阅一下热门专栏】

如果想要学习更多pyTorch/python编程的知识,大家可以点个关注并订阅,持续学习、天天进步你的点赞就是我更新的动力,如果觉得对你有帮助,辛苦友友点个赞,收个藏呀~~~


网站公告

今日签到

点亮在社区的每一天
去签到