From c37aac05cc8551ba5a7e67e9360b4fb06e90bb10 Mon Sep 17 00:00:00 2001 From: YixinKristy <48054808+YixinKristy@users.noreply.github.com> Date: Mon, 19 Apr 2021 10:44:39 +0800 Subject: [PATCH] add homework file (#258) * Add 7day course info on readme * Update README_cn.md * Add homework file * Update homework1.md --- education/README.md | 12 +- education/homework1.md | 99 +++++++++++ education/homework2.md | 231 ++++++++++++++++++++++++ education/homework3.md | 394 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 730 insertions(+), 6 deletions(-) create mode 100644 education/homework1.md create mode 100644 education/homework2.md create mode 100644 education/homework3.md diff --git a/education/README.md b/education/README.md index 6f1a046..ab778c7 100644 --- a/education/README.md +++ b/education/README.md @@ -31,17 +31,17 @@ **Bonus🤩:鼓励大家除了在AI Studio上完成作业外,也可自行创建GitHub repo,在repo上完成作业(内容可和AI Studio一样),并将PaddleGAN加入你的repo的requirements.txt中,最后将repo链接放在AI Studio项目中即可获得加分,最终有机会获得大奖°˖✧◝(⁰▿⁰)◜✧˖°。** -Day 1:客观题_理论层面的单选题(AI Studio作业入口) +Day 1:[客观题_理论层面的单选题](./homework1) -Day 2:代码题_基于DCGAN,改写为LS-GAN(AI Studio入口&GitHub入口) +Day 2:[代码题_基于DCGAN,改写为LS-GAN](./homework2) -Day 3:代码题_填空补全基于pix2pix实现人脸卡通的预测代码(AI Studio入口&GitHub入口) +Day 3:[代码题_填空补全基于pix2pix实现人脸卡通的预测代码](./homework3) -Day 4:客观题+项目展示(照片、视频)(AI Studio作业入口) +Day 4:客观题+项目展示(照片、视频) -Day 5:客观题+项目展示(照片、视频)(AI Studio作业入口) +Day 5:客观题+项目展示(照片、视频) -Day 6-大作业:自选PaddleGAN里面的模型实现超分(AI Studio入口&GitHub入口) +Day 6-大作业:自选PaddleGAN里面的模型实现超分 ## 奖品列表 diff --git a/education/homework1.md b/education/homework1.md new file mode 100644 index 0000000..835ab60 --- /dev/null +++ b/education/homework1.md @@ -0,0 +1,99 @@ +# GAN基础理论客观题 + +**1. (多选)GAN的基础结构包括()** + +A. 生成器 + +B. 判别器 + +C. 编码器 + +D. 解码器 + +参考答案:AB + +**2.(多选)GAN的应用包括()** + +A. 换脸 + +B. 动作迁移 + +C. 图像翻译 + +D. 超分辨率 + +参考答案:ABCD + +**3. (多选)生成对抗网络中的生成模型可以()** + +A. 输入噪声生成图像 + +B. 输入噪声和标签生成图像 + +C. 输入图像生成图像 + +D. 输入文字描述生成图像 + +参考答案:ABCD + +**4. (单选)下列哪一项是GAN的判别器的损失函数()** + +A. ![img](https://user-images.githubusercontent.com/48054808/115173831-b1315300-a0fa-11eb-9616-c8bd39dd74eb.png) + +B. ![img](https://user-images.githubusercontent.com/48054808/115173874-c6a67d00-a0fa-11eb-8447-722a4d0993ca.png) + +C. ![img](https://user-images.githubusercontent.com/48054808/115173903-d1f9a880-a0fa-11eb-90bd-7c143367444b.png) + +D. ![img](https://user-images.githubusercontent.com/48054808/115173943-e178f180-a0fa-11eb-87ce-ba3e96e51572.png) + +参考答案:A + +**5. (多选)下列关于GAN中对抗的描述正确的是()** + +A. 生成器与判别器互相对抗,在对抗中增强 + +B. 两个神经网络通过相互博弈的方式进行学习 + +C. 像警察与假钞,在对抗中增强警察的鉴别能力和小偷造假能力 + +D. 像自然界中捕食者与被捕食者在对抗中的进化 + +参考答案:ABCD + +**6. (多选)下列关于GAN的描述正确的是()** + +A. 生成网络希望Fake image的score尽可能的大 + +B. 生成网络希望Fake image的score尽可能的小 + +C. 判别网络希望Fake image的score尽可能的大 + +D. 判别网络希望Fake image的score尽可能的小 + +参考答案:AD + +**7. (多选)下列关于DCGAN的说法正确的有()** + +A. 使用卷积代替全连接层 + +B. 添加BatchNorm + +C. 在生成器中使用Relu + +D. 在判别器中使用Relu + +参考答案:ABC + +**8. GAN和auto encoder结构本质的区别是()** + +A. 网络结构不同 + +B. 输入不同 + +C. 对数据集的要求不同 + +D. 损失函数不同 + +参考答案:D + + diff --git a/education/homework2.md b/education/homework2.md new file mode 100644 index 0000000..69620a1 --- /dev/null +++ b/education/homework2.md @@ -0,0 +1,231 @@ +# DCGAN代码改写LSGAN的损失函数 + +可以看下有提示的地方。 + + +```python +#导入一些必要的包 +import os +import random +import paddle +import paddle.nn as nn +import paddle.optimizer as optim +import paddle.vision.datasets as dset +import paddle.vision.transforms as transforms +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.animation as animation +``` + + +```python +dataset = paddle.vision.datasets.MNIST(mode='train', + transform=transforms.Compose([ + # resize ->(32,32) + transforms.Resize((32,32)), + # 归一化到-1~1 + transforms.Normalize([127.5], [127.5]) + ])) + +dataloader = paddle.io.DataLoader(dataset, batch_size=32, + shuffle=True, num_workers=4) +``` + + +```python +#参数初始化的模块 +@paddle.no_grad() +def normal_(x, mean=0., std=1.): + temp_value = paddle.normal(mean, std, shape=x.shape) + x.set_value(temp_value) + return x + +@paddle.no_grad() +def uniform_(x, a=-1., b=1.): + temp_value = paddle.uniform(min=a, max=b, shape=x.shape) + x.set_value(temp_value) + return x + +@paddle.no_grad() +def constant_(x, value): + temp_value = paddle.full(x.shape, value, x.dtype) + x.set_value(temp_value) + return x + +def weights_init(m): + classname = m.__class__.__name__ + if hasattr(m, 'weight') and classname.find('Conv') != -1: + normal_(m.weight, 0.0, 0.02) + elif classname.find('BatchNorm') != -1: + normal_(m.weight, 1.0, 0.02) + constant_(m.bias, 0) +``` + + +```python +# Generator Code +class Generator(nn.Layer): + def __init__(self, ): + super(Generator, self).__init__() + self.gen = nn.Sequential( + # input is Z, [B, 100, 1, 1] -> [B, 64 * 4, 4, 4] + nn.Conv2DTranspose(100, 64 * 4, 4, 1, 0, bias_attr=False), + nn.BatchNorm2D(64 * 4), + nn.ReLU(True), + # state size. [B, 64 * 4, 4, 4] -> [B, 64 * 2, 8, 8] + nn.Conv2DTranspose(64 * 4, 64 * 2, 4, 2, 1, bias_attr=False), + nn.BatchNorm2D(64 * 2), + nn.ReLU(True), + # state size. [B, 64 * 2, 8, 8] -> [B, 64, 16, 16] + nn.Conv2DTranspose( 64 * 2, 64, 4, 2, 1, bias_attr=False), + nn.BatchNorm2D(64), + nn.ReLU(True), + # state size. [B, 64, 16, 16] -> [B, 1, 32, 32] + nn.Conv2DTranspose( 64, 1, 4, 2, 1, bias_attr=False), + nn.Tanh() + ) + + def forward(self, x): + return self.gen(x) + + +netG = Generator() +# Apply the weights_init function to randomly initialize all weights +# to mean=0, stdev=0.2. +netG.apply(weights_init) + +# Print the model +print(netG) +``` + + +```python +class Discriminator(nn.Layer): + def __init__(self,): + super(Discriminator, self).__init__() + self.dis = nn.Sequential( + + # input [B, 1, 32, 32] -> [B, 64, 16, 16] + nn.Conv2D(1, 64, 4, 2, 1, bias_attr=False), + nn.LeakyReLU(0.2), + + # state size. [B, 64, 16, 16] -> [B, 128, 8, 8] + nn.Conv2D(64, 64 * 2, 4, 2, 1, bias_attr=False), + nn.BatchNorm2D(64 * 2), + nn.LeakyReLU(0.2), + + # state size. [B, 128, 8, 8] -> [B, 256, 4, 4] + nn.Conv2D(64 * 2, 64 * 4, 4, 2, 1, bias_attr=False), + nn.BatchNorm2D(64 * 4), + nn.LeakyReLU(0.2), + + # state size. [B, 256, 4, 4] -> [B, 1, 1, 1] + nn.Conv2D(64 * 4, 1, 4, 1, 0, bias_attr=False), + # 这里为需要改变的地方 + nn.Sigmoid() + ) + + def forward(self, x): + return self.dis(x) + +netD = Discriminator() +netD.apply(weights_init) +print(netD) +``` + + +```python +# Initialize BCELoss function +# 这里为需要改变的地方 +loss = nn.BCELoss() + +# Create batch of latent vectors that we will use to visualize +# the progression of the generator +fixed_noise = paddle.randn([32, 100, 1, 1], dtype='float32') + +# Establish convention for real and fake labels during training +real_label = 1. +fake_label = 0. + +# Setup Adam optimizers for both G and D +optimizerD = optim.Adam(parameters=netD.parameters(), learning_rate=0.0002, beta1=0.5, beta2=0.999) +optimizerG = optim.Adam(parameters=netG.parameters(), learning_rate=0.0002, beta1=0.5, beta2=0.999) + +``` + + +```python +losses = [[], []] +#plt.ion() +now = 0 +for pass_id in range(100): + for batch_id, (data, target) in enumerate(dataloader): + ############################ + # (1) Update D network: maximize log(D(x)) + log(1 - D(G(z))) + ########################### + + optimizerD.clear_grad() + real_img = data + bs_size = real_img.shape[0] + label = paddle.full((bs_size, 1, 1, 1), real_label, dtype='float32') + real_out = netD(real_img) + errD_real = loss(real_out, label) + errD_real.backward() + + noise = paddle.randn([bs_size, 100, 1, 1], 'float32') + fake_img = netG(noise) + label = paddle.full((bs_size, 1, 1, 1), fake_label, dtype='float32') + fake_out = netD(fake_img.detach()) + errD_fake = loss(fake_out,label) + errD_fake.backward() + optimizerD.step() + optimizerD.clear_grad() + + errD = errD_real + errD_fake + losses[0].append(errD.numpy()[0]) + + ############################ + # (2) Update G network: maximize log(D(G(z))) + ########################### + optimizerG.clear_grad() + noise = paddle.randn([bs_size, 100, 1, 1],'float32') + fake = netG(noise) + label = paddle.full((bs_size, 1, 1, 1), real_label, dtype=np.float32,) + output = netD(fake) + errG = loss(output,label) + errG.backward() + optimizerG.step() + optimizerG.clear_grad() + + losses[1].append(errG.numpy()[0]) + + + ############################ + # visualize + ########################### + if batch_id % 100 == 0: + generated_image = netG(noise).numpy() + imgs = [] + plt.figure(figsize=(15,15)) + try: + for i in range(10): + image = generated_image[i].transpose() + image = np.where(image > 0, image, 0) + image = image.transpose((1,0,2)) + plt.subplot(10, 10, i + 1) + + plt.imshow(image[...,0], vmin=-1, vmax=1) + plt.axis('off') + plt.xticks([]) + plt.yticks([]) + plt.subplots_adjust(wspace=0.1, hspace=0.1) + msg = 'Epoch ID={0} Batch ID={1} \n\n D-Loss={2} G-Loss={3}'.format(pass_id, batch_id, errD.numpy()[0], errG.numpy()[0]) + print(msg) + plt.suptitle(msg,fontsize=20) + plt.draw() + plt.savefig('{}/{:04d}_{:04d}.png'.format('work', pass_id, batch_id), bbox_inches='tight') + plt.pause(0.01) + except IOError: + print(IOError) + paddle.save(netG.state_dict(), "work/generator.params") +``` \ No newline at end of file diff --git a/education/homework3.md b/education/homework3.md new file mode 100644 index 0000000..3d7c54e --- /dev/null +++ b/education/homework3.md @@ -0,0 +1,394 @@ +# Day 3 作业--Pixel2Pixel:人像卡通化 + +经过今天的学习,相信大家对图像翻译、风格迁移有了一定的了解啦,是不是也想自己动手来实现下呢? + +那么,为了满足大家动手实践的愿望,同时为了巩固大家学到的知识,我们Day 3的作业便是带大家完成一遍课程讲解过的应用--**Pixel2Pixel:人像卡通化** + +在本次作业中,大家需要做的是:**补齐代码,跑通训练,提交一张卡通化的成品图,动手完成自己的第一个人像卡通化的应用~** + +![](https://ai-studio-static-online.cdn.bcebos.com/6e3af14bf9f847ab92215753fb3b8f61a66186b538f44da78ca56627c35717b8) + +## 准备工作:引入依赖 & 数据准备 + + +```python +import paddle +import paddle.nn as nn +from paddle.io import Dataset, DataLoader + +import os +import cv2 +import numpy as np +from tqdm import tqdm +import matplotlib.pyplot as plt + +%matplotlib inline +``` + +### 数据准备: + +- 真人数据来自[seeprettyface](http://www.seeprettyface.com/mydataset.html)。 +- 数据预处理(详情见[photo2cartoon](https://github.com/minivision-ai/photo2cartoon)项目)。 + +
+ +
+ + +- 使用[photo2cartoon](https://github.com/minivision-ai/photo2cartoon)项目生成真人数据对应的卡通数据。 + + +```python +# 解压数据 +!unzip -q data/data79149/cartoon_A2B.zip -d data/ +``` + +### 数据可视化 + + +```python +# 训练数据统计 +train_names = os.listdir('data/cartoon_A2B/train') +print(f'训练集数据量: {len(train_names)}') + +# 测试数据统计 +test_names = os.listdir('data/cartoon_A2B/test') +print(f'测试集数据量: {len(test_names)}') + +# 训练数据可视化 +imgs = [] +for img_name in np.random.choice(train_names, 3, replace=False): + imgs.append(cv2.imread('data/cartoon_A2B/train/'+img_name)) + +img_show = np.vstack(imgs)[:,:,::-1] +plt.figure(figsize=(10, 10)) +plt.imshow(img_show) +plt.show() +``` + + +```python +class PairedData(Dataset): + def __init__(self, phase): + super(PairedData, self).__init__() + self.img_path_list = self.load_A2B_data(phase) # 获取数据列表 + self.num_samples = len(self.img_path_list) # 数据量 + + def __getitem__(self, idx): + img_A2B = # 读取一组数据 + img_A2B = # 从0~255归一化至-1~1 + img_A2B = # 维度变换HWC -> CHW + img_A = # 真人照 + img_B = # 卡通图 + return img_A, img_B + + def __len__(self): + return self.num_samples + + @staticmethod + def load_A2B_data(phase): + assert phase in ['train', 'test'], "phase should be set within ['train', 'test']" + # 读取数据集,数据中每张图像包含照片和对应的卡通画。 + data_path = 'data/cartoon_A2B/'+phase + return [os.path.join(data_path, x) for x in os.listdir(data_path)] +``` + + +```python +paired_dataset_train = PairedData('train') +paired_dataset_test = PairedData('test') +``` + +## 第一步:搭建生成器 + +### 请大家补齐空白处的代码,‘#’ 后是提示。 + + +```python +class UnetGenerator(nn.Layer): + def __init__(self, input_nc=3, output_nc=3, ngf=64): + super(UnetGenerator, self).__init__() + + self.down1 = nn.Conv2D(input_nc, ngf, kernel_size=4, stride=2, padding=1) + self.down2 = Downsample(ngf, ngf*2) + self.down3 = Downsample(ngf*2, ngf*4) + self.down4 = Downsample(ngf*4, ngf*8) + self.down5 = Downsample(ngf*8, ngf*8) + self.down6 = Downsample(ngf*8, ngf*8) + self.down7 = Downsample(ngf*8, ngf*8) + + self.center = Downsample(ngf*8, ngf*8) + + self.up7 = Upsample(ngf*8, ngf*8, use_dropout=True) + self.up6 = Upsample(ngf*8*2, ngf*8, use_dropout=True) + self.up5 = Upsample(ngf*8*2, ngf*8, use_dropout=True) + self.up4 = Upsample(ngf*8*2, ngf*8) + self.up3 = Upsample(ngf*8*2, ngf*4) + self.up2 = Upsample(ngf*4*2, ngf*2) + self.up1 = Upsample(ngf*2*2, ngf) + + self.output_block = nn.Sequential( + nn.ReLU(), + nn.Conv2DTranspose(ngf*2, output_nc, kernel_size=4, stride=2, padding=1), + nn.Tanh() + ) + + def forward(self, x): + d1 = self.down1(x) + d2 = self.down2(d1) + d3 = self.down3(d2) + d4 = self.down4(d3) + d5 = self.down5(d4) + d6 = self.down6(d5) + d7 = self.down7(d6) + + c = self.center(d7) + + x = self.up7(c, d7) + x = self.up6(x, d6) + x = self.up5(x, d5) + x = self.up4(x, d4) + x = self.up3(x, d3) + x = self.up2(x, d2) + x = self.up1(x, d1) + + x = self.output_block(x) + return x + + +class Downsample(nn.Layer): + # LeakyReLU => conv => batch norm + def __init__(self, in_dim, out_dim, kernel_size=4, stride=2, padding=1): + super(Downsample, self).__init__() + + self.layers = nn.Sequential( + # LeakyReLU, leaky=0.2 + # Conv2D + # BatchNorm2D + ) + + def forward(self, x): + x = self.layers(x) + return x + + +class Upsample(nn.Layer): + # ReLU => deconv => batch norm => dropout + def __init__(self, in_dim, out_dim, kernel_size=4, stride=2, padding=1, use_dropout=False): + super(Upsample, self).__init__() + + sequence = [ + # ReLU + # Conv2DTranspose + # nn.BatchNorm2D + ] + + if use_dropout: + sequence.append(nn.Dropout(p=0.5)) + + self.layers = nn.Sequential(*sequence) + + def forward(self, x, skip): + x = self.layers(x) + x = paddle.concat([x, skip], axis=1) + return x +``` + +## 第二步:鉴别器的搭建 + +### 请大家补齐空白处的代码,‘#’ 后是提示。 + + +```python +class NLayerDiscriminator(nn.Layer): + def __init__(self, input_nc=6, ndf=64): + super(NLayerDiscriminator, self).__init__() + + self.layers = nn.Sequential( + nn.Conv2D(input_nc, ndf, kernel_size=4, stride=2, padding=1), + nn.LeakyReLU(0.2), + + ConvBlock(ndf, ndf*2), + ConvBlock(ndf*2, ndf*4), + ConvBlock(ndf*4, ndf*8, stride=1), + + nn.Conv2D(ndf*8, 1, kernel_size=4, stride=1, padding=1), + nn.Sigmoid() + ) + + def forward(self, input): + return self.layers(input) + + +class ConvBlock(nn.Layer): + # conv => batch norm => LeakyReLU + def __init__(self, in_dim, out_dim, kernel_size=4, stride=2, padding=1): + super(ConvBlock, self).__init__() + + self.layers = nn.Sequential( + # Conv2D + # BatchNorm2D + # LeakyReLU, leaky=0.2 + ) + + def forward(self, x): + x = self.layers(x) + return x +``` + + +```python +generator = UnetGenerator() +discriminator = NLayerDiscriminator() +``` + + +```python +out = generator(paddle.ones([1, 3, 256, 256])) +print('生成器输出尺寸:', out.shape) # 应为[1, 3, 256, 256] + +out = discriminator(paddle.ones([1, 6, 256, 256])) +print('鉴别器输出尺寸:', out.shape) # 应为[1, 1, 30, 30] +``` + + +```python +# 超参数 +LR = 1e-4 +BATCH_SIZE = 8 +EPOCHS = 100 + +# 优化器 +optimizerG = paddle.optimizer.Adam( + learning_rate=LR, + parameters=generator.parameters(), + beta1=0.5, + beta2=0.999) + +optimizerD = paddle.optimizer.Adam( + learning_rate=LR, + parameters=discriminator.parameters(), + beta1=0.5, + beta2=0.999) + +# 损失函数 +bce_loss = +l1_loss = + +# dataloader +data_loader_train = DataLoader( + paired_dataset_train, + batch_size=BATCH_SIZE, + shuffle=True, + drop_last=True + ) + +data_loader_test = DataLoader( + paired_dataset_test, + batch_size=BATCH_SIZE + ) +``` + + +```python +results_save_path = 'work/results' +os.makedirs(results_save_path, exist_ok=True) # 保存每个epoch的测试结果 + +weights_save_path = 'work/weights' +os.makedirs(weights_save_path, exist_ok=True) # 保存模型 + +for epoch in range(EPOCHS): + for data in tqdm(data_loader_train): + real_A, real_B = data + + optimizerD.clear_grad() + # D([real_A, real_B]) + real_AB = paddle.concat((real_A, real_B), 1) + d_real_predict = discriminator(real_AB) + d_real_loss = bce_loss(d_real_predict, paddle.ones_like(d_real_predict)) + + # D([real_A, fake_B]) + fake_B = + fake_AB = + d_fake_predict = + d_fake_loss = + + # train D + d_loss = (d_real_loss + d_fake_loss) / 2. + d_loss.backward() + optimizerD.step() + + optimizerG.clear_grad() + # D([real_A, fake_B]) + fake_B = + fake_AB = + g_fake_predict = + g_bce_loss = + g_l1_loss = + g_loss = g_bce_loss + g_l1_loss * 100. + + # train G + g_loss.backward() + optimizerG.step() + + print(f'Epoch [{epoch+1}/{EPOCHS}] Loss D: {d_loss.numpy()}, Loss G: {g_loss.numpy()}') + + if (epoch+1) % 10 == 0: + paddle.save(generator.state_dict(), os.path.join(weights_save_path, 'epoch'+str(epoch+1).zfill(3)+'.pdparams')) + + # test + generator.eval() + with paddle.no_grad(): + for data in data_loader_test: + real_A, real_B = data + break + + fake_B = generator(real_A) + result = paddle.concat([real_A[:3], real_B[:3], fake_B[:3]], 3) + + result = result.detach().numpy().transpose(0, 2, 3, 1) + result = np.vstack(result) + result = (result * 127.5 + 127.5).astype(np.uint8) + + cv2.imwrite(os.path.join(results_save_path, 'epoch'+str(epoch+1).zfill(3)+'.png'), result) + + generator.train() +``` + +## 最后:用你补齐的代码试试卡通化的效果吧! + + +```python +# 为生成器加载权重 +last_weights_path = os.path.join(weights_save_path, sorted(os.listdir(weights_save_path))[-1]) +print('加载权重:', last_weights_path) + +model_state_dict = paddle.load(last_weights_path) +generator.load_dict(model_state_dict) +generator.eval() +``` + + +```python +# 读取数据 +test_names = os.listdir('data/cartoon_A2B/test') +img_name = np.random.choice(test_names) +img_A2B = cv2.imread('data/cartoon_A2B/test/'+img_name) +img_A = img_A2B[:, :256] # 真人照 +img_B = img_A2B[:, 256:] # 卡通图 + +g_input = img_A.astype('float32') / 127.5 - 1 # 归一化 +g_input = g_input[np.newaxis, ...].transpose(0, 3, 1, 2) # NHWC -> NCHW +g_input = paddle.to_tensor(g_input) # numpy -> tensor + +g_output = generator(g_input) +g_output = g_output.detach().numpy() # tensor -> numpy +g_output = g_output.transpose(0, 2, 3, 1)[0] # NCHW -> NHWC +g_output = g_output * 127.5 + 127.5 # 反归一化 +g_output = g_output.astype(np.uint8) + +img_show = np.hstack([img_A, g_output])[:,:,::-1] +plt.figure(figsize=(8, 8)) +plt.imshow(img_show) +plt.show() +``` \ No newline at end of file -- GitLab