GAN系列之 StarGAN

StarGAN: Unified Generative Adversarial Networks for Multi-Domain Image-to-Image Translation

Authors

Yunjey Choi, Minje Choi, Munyoung Kim, Jung-Woo Ha, Sunghun Kim, Jaegul Choo

Abstract

Recent studies have shown remarkable success in image-to-image translation for two domains. However, existing approaches have limited scalability and robustness in handling more than two domains, since different models should be built independently for every pair of image domains. To address this limitation, we propose StarGAN, a novel and scalable approach that can perform image-to-image translations for multiple domains using only a single model. Such a unified model architecture of StarGAN allows simultaneous training of multiple datasets with different domains within a single network. This leads to StarGAN’s superior quality of translated images compared to existing models as well as the novel capability of flexibly translating an input image to any desired target domain. We empirically demonstrate the effectiveness of our approach on a facial attribute transfer and a facial expression synthesis tasks.

Pix2Pix模型解决了有Pair对数据的图像翻译问题;CycleGAN解决了Unpaired数据下的图像翻译问题。但无论是Pix2Pix还是CycleGAN,都是解决了一对一的问题,即一个领域到另一个领域的转换。当有很多领域要转换了,对于每一个领域转换,都需要重新训练一个模型去解决。这样的行为太低效了。本文所介绍的StarGAN就是将多领域转换用统一框架实现的算法。

下图是StarGAN的效果,在同一种模型下,可以做多个图像翻译任务,比如更换头发颜色,更换表情,更换年龄等。

StarGAN,顾名思义,就是星形网络结构,在StarGAN中,生成网络G被实现成星形。

1.CycleGAN 不能解决多领域迁移的问题。 只能两个领域的互相转化A->B,B-A。 但是实际场景中,我们可能遇到 多个数据集,或者多种属性的互相转化的要求。这样的话我们就需要O(n^2)的G model。(如下图)

2.有些属性(如人的表情),如果只取其中的两个属性(笑和不笑),那么就无法利用上其他训练数据(比如生气/恐惧等表情数据)。

1.作者提出了StarGAN 来处理多个domain之间互相generate图像 的问题。只用一个generator网络。

假如想实现四个域内图像风格的相互转换,要实现这个目标,通过cycleGAN需要创建12个生成器(如图a)。而starGAN的直观构造如图b,只需要一个生成器即可。

2. G的输入除了图片,还有domain的label,对应的把生成图片变到指定的domain。

starGAN的提出是为了解决多数据集在多域间图像转换的问题,starGAN可以接受多个不同域的训练数据,并且只需要训练一个生成器,就可以拟合所有可用域中的数据。

StarGAN的大致训练流程

i)如图a,训练判别器,将 real_img 和 fake_img 分别传递给判别器,判别器会判别图像的真假,同时它还会判别该图像来自哪个域(只对real_img 的label做判别)。
ii)如图b,训练生成器,与CGAN类似,这里除了输入图像外,还要输入该图像想转换的目标域,这个目标域类似于约束条件,它要求生成器尽可能去生成该目标域中的图像。
iii)如图c,表示循环一致性的过程,如果只是单纯的使用条件去控制生成器生成,那么生成器就会生成满足条件但可能与输入图像无关的数据,为了避免这种情况,便使用循环一致性的思想,即将生成的图像加上输入图像所在的域作为生成器的输入,希望获得的输出与原输入图像越接近越好。
iiii)如图d,表示训练生成器,即将生成器生成的图片交给判别器,让判别器判别图像的真假以及图像所在的域是否正确。

损失函数:

Adversarial loss 为 conditional gan常用的。(实际替换为WGAN的loss)

Reconstruction loss 为L1 Loss (和Cyclegan一样)

Domain classification loss(属性分类)就是传统分类log NLLloss。

类别损失,该损失被分成两个,训练D的时候,使用真实图像在原始领域进行,训练G的时候,使用生成的图像在目标领域进行。

多数据集训练
在多数据集下训练StarGAN存在一个问题,那就是数据集之间的类别可能是不相交的,但内容可能是相交的。比如CelebA数据集合RaFD数据集,前者拥有很多肤色,年龄之类的类别。而后者拥有的是表情的类别。但前者的图像很多也是有表情的,这就导致前一类的图像在后一类的标记是不可知的。

为了解决这个问题,在模型输入中加入了Mask,即如果来源于数据集B,那么将数据集A中的标记全部设为0.

GAN系列之 CycleGAN

摘自 https://zhuanlan.zhihu.com/p/306442363

pixtopix需要一对一,一个image对应一个image,训练集的两组图片一一对应才能训练

CycleGAN的介绍

1.CycleGAN的原理

CycleGAN,即循环生成对抗网络,出自发表于 ICCV17 的论文《Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks》,和它的兄长Pix2Pix(均为朱大神作品)一样,用于图像风格迁移任务。以前的GAN都是单向生成,CycleGAN为了突破Pix2Pix对数据集图片一一对应的限制,采用了双向循环生成的结构,因此得名CycleGAN。

首先,CycleGAN也是一个GAN模型,通过判别器和生成器的对抗训练,学习数据集图片的像素概率分布来生成图片。

要完成X域到Y域的图片风格迁移,就要求GAN网络既要拟合Y域图片的风格分布分布,又要保持X域图片对应的内容特征。打个比方,用草图风格的猫图片生成照片风格的猫图片时,要求生成的猫咪“即要活灵活现,又要姿势不变”。“拟合数据分布”本来就是GAN干的活儿,而“保持原图片特征”在Pix2Pix上是这么实现的:

因为Pix2Pix是一个CGAN,所以,我们通过用X域图片当约束条件来限制Pix2Pix的输出Y域风格图片时保有X域图片的特征。

而送入CycleGAN的两组(X域Y域)图片没有一一对应关系,即使我们将X域图片当成限制条件输入到一个CGAN中,也起不到限制模型输出保有X域图片特征的作用。因为,送入的两组图片完全是随机配在一起,CGAN学不到任何联系。因此,CycleGAN采取了一个绝妙的设计:通过添加“循环生成”并优化一致性损失(Consistency Loss)来代替CGAN中使用的约束条件来限制生成器保有原域图片特征。这样就不需要训练集图片一一对应了。

2.CycleGAN的流程

下面,我们就来看看循环生成网络(CycleGAN)到底是怎么“循环起来”的:

