GAN(Generative Adversarial Nets)

GAN(Generative Adversarial Nets)

引言

GAN由Ian J. Goodfellow等人提出,是Ian J. Goodfellow的代表作之一,他还出版了大家耳熟能详的花书(Deep Learning深度学习),GAN主要的思想是同时训练两个模型,生成模型G用于获取数据分布,判别模型D用于估计样本来自训练数据而不是G的概率。G的训练过程是最大化D犯错误的概率。这个过程对应于一个极小极大两人博弈。在任意函数G和D的空间中,存在唯一解,其中G恢复训练数据分布,并且D处处等于1/2。

用一个警察与小偷的故事来阐述:假设一个城市里有许多小偷,在这些小偷中,部分是技艺高超的偷窃高手,另一部分则是毫无技术的新手。警察开始进行对小偷的抓捕,其中一批“学艺不精”的小偷就被捉住了。这些小偷被抓住或许是因为识别他们毫无难度,警察不需要特殊本领,但是剩下的“偷窃高手”警察就很难抓捕。于是警察们开始继续训练自己的破案技术,开始抓住那些技艺高超的小偷。随着这些他们的落网,警察们也练就了特别的本事,他们能很快能从一群人中识别终逮捕嫌犯;随着警察们的水平大大提高,为了避免被捕,小偷们努力表现得不那么“可疑”,而魔高一尺、道高一丈,警察也在不断提高自己的水平,争取将小偷和无辜的普通群众区分开。随着警察和小偷之间的这种“交流”与“切磋”,小偷们都变得非常谨慎,他们有着极高的偷窃技巧,表现得跟普通群众一模一样,而警察们都练就了“火眼金睛”,一旦发现可疑人员,就能马上发现并及时控制——最终,我们同时得到了最强的小偷和最强的警察。其中,小偷就可以视作生成模型G,警察可以视作判别模型D,通过G和D的对抗,能够获得效果较好的生成模型(判别模型也是如此)参考链接

主要架构

根据引言,一个GAN主要包含两个基础模型:生成器(G)与判别器(D)。其中,生成器用于生成新数据,其生成数据的基础往往是一组噪音或者随机数,而判别器用于判断生成的数据和真实数据哪个才是真的。生成器执行无监督任务;而判别器执行有监督任务,用于二分类,其label是“假与真”(0与1)。
在这里插入图片描述

生成器的目标是生成尽量真实的数据(这也是我们对生成对抗网络的要求),最好能够以假乱真、让判别器判断不出来,因此生成器的学习目标是让判别器上的判断准确性越来越低;相反,判别器的目标是尽量判别出真伪,因此判别器的学习目标是让自己的判断准确性越来越高。

当生成器生成的数据越来越真时,判别器为维持住自己的准确性,就必须向判别能力越来越强的方向迭代。当判别器越来越强大时,生成器为了降低判别器的判断准确性,就必须生成越来越真的数据。在这个奇妙的关系中,判别器与生成器同时训练、相互内卷,对损失函数的影响此消彼长。参考链接

理论支撑

m i n G m a x D V ( D , G ) = E x ∼ p d a t a ( x ) [ l o g D ( x ) ] + E z ∼ p z ( z ) [ l o g ( 1 − D ( G ( z ) ) ) ] \underset{G}{min}\underset{D}{max}V(D,G) = {\mathbb E_{x \sim{p}_{data}(x)} [logD(x)]} + {\mathbb E _{z \sim{p}_{z}(z)}[{log(1-D({G(z)}))}]} GminDmaxV(D,G)=Expdata(x)[logD(x)]+Ezpz(z)[log(1D(G(z)))]
V V V是一个值函数(损失函数), x x x表示真实数据, p d a t a p_{data} pdata表示数据的真实分布, z z z是与真实数据相同分布的随机数据, G ( z ) G(z) G(z)是生成器中基于 z z z生成的数据, D ( x ) D(x) D(x)是判别器在真实数据 x x x上判断的结果, D ( G ( x ) ) D(G(x)) D(G(x))表示判别器在生成器生成的数据 G ( z ) G(z) G(z)上判断出的结果。

那么需要做的就是:

1. 对于判别器D来说,尽可能找到生成器生成的数据

2. 对于生成器G来说,尽可能让生成的数据接近真实数据,使得判别器D无法判别出来

上面表达式需要做的是,首先固定G,在D的层面使得值最大(即让判别器能够精确区分真实数据和生成数据),然后固定D,在G的层面使得值最小(即在判别器能够精确区分数据的情况下,让生成器能够生成更接近真实的数据,使得判别器无法区分),从而实现了D和G的对抗,如此可以找到最好的生成器(生成模型)。
在这里插入图片描述

图(a)中展示了生成器G、判别器D以及真实数据初始状态,此时真实数据与生成数据分布明显不同,判别器此时也只是初始状态;图(b)展示了判别器经过训练后能够进行区分真实数据和生成数据;图©展示了生成器经过训练后能够更加接近真实分布;图(d)展示了经过多次循环之后,生成器和判别器的状态,此时生成数据已经无限接近真实数据分布,同时判别器难以区分出真实数据和生成数据,导致判别答案始终为1/2。

算法

在这里插入图片描述
算法存在的一个问题是需要选择一个较好的k,在算法中要保证:不能一次性让判别器就能够准确的识别出所有生成数据,这会导致生成器没有办法继续提升,生成更加接近真实分布的数据,同时也不能让生成模型一下子生成非常接近真实分布的数据,这会导致判别器难以进行识别能力的提升。

公式分析

对于判别器D

判别器的作用是尽可能找出生成器生成的数据与真实数据分布之间的差异,这是一个二分类的问题,将G固定后,公式就变为:
m a x D V ( D , G ) = E x ∼ p d a t a ( x ) [ l o g D ( x ) ] + E z ∼ p z ( z ) [ l o g ( 1 − D ( G ( z ) ) ) ] \underset{D}{max}V(D,G) = {\mathbb E_{x \sim{p}_{data}(x)} [logD(x)]} + {\mathbb E _{z \sim{p}_{z}(z)}[{log(1-D({G(z)}))}]} DmaxV(D,G)=Expdata(x)[logD(x)]+Ezpz(z)[log(1D(G(z)))]
该公式等价于交叉熵,只不过交叉熵是取负的对数。这个函数的输入一部分是真实数据,分布为 p d a t a {p_{data}} pdata,一部分是生成器的数据(噪声数据),生成器接收的数据 z z z服从分布 p ( z ) p(z) p(z),输入 z z z经过生成器的计算生产的数据分布设为 p G ( x ) {p_{G}}(x) pG(x),这个函数要取得最大值,必然是对于真实数据 D ( x ) = 1 D(x)=1 D(x)=1,对于生成数据 D ( x ) = 0 D(x)=0 D(x)=0,这一步用于优化D,因此可以简写为
D G ∗ = m a x D V ( G , D ) D_G^* = \underset D {max}V(G,D) DG=DmaxV(G,D)
此时,这是D的一元函数,进行求导,得到
在这里插入图片描述
取导数为0,算最优点得到
在这里插入图片描述

对于生成器G