上图左半部分,将原域图片x送入(x2y方向)生成器G生成目标域图片y^,然后再将生成的目标域图片y^送入(y2x方向)生成器F反过来生成原域图片x^。生成x^的目的就是用它与输入的真图片x来算L1 Loss。我们知道Pix2Pix优化时除了使用GAN Loss(对抗损失)外,还加入了生成器输入图片和输出图片的L1 Loss来对齐生成图片与输入图片的宏观轮廓(所谓低频信息)。同样的逻辑,我们也能在CycleGAN中用L1 Loss来对齐“循环生成”的x^与输入的原图片x的内容自然,x生成的y^的轮廓也是和x对齐的了。这就达到了(原论文中的例子)“马变斑马,花纹变,姿势不变”的目的了。(我在网上看到的CycleGAN资料都没有点明这一点的,所以只好自行脑补,欢迎指正。)

在这个x->y^->x^的生成过程中,可以通过判别器Dy与生成器(x2y)G进行对抗训练。那么这个链条上的反向生成器(y2x)F怎么办?当然是加个判别器Dx与它进行对抗训练了。这样CycleGAN就有了两个方向相反的生成器,两个分别判别x域、y域图片的判别器。但要注意一个问题:就像GAN的生成器和判别器不能同时训练一样,Cyc1eGAN的两个生成器、两个判别器也只能一个一个训练,这就形成了CycleGAN训练的两条“环路”。

3.CycleGAN的结构

接下来,我们再看看这两对判别器、生成器怎么摆:

上半部份是生成器G和判别器Dy进行x2y的训练过程,下半部份是生成器F和判别器Dx进行y2x的训练过程。很像是两个风格迁移方向相反Pix2Pix模型,只是这两个GAN是普通GAN,不是Pix2Pix那样的CGAN。这一点,从生成器和判别器的输入就可以看出来,输入的只有原域图片并没有像Pix2Pix一样融合条件图片。

4.CycleGAN的loss函数

前面分析了CycleGAN的原理,我们已经知道了CycleGAN的loss由对抗损失(称为gan loss或adversarial loss)和循环一致性损失(consitency loss)组成,下面看看公式:

上面公式中:

​指的是x2y过程的对抗损失(adversarial loss)

​指的是y2x过程的对抗损失(adversarial loss)

​指的是生成器G和生成器F的循环一致性损失。

其中为循环一致性损失(consitency loss)的缩放系数,是一个超参数。

实际上,原论文的代码还加入了本体映射损失(identity loss),只是默认设置为关闭。CycleGAN正常训练时,生成器G输入x,生成y^。计算生成器G的本体映射损失(identity loss)时,生成器G输入y,生成y^,然后用y与y^的L1 loss作为G的identity loss。相应地,生成器F的identity loss则是输入的x与生成的x^的L1 loss。优化CycleGAN时,如果启用identity loss则将这两部分加到模型总loss中。与循环一致性损失(consistency loss)一样,也使用缩放系数超参控制其在总loss中所占比重。

论文中提到,CycleGAN使用identity loss的目的是在迁移的过程中保持原色调,下面是使用identity loss的对比效果:

上面图片最右边一列使用identity loss后果然纠正了生成器的色偏。

code:

https://github.com/eriklindernoren/PyTorch-GAN

GAN系列之pix2pix

也许是CycleGAN的光芒太过耀眼,Pix2Pix就像家中的次子,还没得宠多长时间,就被弟弟CycleGAN抢走了风头。这也怪不得它们的“爹滴”朱大神把“域风格迁移”的CycleGAN(下个项目介绍)造得太好用了,似乎完全能够代替“像素风格迁移”的Pix2Pix,以至于都来不及给Pix2Pix起个××GAN的名字~

其实,除了“白天照片变夜晚”、“图片着色”、“蓝图变街景”等它弟弟CycleGAN更容易玩儿的花样儿外,Pix2Pix是有着自己的独门绝技的。比如,用自然风景照片训练好的Pix2Pix模型,能实时将手绘的草图渲染成对应风景照片。如果训练集照片里包括老虎等动物,我们几笔在一个圆圈脑袋上画个王字,Pix2Pix模型就能生成一张活灵活现的大老虎,比《照相馆的故事》快多了~Pix2Pix的工作也启发了一些更具体的应用,比如专门手绘照片的SketchyGAN、手绘人脸的模型DeepFaceDrawing等。另外Pix2Pix->Pix2PixHD(高清渲染)->Vid2Vid(视频实时渲染)也是一条发展路线。试想,只需建好游戏人物和场景的结构模型,然后机器自动按训练的风格渲染人物和场景,游戏设计师们有没有感到点儿激动。

1. Pix2Pix的原理

发表在CVPR2017上的论文《Image-to-Image Translation with Conditional Adversarial Networks》是将GAN应用于有监督的图像到图像翻译的经典论文,提出的GAN模型被简称为Pix2Pix(不叫××GAN,很像是小名儿吧~)。为了解决图像到图像的翻译(也就是前面提到的那些上色、手绘草图的应用),我们需要建立一个模型实现图像到图像的映射。

以前曾经有过尝试搭建一个CNN网络进行映射,并用L1距离来度量、优化模型,结果发现效果很模糊(用L2距离更模糊),就像下面这样:

那么,既然GAN能够较好地生成图片的细节,我们何不拿来一用?显然,经典GAN是不行的,没法控制输出嘛。CGAN正好拿来一用。对此,朱大神在报告里曾经解释过:如果我们用经典GAN,判别器判别时会出现这样的问题。

这样的生成图片判别为真没问题

但是,这样的生成图片也判断为真就有问题了。显而易见,生成的猫图片与手绘的猫草图的形态完全不一致。但因为这也是一张猫图片,是符合训练集图片的像素概率分布的,所以会被经典GAN判别为真图片。

为了解决这一问题,我们将输入的猫草图作为“条件标签”和生成的猫图片一起送入判别器进行判断,如下图:

这看上去是不是有点儿CGAN的影子?没错,这个Pix2Pix就是个CGAN!

2.Pix2Pix的结构

我们将Pix2Pix的结构与上篇CGAN的结构对比一下:

上图的上半部份是普通CGAN的结构,下半部分是Pix2Pix的结构。对比发现,Pix2Pix与CGAN的结构有两点不同:

  1. 在Pix2Pix中,输入生成器的控制条件由“分类标签y”变成了A组(原风格)图片,因为这里我们要用A组(原风格)图片做为控制条件来生成B组(目标风格)图片。由于输入生成器的A组图片的维度(图片尺寸)与生成器输出的B组图片的维度相同,足以映射复杂分布,所以,我们不必再输入噪声z。细心的同学可能会发现:在刚才那张“对比普通CGAN和Pix2Pix结构”的图片中,我们对“条件y”的解释,与上一张“介绍给Pix2Pix加标签原因”的图片中的解释不一样。“对比结构”的图片中将生成器的输入解释为“条件y”,而“解释用CGAN原因”的图片中将生成器的输入解释为“输入x”。实际上这两种对生成器输入的解释都指的是A(原风格)组图片,不影响后面的推理。但个人觉得:将生成器的输入解释为“条件y”更容易帮助理解Pix2Pix的CGAN本质。我理解,Pix2Pix拟合的是训练集中B组(目标风格)图片的像素概率分布,A组(原风格)图片是作为“约束条件”来使用的。对比一下普通CGAN的结构就清楚了。
  2. 在Pix2Pix中,输入判别器的控制条件也由“分类标签y”变成了A组(原风格)图片。A组(原风格)图片作为“条件y”要和真B组(目标风格)图片或生成器生成的假B组图片(在图像通道维度上)拼接在一起送入判别器。这个很好理解,也说明了前面把生成器的输入解释为“条件y”更“工整”。

这样,Pix2Pix做了以上改动后,整个模型从“输入噪声、输出图片”的流程,变成了“输入A组图片、输出B组图片”的流程。

3.Pix2Pix的loss

在大神造Pix2Pix的过程中也试过各种“配方”。包括使用L1损失、使用CGAN损失和使用两者之和,测试结果如下:

观察结果发现:

  • 只用L1损失时,生成的图片比较模糊。
  • 只用CGAN损失时,生成的图片很清晰,但颜色风格与Ground Truth图片差别较大。
  • 使用L1+CGAN损失时,生成的图片又清晰,又保留了更多Ground Truth图片的特征。

所以,最后Pix2Pix使用了L1+CGAN损失。我们看下loss的构成。

先看L1损失:

L1损失的计算方法就是真B组(目标风格)图片与生成器生成的假B组图片逐像素求差的绝对值再求平均。公式中的x指A组(原风格)图片,y指B组(目标风格)图片,z指C输入给生成器的(一般是高斯分布的)噪声,代码中并未使用。

再来看看CGAN损失:

Pix2Pix的CGAN损失和普通CGAN损失一模一样

Pix2Pix总的损失是这两者之和:

GAN系列之CGAN(Conditional GAN)

GAN只是拟合原数据集的像素概率分布,生成的样本并没有提供新的信息以优化模型的分类边界。我理解,样本插值还能优化一下分类边界,原始GAN充其量只能添加一点噪声,或许能增强一点模型泛化能力吧(真做数据增强还得InforGAN、styleGAN这样的才好,能通过潜空间插值对图像做高级语义的增强,这是后话。)。

原始GAN用起来也不方便,为了分别生成0~9的数字,得将原数据集按标签分为10组,每组用一个模型训练,一共需要10个模型。训练时由于每组的数据量少到原来的十分之一,也会发生因样本太少导致模型无法拟合的现象。所以,意欲降伏GAN的大神给原始GAN装了个钮,让GAN乖乖要啥给啥。这个带按钮的改进版就是CGAN。

CGAN(Conditional GAN)介绍

1、CGAN的原理

CGAN的全称是Conditional Generative Adversarial Nets,即条件生成对抗网络。故名思议,就是通过添加限制条件,来控制GAN生成数据的特征(类别)。

当我第一次了解了CGAN原理,我惊诧于它给GAN“加按钮”的方法竟然如此简单粗暴,要做仅仅就是“把按钮加上去”——训练时将控制生成类别的标签连同噪声一起送进生成器的输入端,这样在预测时,生成器就会同样根据输入的标签生成指定类别的图片了判别器的处理也是一样,仅仅在输入加上类别标签就可以了。

那么,为什么加了标签,CGAN就乖乖听话、要啥给啥了呢?原理也是十分简单,我们知道GAN要干的就是拟合数据的概率分布,而CGAN拟合的就是条件下的概率分布。

GAN:

原生GAN中的概率全改成条件概率:

而上面CGAN公式中的条件y就是咱给GAN装的“钮”。加上了这个条件按钮,GAN优化的概率期望分布公式就变成了CGAN优化的条件概率期望分布公式。即CGAN优化的目标是:在条件Y下,在判别器最大化真实数据与生成数据差异的情况下,最小化这个差距。训练CGAN的生成器时要同时送入随机噪声z和和条件y(在本项目中y就是MNIST手写数字数据集的数字标签)。就是这么简单!

2、CGAN的结构

CGAN设计巧妙,而结构也十分简单、清晰,与经典GAN只有输入部分稍许不同。

我们看看原始GAN与CGAN的结构对比(包括生成器和判别器),上半部份的是经典GAN,下半部分是CGAN:

我们先回顾下经典GAN的结构流程(如上图上半部份所示):

  • 训练判别器。将噪声z送入生成器,输出fake_x;将fake_x送入判别器,在更新判别器参数时尝试拉近判别器的输出与真标签1的距离,即最小化判别器输出与真标签1的交叉熵损失。再将真图片送入判别器,更新判别器参数时尝试拉近判别器的输出与假标签0的距离,即最小化判别器输出与假标签0的交叉熵损失。这个过程中,用真、“假”图片训练判别器的顺序不必需固定,真、假标签取值0、1也无需固定(可相反,效果没有区别)。要注意的是,训练判别器的过程中,只更新判别器参数,不更新生成器参数。
  • 训练生成器。生成器训练的过程和判别器基本一样,只是将生成器输出的“假图片”送入判别器后,将判别器的输出与真标签(1)拉近。目的就是,使生成器参数更新的方向朝着“骗过判别器的目标”进行,也就是所谓“对抗过程”。当然判别器出掌(判别器更新参数)时,生成器不还手(生成器不更新参数),轮到生成器还手(生成器更新参数)时,判别器也得双手背后(判别器不更新参数)。不然就打成一团,谁也看不到招式(无法正确更新参数,提高生成能力)了

我们再看下CGAN给GAN加的“料”(如上图下半部份所示):

  • 先看判别器。如图,无论是给判别器送入真图片还是生成器生成的假图片时,都要加上个“条件y”,也就是分类标签。判别器输出没有变化仍然只是判断输入图片的真假。老实说,当时我曾想:既然咱都conditional GAN了,这个判别器是不是要输出分类标签y来训练Condition那部分?但转念一想,不行,判别器还是得判别真假,不然没法和生成器对抗了。BUT,后来我发现还真有走这个路线的GAN,叫InfoGAN。这个InfoGAN给生成器配了两个判别器,一个判真假,一个分类别。
  • 再看生成器。生成器的输入除了随机噪声z外,也加入了“条件y”。到这儿,我又想:既然有了条件标签,就不用输入噪声z了吧~。答案当然是,不行!因为,噪声z的维度是和生成器输出图片的尺寸、复杂度相关的。本项目中输出图片尺寸是28×28=784。按理说模型进行映射的输入、输出尺寸应该是相等的。但是输出图片只是手写数字,规律比较简单,输入的尺寸可以进行一定程度的压缩。一般噪声z的维度为几十到一百就能生成比较理想的图片细节,如果太低会导致生成器拟合能力不足,生成图片质量低下。条件z只是一个取值0~9的维度为一的向量,模型拟合像素概率分布的效果可想而知。后面我们介绍的Pix2Pix模型的输入是一张和输出尺寸相同的图片,就不再输入噪声z了。

CGAN需要注意的一点是:输入的条件标签y不但要在输入时与噪声z融合在一起,在生成器和判别器的每一层输入里都要与特征图相融合,才能让模型“学好条件y”。不然,标签可能不灵~

code https://github.com/eriklindernoren/PyTorch-GAN

GAN系列之经典GAN(一)

reference:

https://zhuanlan.zhihu.com/p/78777020

https://zhuanlan.zhihu.com/p/28853704

GAN全称:Generative Adversarial Network 即生成对抗网络,由Ian J. Goodfellow等人于2014年10月发表在NIPS大会上的论文《Generative Adversarial Nets》中提出。此后各种花式变体Pix2Pix、CYCLEGAN、STARGAN、StyleGAN等层出不穷,在“换脸”、“换衣”、“换天地”等应用场景下生成的图像、视频以假乱真,好不热闹。前段时间PaddleGAN实现的First Order Motion表情迁移模型,能用一张照片生成一段唱歌视频。各种搞笑鬼畜视频火遍全网。用的就是一种GAN模型哦。深度学习三巨神之一的LeCun也对GAN大加赞赏,称“adversarial training is the coolest thing since sliced bread”。

对抗生成模型GAN首先是一个生成模型,和大家比较熟悉的、用于分类的判别模型不同。

判别模型的数学表示是y=f(x),也可以表示为条件概率分布p(y|x)。当输入一张训练集图片x时,判别模型输出分类标签y。模型学习的是输入图片x与输出的类别标签的映射关系。即学习的目的是在输入图片x的条件下,尽量增大模型输出分类标签y的概率。

而生成模型的数学表示是概率分布p(x)。没有约束条件的生成模型是无监督模型,将给定的简单先验分布π(z)(通常是高斯分布),映射为训练集图片的像素概率分布p(x),即输出一张服从p(x)分布的具有训练集特征的图片。模型学习的是先验分布π(z)与训练集像素概率分布p(x)的映射关系。

生成对抗网络一般由一个生成器(生成网络),和一个判别器(判别网络)组成。生成器的作用是,通过学习训练集数据的特征,在判别器的指导下,将随机噪声分布尽量拟合为训练数据的真实分布,从而生成具有训练集特征的相似数据。而判别器则负责区分输入的数据是真实的还是生成器生成的假数据,并反馈给生成器。两个网络交替训练,能力同步提高,直到生成网络生成的数据能够以假乱真,并与与判别网络的能力达到一定均衡。

GAN的本质

其实GAN模型以及所有的生成模型都一样,做的事情只有一件:拟合训练数据的分布。对图片生成任务来说就是拟合训练集图片的像素概率分布。下面我们从原理的角度演示一下GAN的训练过程:

上图中: 黑色点线为训练集数据分布曲线 蓝色点线为判别器输出的分布曲线 绿色实线为生成器输出的分布曲线 z展示的是生成器映射前的简单概率分布(一般是高斯分布)的范围和密度 x展示的是生成器映射后学到的训练集的概率分布的范围和密度 (a)判别器与生成器均未训练呈随机分布 (b)判别器经过训练,输出的分布在靠近训练集“真”数据分布的区间趋近于1(真),在靠近生成器生成的“假”数据分布的区间趋近于0(假) (c)生成器根据判别器输出的(真假)分布,更新参数,使自己的输出分布趋近于训练集“真”数据的分布。 经过(b)(c)(b)(c)…步骤的循环交替。判别器的输出分布随着生成器输出的分布与训练集分布的接近而更加平缓;生成器输出的分布则在判别器输出分布的指引下逐渐趋近于训练集“真”数据的分布。 (d)训练完成时,生成器输出的分布完美拟合了训练集数据的分布,判别器的输出由于生成器的完美拟合而无法判别生成器输出的真伪而呈一条取值约为0.5(真假之间)的直线。

GAN的组成

  1. 解读GAN的loss函数

GAN网络的训练优化目标就是如下公式:

公式出自Goodfellow在2014年发表的论文Generative Adversarial Nets。这里简单介绍下公式的含义和如何应用到代码中。上式中等号左边的部分: V(D,G)表示的是生成样本和真实样本的差异度,可以使用二分类(真、假两个类别)的交叉熵损失。

maxV(D, G)表示在生成器固定的情况下,通过最大化交叉熵损失V(D,G)来更新判别器D的参数。

min maxV(D, G)表示生成器要在判别器最大化真、假图片交叉熵损失V(D,G)的情况下,最小化这个交叉熵损失

首先固定G训练D :

1)训练D的目的是希望这个式子的值越大越好。真实数据希望被D分成1,生成数据希望被分成0。

第一项,如果有一个真实数据被分错,那么log(D(x))<<0,期望会变成负无穷大。

第二项,如果被分错成1的话,第二项也会是负无穷大。

很多被分错的话,就会出现很多负无穷,那样可以优化的空间还有很多。可以修正参数,使V的数值增大。

2)训练G ,它是希望V的值越小越好,让D分不开真假数据。

因为目标函数的第一项不包含G,是常数,所以可以直接忽略 不受影响。

对于G来说 它希望D在划分他的时候能够越大越好,他希望被D划分1(真实数据)。

第二个式子和第一个式子等价。在训练的时候,第二个式子训练效果比较好 常用第二个式子的形式。

证明V是可以收敛导最佳解的。