当且仅当 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x)时,有
D G ∗ = P d a t a ( x ) P G ( x ) + P d a t a ( x ) = 1 2 D_G^* = \frac{{{P_{data}}(x)}}{{{P_G}(x) + {P_{data}}(x)}} = \frac{1}{2} DG=PG(x)+Pdata(x)Pdata(x)=21
此时生成器无法判别数据是真实数据或者生成数据。
我们假设 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x),可以反向推出
V ( G , D G ∗ ) = ∫ x P d a t a ( x ) l o g 1 2 + P G ( x ) l o g ( 1 − 1 2 ) d x {V(G,D_G^*) = \int_x {{P_{data}}(x)log\frac{1}{2}}+ {P_G}(x)log(1 - \frac{1}{2})dx} V(G,DG)=xPdata(x)log21+PG(x)log(121)dx
⇔ V ( G , D G ∗ ) = − log ⁡ 2 ∫ x P G ( x ) d x − log ⁡ 2 ∫ x P d a t a ( x ) d x = − 2 log ⁡ 2 = − log ⁡ 4 \Leftrightarrow {V(G,D_G^*) = - \log 2\int\limits_x {{P_G}(x)} dx - \log 2\int\limits_x {{P_{data}}(x)} dx = - 2\log 2 = - \log 4} V(G,DG)=log2xPG(x)dxlog2xPdata(x)dx=2log2=log4
该值是全局最小值的候选,因为它只有在 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x)
的时候才出现。
对于任意一个G,将 D ∗ {D^*} D带入到 V ( G , D ) V(G,D) V(G,D)中:
在这里插入图片描述

结合KL散度得到:
= − 2 l o g 2 + K L ( P d a t a ( x ) ∣ ∣ P d a t a ( x ) + P G ( x ) 2 ) + K L ( P G ( x ) ∣ ∣ P d a t a ( x ) + P G ( x ) 2 ) { = - 2log2 + KL({P_{data}}(x)||\frac{{{P_{data}}(x) + {P_G}(x)}}{2}) + KL({P_G}(x)||\frac{{{P_{data}}(x) + {P_G}(x)}}{2})} =2log2+KL(Pdata(x)∣∣2Pdata(x)+PG(x))+KL(PG(x)∣∣2Pdata(x)+PG(x))
最后根据JS散度得到:
V ( G , D ) = − log ⁡ 4 + 2 ∗ J S D ( P d a t a ( x ) ∣ P G ( x ) ) V(G,D) = - \log 4 + 2*JSD({P_{data}}(x)|{P_G}(x)) V(G,D)=log4+2JSD(Pdata(x)PG(x))
根据他的属性:当 P G ( x ) = P d a t a ( x ) {P_G}(x) = {P_{data}}(x) PG(x)=Pdata(x)
时,
为0。综上所述,生成分布当前仅当等于真实数据分布式时,我们可以取得最优生成器。前后逻辑自洽。

注:
对于判别器D的优化:这是一个二分类,满足 y l o g q + ( 1 − y ) l o g ( 1 − q ) ylogq+(1-y)log(1-q) ylogq+(1y)log(1q),对于x,标签只会为1,因此只有log(D(x))这一项;对于g(z),其标签只会为0,因此只有log(1-D(G(z)))这一项,因此可以有损失函数:
l o s s = c r o s s E n t r o p y L o s s ( D ( x ) , 1 ) + c r o s s E n t r o p y L o s s ( D ( x ) , 0 ) loss = crossEntropyLoss(D(x),1)+crossEntropyLoss(D(x),0) loss=crossEntropyLoss(D(x),1)+crossEntropyLoss(D(x),0)
对于生成器G的优化:因为D(x)这一项,并不包含生成器的优化参数,因此在求梯度的时候D(x)这一项为0,因此只有log(1-D(G(z)))这一项,损失函数:
l o s s = c r o s s E n t r o p y L o s s ( D ( G ( z ) ) , 1 ) loss = crossEntropyLoss(D(G(z)),1) loss=crossEntropyLoss(D(G(z)),1)

代码