(1)global optimum 存在

(2)global optimum训练过程收敛

全局优化首先固定G优化D,D的最佳情况为:

1、证明D*G(x)是最优解

由于V是连续的所以可以写成积分的形式来表示期望:

通过假设x=G(z)可逆进行了变量替换,整理式子后得到:

然后对V(G,D)进行最大化:对D进行优化令V取最大

取极值,对V进行求导并令导数等于0.求解出来可得D的最佳解D*G(x)结果一样。

2、假设我们已经知道D*G(x)是最佳解了,这种情况下G想要得到最佳解的情况是:G产生出来的分布要和真实分布一致,即:

在这个条件下,D*G(x)=1/2。

接下来看G的最优解是什么,因为D的这时已经找到最优解了,所以只需要调整G ,令

对于D的最优解我们已经知道了,D*G(x),可以直接把它带进来 并去掉前面的Max

然后对 log里面的式子分子分母都同除以2,分母不动,两个分子在log里面除以2 相当于在log外面 -log(4) 可以直接提出来:

结果可以整理成两个KL散度-log(4)

KL散度是大于等于零的,所以C的最小值是 -log(4)

当且仅当

所以证明了 当G产生的数据和真实数据是一样的时候,C取得最小值也就是最佳解。

如上图所示GAN由一个判别器(Discriminator)和一个生成器(Generator)两个网络组成。

训练时先训练判别器:将训练集数据(Training Set)打上真标签(1)和生成器(Generator)生成的假图片(Fake image)打上假标签(0)一同组成batch送入判别器(Discriminator),对判别器进行训练。计算loss时使判别器对真数据(Training Set)输入的判别趋近于真(1),对生成器(Generator)生成的假图片(Fake image)的判别趋近于假(0)。此过程中只更新判别器(Discriminator)的参数,不更新生成器(Generator)的参数。

然后再训练生成器:将高斯分布的噪声z(Random noise)送入生成器(Generator),然后将生成器(Generator)生成的假图片(Fake image)打上真标签(1)送入判别器(Discriminator)。计算loss时使判别器对生成器(Generator)生成的假图片(Fake image)的判别趋近于真(1)。此过程中只更新生成器(Generator)的参数,不更新判别器(Discriminator)的参数。

判别器结构:

生成器结构:

代码实现:http://139.9.1.231/index.php/2021/12/29/gan/

数据降维方法汇总

网上关于各种降维算法的资料参差不齐,同时大部分不提供源代码。这里有个 GitHub 项目整理了使用 Python 实现了 11 种经典的数据抽取(数据降维)算法,包括:PCA、LDA、MDS、LLE、TSNE 等,并附有相关资料、展示效果。

所谓降维,即用一组个数为 d 的向量 Zi 来代表个数为 D 的向量 Xi 所包含的有用信息,其中 d<D;通俗来讲,即将高维度下降至低维度;将高维数据下降为低维数据。

降维算法资料链接代码展示
PCA资料链接1 资料链接2 资料链接3PCA
KPCA资料链接1 资料链接2 资料链接3KPCA
LDA资料链接1 资料链接2LDA
MDS资料链接1MDS 
ISOMAP资料链接1 资料链接2ISOMAP
LLE资料链接1 资料链接2LLE
TSNE资料链接1TSNE
AutoEncoder无 
FastICA资料链接1FastICA
SVD资料链接1 资料链接2SVD
LE资料链接1资料链接2LE
LPP资料链接1 资料链接2LPP

环境: python3.6 ubuntu18.04(windows10) 需要的库: numpy sklearn tensorflow matplotlib

github:https://github.com/heucoder/dimensionality_reduction_alo_codes 

c++ 复合类型

  • 1、数组

数组声明: typename arrayname[arraysize]

或者 在声明时候赋值 int x[2]={1,2};

如果只对数组的一部分进行初始化,则编译器会把其他元素设置为0,因此:

long tio[5]={10};

如果初始化时方括号内【】为空,编译器自动计算元素个数:

short totals[ ]={1,2,3}

注意:arraysize指定元素的数目 ,必须是整型常数或者const值,具体来说,arraysize不能是变量。当然可以使用new运算符来避开这种限制。

访问数组元素:arrayname[i] i从0开始,到size-1

c++11 初始化数组时,省略等号 doouble x[2] {1,2};

可以不再大括号中包含任何内容 doouble x[2] {} ,这将会把所有元素设为0

c++的标准模板库提供了一种数组替代品 -模板类vector,c++11新增了array。

  • 字符串

c++处理字符串方式有两种 一种来自c语言,另一种基于string类库的方法。

c语言风格字符串性质:以空字符结尾 ,空字符 \0,其ascii码为0.

char dog[3] ={‘b’,’e’,’\0′} //字符串

char cat[3] ={‘a’,’v’,’d’} //字符数组

char bird[11] = “mr. cheepes” //注意“”里不用显示的包括\0,但隐式的包括空字符,所以数组大小必须比实际长度大于一个以上。

使用键盘输入字符串时,将自动加上空字符

注意字符串常量(” “)不能与字符常量(’ ‘)互换。”s”表示字符串,是由’s’和’\0’组成,而’s’表示单个字符。

字符串输入:

cin>>name; cin使用空白(空格、制表符、换行符)来确定字符串结束的位置,因此cin在读取 字符串时只能读取一个单词,然后将其放到数组name中。

为了解决上面的问题,将整个一行字符作为输入:istream中cin提供了类成员函数:getline()和get(),读取输入,直到遇到换行符后才停止。区别 getline() 将丢弃换行符,get将保留换行符在输入队列中。

cin.getline(name,size)

name:存储的数组名,size:读取的字符数,如果size=20,那么最多读取19个字符,且不hi存储最后的换行符,余下空间用于自动存储\0空字符。

cin.get( name,size )

混合输入字符串和数字

  • string类

包含头文件 <string>,string类位于std命名空间中,必须使用using编译指令或者使用std::string引用它。

定义: string str1; string str2=”sssssss”;

赋值: cin>>str1;

显示:cout<<str1

通过访问数组的方法访问string ,str1[3]

string和字符数组区别:可以将 string对象声明为简单变量,而不是数组。声明时候不用指定大小。

c++11 初始化string : string one={“dddfse”}; string two{“sdhfhs”}

string赋值、拼接和附加 string str2 =str1 string str3 =str1+str2

c中ctring头文件中的函数 strcpy(chaar1,charr2) //copy charr2 to charr1 strcat(charr1,charr2) 拼接charr2到charr1中

string的输入输出 cin cout,当读取一行时,使用的方法不同:

数组字符串: cin.getline(charr,20)

string: getline(cin,str1)

  • 结构体

创建该类型变量 : inflatable hat;

可以将结构作为参数传递给函数,也可以让函数返回一个结构,可以使用 = 将一个结构赋给同类型的结构。

可以在定义结构体同时创建结构变量,只需要将变量名放在定义 结构的括号后面。

结构数组

  • 共用体 union

共用体是一种数据格式,能够存储不同的数据类型 ,但只能同时同时存储其中的一种类型。

声明 和struct类似

  • 枚举 enum ,可以替代 const,还允许定义新类型

enum spectrum {red,orange,yellow,green,blue,violet,indigo,ultraviolet};

默认情况下,将整数值赋给枚举变量 ,第一个枚举值为0,以此类推。当然也可以显式指定整数来覆盖默认值。

枚举只有 赋值运算,没有算数运算。

如果使用 整数:需要强制类型转换 枚举名(整数)

设置枚举值

  • 指针、数组

地址运算符 &变量 获得该变量的地址

指针:用于存储值的地址,使用*运算符,可以得到该地址处的值,*被称为间接值运算符,即 x是一个地址,*x时该地址的数值

可以在命名的同时赋值:

int *p

声明一个指针:

int *p or int* p or int*p

可以在声明的同时初始化: int * p= &x

注意:初始化的是 p,而不是*p

new运算符

delete 释放内存

int *p = new int

delete p

注意:只能用delete删除new分配的内存

使用 new来新建动态数组

指针小结

  • 数组名是一个指针,指向的是数组array[0]
  • 将&用于数组名时候,获得的是整个数组的内存地址,因此需要取 &array[0]作为数组指针的地址

指针和字符串

C++处理字符串有两种方式,即:指针方式和数组方式

数组方式:char a[] = “HelloWorld”;
指针方式:const char* s = “HelloWorld”; const可以忽略
接下来详细讲解一下字符串指针

首先,为什么字符串可以直接赋值给指针,即char* s = “HelloWorld”不会报错,不应该是把字符串的地址赋值给指针吗?

原因:这里的双引号做了3件事:

1.申请了空间(在常量区),存放了字符串

  1. 在字符串尾加上了’/0′
    3.返回地址
    为什么字符串指针的指针名输出字符串内容而不是地址?

字符串指针的指针名代表字符串的首地址,但输出字符串指针名时输出的却是完整字符串,如下:

char* s = "HelloWorld";
cout<<s<<endl; //s是字符串的首地址,但却输出HelloWorld
cout<<*s<<endl;  //输出H
cout<<*(s+1)<<endl;  //输出e,s+1是第二个字符的地址
    cout <<static_cast<void *>(s) << endl; //此时输出的才是字符串地址

原因是C++标准库中I/O类对<<操作符重载,在遇到字符型指针时会将其当作字符串名来处理,输出指针所指的字符串。既然这样,那么我们就别让它知道那是字符型指针,所以得用到强制类型转换,用static_cast把字符串指针转换成无类型指针

字符串指针指向的地址可以修改,但所指向的字符串内容不能修改,因为字符串常量是不能改变的

char* s = "HelloWorld";
s="abcd"; //合法
cout<<*(s+1)<<endl;
*(s+1)='d'; //不合法,这里虽然没报错,但这一句实际下一句并未执行
cout<<s<<endl; //未执行

字符串指针数组:

char *p[6]={"ABCD","EFGH","IJKL","MNOP"};
int i;
for(i=0;i<4;i++) 
    cout<<p[i]<<endl;  //输出每个字符串,实际上p[i]为第i个字符串的首地址
for(i=0;i<4;i++) 
    cout<<*p[i];  //输出每个字符串第一个字符AEIM
cout<<endl;
for(i=0;i<4;i++) 

cout<<*(p[i]+1); //输出每个字符串第二个字符BFJN

C++中使用char*定义字符串,不能改变字符串内的字符的内容,但却可以把另外一个字符串(新地址)赋值给它,即p1是一个char型指针变量,其值(指向)可以改变;此时,若指向的新地址为字符串数组的地址,则可更改字符串中的内容

使用new创建动态结构:

类型组合

模板类 vector 和 array

AXI4协议

ZYNQ将高性能ARM Cotex-A系列处理器与高性能FPGA在单芯片内紧密结合,为设计带来了如减小体积和功耗、降低设计风险,增加设计灵活性等诸多优点。在将不同工艺特征的处理器与FPGA融合在一个芯片上之后,片内处理器与FPGA之间的互联通路就成了ZYNQ芯片设计的重中之重。如果Cotex-A9与FPGA之间的数据交互成为瓶颈,那么处理器与FPGA结合的性能优势就不能发挥出来。

AXI的英文全称是Advanced eXtensible Interface,即高级可扩展接口,它是ARM公司所提出的AMBA(Advanced Microcontroller Bus Architecture)协议的一部分

AXI协议就是描述主设备和从设备之间的数据传输方式,在该协议中,主设备和从设备之间通过握手信号建立连接。

AXI协议是一种高性能、高带宽、低延迟的片内总线,具有如下特点:
1、总线的地址/控制和数据通道是分离的;
2、支持不对齐的数据传输;
3、支持突发传输,突发传输过程中只需要首地址;
4、具有分离的读/写数据通道;
5、支持显着传输访问和乱序访问;
6、更加容易进行时序收敛。

在数字电路中只能传输二进制数0和1,因此可能需要一组信号才能高效地传输信息,这一组信号就组成了接口。AXI4协议支持以下三种类型的接口:
1、 AXI4:高性能存储映射接口。
2、 AXI4-Lite:简化版的AXI4接口,用于较少数据量的存储映射通信。
3、 AXI4-Stream:用于高速数据流传输,非存储映射接口。

在这里我们首先解释一下存储映射(Meamory Map)这一概念。如果一个协议是存储映射的,那么主机所发出的会话(无论读或写)就会标明一个地址。这个地址对应于系统存储空间中的一个地址,表明是针对该存储空间的读写操作。

AXI4协议支持突发传输,主要用于处理器访问存储器等需要指定地址的高速数据传输场景。AXI-Lite为外设提供单个数据传输,主要用于访问一些低速外设中的寄存器。而AXI-Stream接口则像FIFO一样,数据传输时不需要地址,在主从设备之间直接连续读写数据,主要用于如视频、高速AD、PCIe、DMA接口等需要高速数据传输的场合。