import argparse
import os
import numpy as np
import mathimport torchvision.transforms as transforms
from torchvision.utils import save_imagefrom torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variableimport torch.nn as nn
import torch.nn.functional as F
import torchos.makedirs("images", exist_ok=True)parser = argparse.ArgumentParser()
parser.add_argument("--n_epochs", type=int, default=50, help="number of epochs of training")
parser.add_argument("--batch_size", type=int, default=64, help="size of the batches")
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--n_cpu", type=int, default=8, help="number of cpu threads to use during batch generation")
parser.add_argument("--latent_dim", type=int, default=100, help="dimensionality of the latent space")
parser.add_argument("--img_size", type=int, default=28, help="size of each image dimension")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=400, help="interval betwen image samples")
#使用jupyter时需要传入list,
opt = parser.parse_args(args=[])
#opt = parser.parse_args()
#print(opt)#图像形状为:1*28*28,图像大小为784
img_shape = (opt.channels, opt.img_size, opt.img_size)#这里提前做了一下cuda的判断
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")#生成器G,用于进行数据的生成
class Generator(nn.Module):def __init__(self):super(Generator, self).__init__()#定义模型的中间块def block(in_feat, out_feat, normalize=True):layers = [nn.Linear(in_feat, out_feat)] #多个线性层的组合if normalize:layers.append(nn.BatchNorm1d(out_feat, 0.8)) #进行正则化layers.append(nn.LeakyReLU(0.2, inplace=True)) #激活函数return layers#多个模型块进行组合(MLP),输入维度的映射过程为:input_dim->128->256->512->1024->1*28*28self.model = nn.Sequential(*block(opt.latent_dim, 128, normalize=False),*block(128, 256),*block(256, 512),*block(512, 1024),nn.Linear(1024, int(np.prod(img_shape))),nn.Tanh() #Tanh取值范围为-1,1,即将输出映射到-1,1)def forward(self, z):img = self.model(z) #对噪声数据处理进行数据生成img = img.view(img.size(0), *img_shape) #生成数据为(batch_size,1,28,28)return img#判别器D,用于区分真实数据和生成数据
class Discriminator(nn.Module):def __init__(self):super(Discriminator, self).__init__()#维度映射过程:1*28*28->512->256->1,使用sigmoid进行二分类激活,映射为0,1之间的数self.model = nn.Sequential(nn.Linear(int(np.prod(img_shape)), 512),nn.LeakyReLU(0.2, inplace=True),nn.Linear(512, 256),nn.LeakyReLU(0.2, inplace=True),nn.Linear(256, 1),nn.Sigmoid(),)def forward(self, img):img_flat = img.view(img.size(0), -1) #将输入展平validity = self.model(img_flat) #进行判别return validity# Loss function
adversarial_loss = torch.nn.BCELoss() #损失函数使用BCE(二分类交叉熵)# Initialize generator and discriminator
generator = Generator()
discriminator = Discriminator()if cuda:generator.to(device)discriminator.to(device)adversarial_loss.to(device)# Configure data loader
os.makedirs("./data/mnist", exist_ok=True)
#数据集加载
dataloader = torch.utils.data.DataLoader(datasets.MNIST("./data/mnist",train=True,download=True,transform=transforms.Compose([transforms.Resize(opt.img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]),),batch_size=opt.batch_size,shuffle=True,
)# Optimizers
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))# ----------
#  Training
# ----------for epoch in range(opt.n_epochs):for i, (imgs, _) in enumerate(dataloader):# Adversarial ground truthsvalid = torch.tensor([[1.0]] * imgs.size(0), requires_grad=False).to(device) #定义真实数据标号为1fake = torch.tensor([[0.0]] * imgs.size(0), requires_grad=False).to(device)  #定义虚假数据标号为0# Configure inputreal_imgs = torch.tensor(imgs.type(torch.Tensor)).to(device)# -----------------#  Train Generator# -----------------optimizer_G.zero_grad()# Sample noise as generator inputz = torch.tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim)), dtype=torch.float32).to(device) #随机生成噪声# Generate a batch of imagesgen_imgs = generator(z) #生成数据# Loss measures generator's ability to fool the discriminatorg_loss = adversarial_loss(discriminator(gen_imgs), valid)g_loss.backward()optimizer_G.step()# ---------------------#  Train Discriminator# ---------------------optimizer_D.zero_grad()# Measure discriminator's ability to classify real from generated samplesreal_loss = adversarial_loss(discriminator(real_imgs), valid)fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)d_loss = (real_loss + fake_loss) / 2d_loss.backward()optimizer_D.step()print("[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"% (epoch, opt.n_epochs, i, len(dataloader), d_loss.item(), g_loss.item()))batches_done = epoch * len(dataloader) + iif batches_done % opt.sample_interval == 0:save_image(gen_imgs.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)

参考

参考:
文章1
文章2
文章3
代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/445570.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【重磅升级】基于大数据的股票量化分析与预测系统

温馨提示:文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 伴随全球经济一体化和我国经济的快速发展,中国股票市场对世界经济的影响力不断攀升,中国股市已成为全球第二大股票交易市场。在当今的金融市场中,股票价格的波动…

只需5步,就可以使用大语言模型(LLM)打造高效的应用

01 概述 随着人工智能技术的飞速发展,大型语言模型(LLM)正逐渐成为各个领域的得力助手。从最初的文本理解、生成到翻译,这些模型在自然语言处理(NLP)中的出色表现,让它们在聊天机器人、虚拟助…

微调大语言模型——超详细步骤

微调一个语言模型,其实就是在一个已经训练过的模型上,继续用新数据进行训练,帮助模型更好地理解和处理这个新的任务。可以把这个过程想象成教一个已经懂很多道理的人去解决新的问题。 这个过程可以分为五个简单的步骤: 加载预训练…

自定义注解和组件扫描在Spring Boot中动态注册Bean(一)

​ 博客主页: 南来_北往 系列专栏:Spring Boot实战 在Spring Boot中,自定义注解和组件扫描是两种强大的机制,它们允许开发者以声明性的方式动态注册Bean。这种方式不仅提高了代码的可读性和可维护性,还使得Spring Boot应用的…

动态规划lc

先找到规律,然后找边界情况;部分特殊情况分类讨论 *递归 70.爬楼梯 简单 提示 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入&#xff1a…

UCI-HAR数据集深度剖析:训练仿真与可视化解读

在本篇文章中,我们将深入探讨如何使用Python对UCI人类活动识别(HAR)数据集进行分割和预处理,以及运用模型网络CNN对数据集进行训练仿真和可视化解读。 一、UCI-HAR数据集分析及介绍 UCI-HAR数据集是一个公开的数据集&#xff0c…

【C++差分数组】P1672何时运输的饲料

本文涉及知识点 C差分数组 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 P1672何时运输的饲料 原文比较啰嗦&#xff0c;我简述一下&#xff1a; 第x天运来F1(1<F1<1e6)千克的饲料&#xff0c;第D&#xff08;1<2e3)天还剩F2&…

树莓派3b安装ubuntu18.04服务器系统server配置网线连接

下载ubuntu镜像网址 img镜像&#xff0c;即树莓派官方烧录器使用的镜像网址 ubuntu18.04-server&#xff1a;ARM/RaspberryPi - Ubuntu Wiki 其他版本&#xff1a;Index of /ubuntu/releases 下载后解压即可。 发现使用官方烧录器烧录配置时配置wifi无论如何都不能使用&am…

Charles安卓抓包环境配置

下载安装Charles 官网搜索然后直接下载就可以了 抓HTTP的包 HTTP代理 在Proxy->Proxy Settings里配置HTTP代理 手机上配置代理 进入WIFI&#xff0c;找到连接的网络&#xff0c;打开高级选项&#xff0c;里面有一个代理选项&#xff0c;将其改为手动&#xff0c;然后…

子网掩码、网络地址、广播地址、子网划分及计算

1. IPV4地址分类及组成 IP地址网络地址主机地址&#xff0c;&#xff08;又称&#xff1a;主机号和网络号&#xff09; 由上图可见网络号和主机号之和是32&#xff0c;而且此多彼少。 例&#xff1a;IP地址为192.168.2.131&#xff0c;转换成二进制1111 1111.1010 1000.0000 00…