AXI4:高性能存储映射接口

AXI4接口,它由五个独立的通道构成

1、 读地址
2、 读数据
3、 写地址
4、 写数据
5、 写响应
下面是使用读地址和读数据通道实现读传输过程的示意图:

从图 15.1.1中可以看到,在一个读传输过程中,主机首先在读地址通道给出读地址和控制信号,然后从机由读数据通道返回读出的数据。另外我们需要注意的是,这是一次突发读操作,主机只给出一个地址,从该地址连续突发读出四个数据。

写传输过程如图 15.1.2所示,它用到了写地址、写数据和写响应三个通道。主机在写地址通道给出写地址和控制信号,然后在写数据通道连续突发写四个数据。从机在接收数据之后,在写响应通道给出响应信号。

AXI总线中的每个通道都包含了一组信息信号,还有一个V ALID和一个READY信号。V ALID信号由源端(source)产生,表示当前地址或者数据线上的信息是有效的;而READY信号由目的端(destination)产生,则表示已经准备好接收地址、数据以及控制信息。VALID和READY信号提供了AXI总线中的握手机制,如下图所示:

ACLK为时钟信号,在AXI协议中,所有的输入信号都在是ACLK的上升沿采样,所
有的输出信号必须在ACLK的上升沿之后才能改变。在T1之后,源端将V ALID拉高,表明INFORMA TION信号线上传输的是有效的地址、数据或者控制信息。目的端在T2之后将READY拉高,表明它已经准备好接收数据,此时源端必须保持INFORMA TION数据稳定不变,直到T3时刻进行数据传输。
需要注意的是,源端不允许等目的端的READY信号拉高之后,才将V ALID信号置为有效状态。而且,一旦V ALID拉高,源端必须保持其处于有效状态,直至成功握手(在时钟上升沿检测到V ALID和READY同时为有效状态)。

接下来通过自定义一个AXI4接口的IP核,通过AXI_HP接口对PS端DDR3进行读写测试。

我们在PL内自定义的DDR3 Test IP核作为主设备,通过PS AXI_HP0接口,与DDR控制器进行通信,最终对DDR3存储器进行读写操作。

SDK