小程序知识付费的优势 知识付费服务 知识付费平台 知识付费方法

在信息爆炸的时代&#xff0c;知识如同繁星点点&#xff0c;璀璨而散落。如何在这片知识的海洋中精准捕捞&#xff0c;成为现代人追求自我提升的迫切需求。小程序知识付费&#xff0c;正是这样一座桥梁&#xff0c;它以独特的优势&#xff0c;让智慧触手可及&#xff0c;轻触未…

【宝可梦】游戏

pokemmo https://pokemmo.com/zh/ 写在最后&#xff1a;若本文章对您有帮助&#xff0c;请点个赞啦 ٩(๑•̀ω•́๑)۶

【Java】 —— 数据结构与集合源码:Vector、LinkedList在JDK8中的源码剖析

目录 7.2.4 Vector部分源码分析 7.3 链表LinkedList 7.3.1 链表与动态数组的区别 7.3.2 LinkedList源码分析 启示与开发建议 7.2.4 Vector部分源码分析 jdk1.8.0_271中&#xff1a; //属性 protected Object[] elementData; protected int elementCount;//构造器 public …

数据安全防线:移动应用等保测评在个人信息保护中的作用“

在数字化浪潮席卷全球的当下&#xff0c;移动应用&#xff08;App&#xff09;已成为人们日常生活中不可或缺的一部分。然而&#xff0c;随之而来的个人信息泄露事件频发&#xff0c;引发了社会对数据安全和个人隐私保护的广泛关注。在此背景下&#xff0c;等保测评作为一项重要…

黑马程序员C++提高编程学习笔记

黑马程序员C提高编程 提高阶段主要针对泛型编程和STL技术 文章目录 黑马程序员C提高编程一、模板1.1 函数模板1.1.1 函数模板基础知识 案例一&#xff1a; 数组排序1.2.1 普通函数与函数模板1.2.2 函数模板的局限性 1.2 类模板1.2.1 类模板的基础知识1.2.2 类模板与函数模板1.…

【Postman】接口测试工具使用

干就完啦 Postman发送get请求案例1&#xff1a; Postman发送post请求案例2 Postman发送其他请求Postman测试实战 学习目标&#xff1a;能够使用Postman发送get/post/put/delete请求并获取响应结果 Postman发送get请求 首先postman是一款接口调试工具&#xff0c;支持win&…

【学术会议投稿链接】React前端框架:构建现代Web应用的强大工具

【即将截稿】第五届经济管理与大数据应用国际学术会议&#xff08;ICEMBDA 2024&#xff09;_艾思科蓝_学术一站式服务平台 更多学术会议请看&#xff1a;https://ais.cn/u/nuyAF3 目录 引言 一、React简介 二、React的核心概念 1. 组件化 2. 虚拟DOM&#xff08;Virtua…

深度对比:IPguard与Ping32在企业网络管理中的应用

随着网络安全形势日益严峻&#xff0c;企业在选择网络管理工具时需慎之又慎。IPguard与Ping32是目前市场上两款颇具代表性的产品&#xff0c;它们在功能、性能以及应用场景上各有优势。本文将对这两款产品进行深度对比&#xff0c;以帮助企业找到最合适的解决方案。 IPguard以其…

线性回归详解

线性回归 线性回归介绍 学习目标&#xff1a; 1.理解线性回归是什么&#xff1f; 2.知道一元线性回归和多元线性回归的区别 3.知道线性回归的应用场景 【理解】举个栗子 假若有了身高和体重数据&#xff0c;来了播仔的身高&#xff0c;你能预测播仔体重吗? 这是一个回归…

React复习

文章目录 常用的HooksuseStateuseReduceruseRefuseContextuseMemouseCallbackuseEffect 组件通信Props&#xff08;属性&#xff09;Ref&#xff08;引用&#xff09;Context&#xff08;上下文&#xff09;State&#xff08;状态&#xff09;回调函数Event Bus&#xff08;事件…