1  #include <stdio.h> 
2  #include "xil_cache.h" 
3  #include "xil_printf.h" 
4  #include "xil_io.h" 
5   
6  int main)() 
7  { 
8      int i; 
9      char c; 
10  
11     Xil_DCacheDisable)(); 
12     print("AXI4 PL DDR TEST!\n\r";); 
13  
14     hlwhile(1{){ 
15         scanf("%c"&,&c;); 
16         fif(c==='c'{){ 
17             printf("start\n\r";); 
18             ofor(i=0;i<4096;i=i+4{){ 
19                 printf("%d is %d\n",i(,(int)Xil_In32(0x10000000+i))); 
20             } 
21         } 
22     } 
23  
24     eunreturn 0; 
25 } 

AXI4-Stream协议

AXI4-Stream协议一般被翻译为AXI流协议,是AXI总线的一种演化版本。AXI4流协议作为一个标准接口,用于连接进行数据交换的组件。接口可以用来连接一个单一的主机,主机向接收数据的单一从机发送数据,也可用于连接若干个主机和从机的组件。协议支持共用一组信号线的多个数据流,允许构建一个通用互联。相比于AHB/APB,AXI流协议提出了数据包、数据帧以及传输操作等概念,这也是其被称为流(Stream)的原因。
关于AXI Stream的基本概念解释如下:
传输(Transfer):通过 AXI4 流接口进行的一个单一数据传输。一个单一数据传输由TV ALID和TREADY握手信号定义。
包(Packet):通过 AXI4 流接口被一起传输的一组字节,包类似于 AXI4 的突发。
帧(Frame):一个 AXI4 流中最高级别的字节编组。一帧可以包含很大数量的字节数,例如,一个完整的视频帧缓存。
数据流(Data Stream):从一个源设备到一个目标设备传输的数据。
两个模块之间进行数据传输,需要事先约定好这两个模块之间的传输协议,这是两个信号握手的概念。TV ALID和TREADY信号的握手包含三种情况:TV ALID先于 TREADY 的握手、TREADY先于 TV ALID的握手、TV ALID 和 TREADY 同时发生的握手。

注意该协议中使用上升沿采样
下图中,主机发出了数据和控制信息并将TV ALID 信号置为高。一旦主机驱动了 TV ALID ,主机发出的数据或控制信息必须保持不变,直到从机驱动 TREADY 信号为高表示可以接收数据和控制信息。在这种情况下,一旦从机设置 TREADY 为高,传输就会发生。箭头标示出了传输发生的位置。

下图中,从机在数据和控制信息有效之前驱动TREADY为高。这表示目标设备可以在一个ACLK周期内接收数据和控制信息。在这种情况下,一旦主机驱动 TV ALID 为高,则传输就会发生。箭头标示出了传输发生的位置。

下图中,主机驱动TV ALID为高,从机在同一时钟(ACLK)周期内也驱动TREADY为高。在这种情况下,如图中箭头标注,传输在同一周期内发生。

本次实验我们需要使用Vivado HLS工具生成带有AXI4-Stream接口的IP核,并将此IP核的AXI4-Stream接口连接到“AXI4-Stream to Video Out”模块中的AXI4-Stream接口,如下图所示:

我们重点关注图中的“s_axis_video_tlast”和“s_axis_video_tuser” 信号,其中“s_axis_video_tlast”是AXI4-Stream协议中“TLAST”信号,这个信号设置为高表示一行像素传输结束,“s_axis_video_tuser”是AXI4-Stream协议中的“TUSER”信号,这个信号设置为高表示一帧图像传输开始。时序图如下图所示:

图中的“EOL”表示“End of line”是行传输结束信号,它在一行图像像素传输结束的时候拉高一个时钟周期;图中的“SOF”表示“Start of frame”是帧传输开始信号。它在一帧图像像素传输开始的时候拉高一个时钟周期。

c++ 数据类型

本文主要关于数据类型。面向对象编程的本质就是设计并扩展自己的的数据类型。

2022年 1月
 12
3456789
10111213141516
17181920212223
24252627282930
31  

首先了解c++的内置数据类型 :基本类型和复合类型

基本类型:整形和浮点型 复合类型:数组、指针、字符串、结构 存储数据的方法:变量

  • 简单变量

变量命名规则:

如果想用多个单词组成一个名称,通常使用下划线字符将单词分开,如 my_onions,或者 从第二个单词开始将每个单词的第一个字母大写:myEyeTooth

  • 整型

不含小数的数字 0,-3 ,100。不同的整型使用不同的内存来存储整数。有符号和无符号类型分别表示正负数和正数

short int long longlong 通过不同数目的位存储值:(都是有符号数)

short 至少16位 :short x (short== short int)

int 至少与short一样长

long 至少32位,且至少与int一样长 (long == long int)

lonng long 之少64位,且至少与long一样长

sizeof 运算符,获得变量的所占字节,对于类型名 int等使用时,需要加括号: sizeof (int),如果是对于变量,可加可不加。

#include<iostream>
using  namespace std;
int main(){
    int x_collec =2;
    cout<<"x_collec is"<<sizeof(x_collec)<< endl;
}

初始化:

int year =2022 //如果知道变量初始值,建议定义时候赋初值。

c++11 初始化方法:将大括号用于单值变量,采用这种方法时候,=可以去掉

int x={3} or int x{3}

大括号中不含值默认为0 int z{}

头文件climits

climits定义了符号常量来并表示类型的限制: int n =INT_MAX;

  • 无符号类型

要创建无符号类型,只需要使用unsigned 来修改变量声明。

unsigned   short  x  
unsigned   int  x
unsigned   long  x       
  • char 类型 (也是整型)

char用于存储字符(字母和数字) char x =”M” ,实际上,计算机中存储的是对应的字符编码77,可以将x =x+1,char值位78,对应N,可以通过 (int)x强制转换为78

有些字符不能通过键盘直接输入,比如换行符不能用回车,因此,有了下面的转义字符:

char 占8bit,unsigned char 表示范围0-255, signed char 表示范围-128~127

c++11新增 char16_t char32_t, char16_t 无符号16位, char32_t 32位有符号数,使用前缀u表示 char16_t 类型的字符常量和字符串常量, 使用前缀U表示 char32_t 类型的字符常量和字符串常量 : char16_t ch=u’q’;

  • bool类型

布尔值 true or false,将非 0值解释为true,将0解释为false。字面值true和false都可以通过提升转换(不用显式强制转换)为 int类型,true转换为1,false转换为0。

  • const限定符

常量被初始化后就不能修改了 const int year =2022

const type name =value

浮点数

能够表示 带小数部分的数字

书写浮点数:

1、标准写法 12.34 22.3 0.12 8.0

2、E表示法 3.45E6 指的是3.45与1000000相乘结果,E6指的是10的6次方,6是指数,指数可以是正数也可以是负数。E可以写成e。

  • 浮点类型

三种:float 32位 double 64位 long double 128位,浮点数有精度限制。

float 只能保证6位精确位,double保证13位精确度。

cout所属的ostream类有一个类成员函数,能够精确的控制输出格式-字段宽度、小数位数、采用小数格式还是E格式等。后面会给出实现。

在程序中使用 浮点常量时候,默认会认为是double型,如果要指定类型,在常量后加后缀:

1.23f —-float型

1.23L —–long double

1.23 —-默认double 类型

c++ 算数运算符

加、减、乘、除、求模

除法:如果两个整数相除,结果会是一个整数(小数部分直接舍去),如果两个数中 有一个或两个是浮点数,则小数部分会被保留。(因为系统会将不同操作数进行自动准换成相同的类型)

类型转换

1、初始化和赋值进行的转换

比如赋值时 double x = 3.14f 将一个float型付给double ,如果将double付给float变量,可能会导致降低精度。int x =3.14f 最终x=3(直接丢弃小数部分)

0赋值给bool,会转换为false,非0值会变为true

2、算数运算时

变量提升:在计算表达式时c++将 bool、char、unsigned char 、signed char 和short 转换为int。

3、传递参数时转换

4、强制类型转换

首先要明确一点强制转换不会修改变量本身 ,而是创建一个新的、指定类型的值。

以下两种方法都可以:

(long) x 或者 long (x)

(typename) value type (value)

c++还引入了强制类型转换运算符: static _cast<typename> (value)

C++中的auto声明

c++11新增了auto,让编译器能够根据初始值类型推断变量类型。

typedef

C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。

typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写
https://pixabay.com/photos/nature-winter-tree-season-outdoors-6891549/

c++ 入门

本科大一的时候学习过c++,但因为后来大部分项目都是用python,所以基本上都还给老师了,但其实回过头发现,很多python开源库都是用c++写的,像opencv,因此很有必要去在回顾一下子c++的基本概念。

2022年 1月
 12
3456789
10111213141516
17181920212223
24252627282930
31  
  1. c++注释

以双斜杠开头: //这是一行注释,c++也能识别c注释,c注释包括在符号/* */之间,可以跨越多行。

#include <iostream>  
int main (){  
#c++ 例子  
    usinng namespace std;  
    cout<<"hello world"  
    cout<<endl;  
    return 0  
}  
  1. 预处理器和iostream

#include <iostream>

  1. 头文件和命名空间

如果使用iostream,而不是iostream.h,则应使用下面的名称空间编译指令来使iostream中的定义对程序可用: using namespace std;

命名空间作用:假如两个封装好的库,都有名为cout的函数,那么在调用cout时,编译器不知道是哪个函数,因此可以把某个库中函数定义到一个命名空间,就可以通过 std:cout (命名空间:函数名)调用,此外,这样写比较麻烦,还可以使用using,而不必使用std前缀 : using namespace std ;使得std中所有名称可用。在大型工程中,一般使用:using std:cout; using std:cin;单独定义所需的函数

  1. 输入输出

cout<<“hello” 和 cin<<a cout 还可以拼接 cout<<“s”<<“v”<<endl;

endl 是一个特殊的c++符号,表示换行,此外还可以使用c中的\n换行符 cout<<“hello \n”

  1. 声明语句和变量

int carrots; 这条语句声明了需要的内存和内存单元名称.为什么需要声明变量:如果不显示的声明,那么当我们在多次使用 carrots 变量时候,如果中间有个写错了 carrot ,系统不会报错,而是认为这是一个新的变量。

变量赋值 a=3

  1. 函数

type functionname(argumentlist){ statements }

函数头: type functionname(argumentlist) ,函数中可以使用using编译指令,起作用范围为函数内部。

如果using 放置在函数定义之前,文件中所有的函数都可以使用std中的元素,using放在特定函数中,则该函数能使用