Files
2026-07-13 12:40:42 +08:00

706 lines
21 KiB
Python

# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import random
import time
import unittest
import numpy as np
from PIL import Image, ImageOps
from paddle import base
# Use GPU:0 to eliminate the influence of other tasks.
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
from dygraph_to_static_utils import (
Dy2StTestBase,
enable_to_static_guard,
)
import paddle
from paddle.nn import BatchNorm
# Note: Set True to eliminate randomness.
# 1. For one operation, cuDNN has several algorithms,
# some algorithm results are non-deterministic, like convolution algorithms.
# 2. If include BatchNorm, please set `use_global_stats=True` to avoid using
# cudnnBatchNormalizationBackward which is non-deterministic.
if base.is_compiled_with_cuda():
base.set_flags({'FLAGS_cudnn_deterministic': True})
# set False to speed up training.
use_cudnn = False
step_per_epoch = 10
lambda_A = 10.0
lambda_B = 10.0
lambda_identity = 0.5
# TODO(Aurelius84): Modify it into 256 when we move ut into CE platform.
# It will lead to timeout if set 256 in CI.
IMAGE_SIZE = 64
SEED = 2020
class Cycle_Gan(paddle.nn.Layer):
def __init__(self, input_channel, istrain=True):
super().__init__()
self.build_generator_resnet_9blocks_a = build_generator_resnet_9blocks(
input_channel
)
self.build_generator_resnet_9blocks_b = build_generator_resnet_9blocks(
input_channel
)
if istrain:
self.build_gen_discriminator_a = build_gen_discriminator(
input_channel
)
self.build_gen_discriminator_b = build_gen_discriminator(
input_channel
)
def forward(self, input_A, input_B):
"""
Generator of GAN model.
"""
fake_B = self.build_generator_resnet_9blocks_a(input_A)
fake_A = self.build_generator_resnet_9blocks_b(input_B)
cyc_A = self.build_generator_resnet_9blocks_b(fake_B)
cyc_B = self.build_generator_resnet_9blocks_a(fake_A)
diff_A = paddle.abs(paddle.subtract(x=input_A, y=cyc_A))
diff_B = paddle.abs(paddle.subtract(x=input_B, y=cyc_B))
cyc_A_loss = paddle.mean(diff_A) * lambda_A
cyc_B_loss = paddle.mean(diff_B) * lambda_B
cyc_loss = cyc_A_loss + cyc_B_loss
fake_rec_A = self.build_gen_discriminator_a(fake_B)
g_A_loss = paddle.mean(paddle.square(fake_rec_A - 1))
fake_rec_B = self.build_gen_discriminator_b(fake_A)
g_B_loss = paddle.mean(paddle.square(fake_rec_B - 1))
G = g_A_loss + g_B_loss
idt_A = self.build_generator_resnet_9blocks_a(input_B)
idt_loss_A = (
paddle.mean(paddle.abs(paddle.subtract(x=input_B, y=idt_A)))
* lambda_B
* lambda_identity
)
idt_B = self.build_generator_resnet_9blocks_b(input_A)
idt_loss_B = (
paddle.mean(paddle.abs(paddle.subtract(x=input_A, y=idt_B)))
* lambda_A
* lambda_identity
)
idt_loss = paddle.add(idt_loss_A, idt_loss_B)
g_loss = cyc_loss + G + idt_loss
return (
fake_A,
fake_B,
cyc_A,
cyc_B,
g_A_loss,
g_B_loss,
idt_loss_A,
idt_loss_B,
cyc_A_loss,
cyc_B_loss,
g_loss,
)
def discriminatorA(self, input_A, input_B):
"""
Discriminator A of GAN model.
"""
rec_B = self.build_gen_discriminator_a(input_A)
fake_pool_rec_B = self.build_gen_discriminator_a(input_B)
return rec_B, fake_pool_rec_B
def discriminatorB(self, input_A, input_B):
"""
Discriminator B of GAN model.
"""
rec_A = self.build_gen_discriminator_b(input_A)
fake_pool_rec_A = self.build_gen_discriminator_b(input_B)
return rec_A, fake_pool_rec_A
class build_resnet_block(paddle.nn.Layer):
def __init__(self, dim, use_bias=False):
super().__init__()
self.conv0 = conv2d(
num_channels=dim,
num_filters=dim,
filter_size=3,
stride=1,
stddev=0.02,
use_bias=False,
)
self.conv1 = conv2d(
num_channels=dim,
num_filters=dim,
filter_size=3,
stride=1,
stddev=0.02,
relu=False,
use_bias=False,
)
self.dim = dim
def forward(self, inputs):
pad1 = paddle.nn.Pad2D([1, 1, 1, 1], mode="reflect")
out_res = pad1(inputs)
out_res = self.conv0(out_res)
pad2 = paddle.nn.Pad2D([1, 1, 1, 1], mode="reflect")
out_res = pad2(out_res)
out_res = self.conv1(out_res)
return out_res + inputs
class build_generator_resnet_9blocks(paddle.nn.Layer):
def __init__(self, input_channel):
super().__init__()
self.conv0 = conv2d(
num_channels=input_channel,
num_filters=32,
filter_size=7,
stride=1,
padding=0,
stddev=0.02,
)
self.conv1 = conv2d(
num_channels=32,
num_filters=64,
filter_size=3,
stride=2,
padding=1,
stddev=0.02,
)
self.conv2 = conv2d(
num_channels=64,
num_filters=128,
filter_size=3,
stride=2,
padding=1,
stddev=0.02,
)
self.build_resnet_block_list = []
dim = 128
for i in range(9):
Build_Resnet_Block = self.add_sublayer(
f"generator_{i + 1}", build_resnet_block(dim)
)
self.build_resnet_block_list.append(Build_Resnet_Block)
self.deconv0 = DeConv2D(
num_channels=dim,
num_filters=32 * 2,
filter_size=3,
stride=2,
stddev=0.02,
padding=[1, 1],
outpadding=[0, 1, 0, 1],
)
self.deconv1 = DeConv2D(
num_channels=32 * 2,
num_filters=32,
filter_size=3,
stride=2,
stddev=0.02,
padding=[1, 1],
outpadding=[0, 1, 0, 1],
)
self.conv3 = conv2d(
num_channels=32,
num_filters=input_channel,
filter_size=7,
stride=1,
stddev=0.02,
padding=0,
relu=False,
norm=False,
use_bias=True,
)
def forward(self, inputs):
pad1 = paddle.nn.Pad2D([3, 3, 3, 3], mode="reflect")
pad_input = pad1(inputs)
y = self.conv0(pad_input)
y = self.conv1(y)
y = self.conv2(y)
for build_resnet_block_i in self.build_resnet_block_list:
y = build_resnet_block_i(y)
y = self.deconv0(y)
y = self.deconv1(y)
pad2 = paddle.nn.Pad2D([3, 3, 3, 3], mode="reflect")
y = pad2(y)
y = self.conv3(y)
y = paddle.tanh(y)
return y
class build_gen_discriminator(paddle.nn.Layer):
def __init__(self, input_channel):
super().__init__()
self.conv0 = conv2d(
num_channels=input_channel,
num_filters=64,
filter_size=4,
stride=2,
stddev=0.02,
padding=1,
norm=False,
use_bias=True,
relufactor=0.2,
)
self.conv1 = conv2d(
num_channels=64,
num_filters=128,
filter_size=4,
stride=2,
stddev=0.02,
padding=1,
relufactor=0.2,
)
self.conv2 = conv2d(
num_channels=128,
num_filters=IMAGE_SIZE,
filter_size=4,
stride=2,
stddev=0.02,
padding=1,
relufactor=0.2,
)
self.conv3 = conv2d(
num_channels=IMAGE_SIZE,
num_filters=512,
filter_size=4,
stride=1,
stddev=0.02,
padding=1,
relufactor=0.2,
)
self.conv4 = conv2d(
num_channels=512,
num_filters=1,
filter_size=4,
stride=1,
stddev=0.02,
padding=1,
norm=False,
relu=False,
use_bias=True,
)
def forward(self, inputs):
y = self.conv0(inputs)
y = self.conv1(y)
y = self.conv2(y)
y = self.conv3(y)
y = self.conv4(y)
return y
class conv2d(paddle.nn.Layer):
"""docstring for Conv2D"""
def __init__(
self,
num_channels,
num_filters=64,
filter_size=7,
stride=1,
stddev=0.02,
padding=0,
norm=True,
relu=True,
relufactor=0.0,
use_bias=False,
):
super().__init__()
if not use_bias:
con_bias_attr = False
else:
con_bias_attr = base.ParamAttr(
initializer=paddle.nn.initializer.Constant(0.0)
)
self.conv = paddle.nn.Conv2D(
in_channels=num_channels,
out_channels=num_filters,
kernel_size=filter_size,
stride=stride,
padding=padding,
weight_attr=paddle.ParamAttr(
initializer=paddle.nn.initializer.Normal(mean=0.0, std=stddev)
),
bias_attr=con_bias_attr,
)
# Note(Aurelius84): The calculation of GPU kernel in BN is non-deterministic,
# failure rate is 1/100 in Dev but seems incremental in CE platform.
# If on GPU, we disable BN temporarily.
if base.is_compiled_with_cuda():
norm = False
if norm:
self.bn = BatchNorm(
use_global_stats=True, # set True to use deterministic algorithm
num_channels=num_filters,
param_attr=base.ParamAttr(
initializer=paddle.nn.initializer.Normal(1.0, 0.02)
),
bias_attr=base.ParamAttr(
initializer=paddle.nn.initializer.Constant(0.0)
),
trainable_statistics=True,
)
self.relufactor = relufactor
self.use_bias = use_bias
self.norm = norm
self.relu = relu
def forward(self, inputs):
conv = self.conv(inputs)
if self.norm:
conv = self.bn(conv)
if self.relu:
conv = paddle.nn.functional.leaky_relu(conv, self.relufactor)
return conv
class DeConv2D(paddle.nn.Layer):
def __init__(
self,
num_channels,
num_filters=64,
filter_size=7,
stride=1,
stddev=0.02,
padding=[0, 0],
outpadding=[0, 0, 0, 0],
relu=True,
norm=True,
relufactor=0.0,
use_bias=False,
):
super().__init__()
if not use_bias:
de_bias_attr = False
else:
de_bias_attr = base.ParamAttr(
initializer=paddle.nn.initializer.Constant(0.0)
)
self._deconv = paddle.nn.Conv2DTranspose(
num_channels,
num_filters,
filter_size,
stride=stride,
padding=padding,
weight_attr=base.ParamAttr(
initializer=paddle.nn.initializer.Normal(mean=0.0, std=stddev)
),
bias_attr=de_bias_attr,
)
if base.is_compiled_with_cuda():
norm = False
if norm:
self.bn = BatchNorm(
use_global_stats=True, # set True to use deterministic algorithm
num_channels=num_filters,
param_attr=base.ParamAttr(
initializer=paddle.nn.initializer.Normal(1.0, 0.02)
),
bias_attr=base.ParamAttr(
initializer=paddle.nn.initializer.Constant(0.0)
),
trainable_statistics=True,
)
self.outpadding = outpadding
self.relufactor = relufactor
self.use_bias = use_bias
self.norm = norm
self.relu = relu
def forward(self, inputs):
conv = self._deconv(inputs)
tmp_pad = paddle.nn.Pad2D(
padding=self.outpadding, mode='constant', value=0.0
)
conv = tmp_pad(conv)
if self.norm:
conv = self.bn(conv)
if self.relu:
conv = paddle.nn.functional.leaky_relu(conv, self.relufactor)
return conv
class ImagePool:
def __init__(self, pool_size=50):
self.pool = []
self.count = 0
self.pool_size = pool_size
def pool_image(self, image):
if self.count < self.pool_size:
self.pool.append(image)
self.count += 1
return image
else:
p = np.random.rand()
if p > 0.5:
random_id = np.random.randint(0, self.pool_size - 1)
temp = self.pool[random_id]
self.pool[random_id] = image
return temp
else:
return image
def reader_creator():
def reader():
while True:
fake_image = np.uint8(
np.random.random((IMAGE_SIZE + 30, IMAGE_SIZE + 30, 3)) * 255
)
image = Image.fromarray(fake_image)
# Resize
image = image.resize((286, 286), Image.BICUBIC)
# RandomCrop
i = np.random.randint(0, 30)
j = np.random.randint(0, 30)
image = image.crop((i, j, i + IMAGE_SIZE, j + IMAGE_SIZE))
# RandomHorizontalFlip
sed = np.random.rand()
if sed > 0.5:
image = ImageOps.mirror(image)
# ToTensor
image = np.array(image).transpose([2, 0, 1]).astype('float32')
image = image / 255.0
# Normalize, mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5]
image = (image - 0.5) / 0.5
yield image
return reader
class Args:
epoch = 1
batch_size = 4
image_shape = [3, IMAGE_SIZE, IMAGE_SIZE]
max_images_num = step_per_epoch
log_step = 1
train_step = 3
def optimizer_setting(parameters):
lr = 0.0002
optimizer = paddle.optimizer.Adam(
learning_rate=paddle.optimizer.lr.PiecewiseDecay(
boundaries=[
100 * step_per_epoch,
120 * step_per_epoch,
140 * step_per_epoch,
160 * step_per_epoch,
180 * step_per_epoch,
],
values=[lr, lr * 0.8, lr * 0.6, lr * 0.4, lr * 0.2, lr * 0.1],
),
parameters=parameters,
beta1=0.5,
)
return optimizer
def train(args):
place = (
base.CUDAPlace(0) if base.is_compiled_with_cuda() else base.CPUPlace()
)
with base.dygraph.guard(place):
max_images_num = args.max_images_num
data_shape = [-1, *args.image_shape]
random.seed(SEED)
np.random.seed(SEED)
paddle.seed(SEED)
A_pool = ImagePool()
B_pool = ImagePool()
A_reader = paddle.batch(reader_creator(), args.batch_size)()
B_reader = paddle.batch(reader_creator(), args.batch_size)()
cycle_gan = paddle.jit.to_static(
Cycle_Gan(input_channel=data_shape[1], istrain=True)
)
t_time = 0
vars_G = (
cycle_gan.build_generator_resnet_9blocks_a.parameters()
+ cycle_gan.build_generator_resnet_9blocks_b.parameters()
)
vars_da = cycle_gan.build_gen_discriminator_a.parameters()
vars_db = cycle_gan.build_gen_discriminator_b.parameters()
optimizer1 = optimizer_setting(vars_G)
optimizer2 = optimizer_setting(vars_da)
optimizer3 = optimizer_setting(vars_db)
loss_data = []
for epoch in range(args.epoch):
for batch_id in range(max_images_num):
data_A = next(A_reader)
data_B = next(B_reader)
s_time = time.time()
data_A = np.array(
[data_A[0].reshape(3, IMAGE_SIZE, IMAGE_SIZE)]
).astype("float32")
data_B = np.array(
[data_B[0].reshape(3, IMAGE_SIZE, IMAGE_SIZE)]
).astype("float32")
data_A = paddle.to_tensor(data_A)
data_B = paddle.to_tensor(data_B)
# optimize the g_A network
(
fake_A,
fake_B,
cyc_A,
cyc_B,
g_A_loss,
g_B_loss,
idt_loss_A,
idt_loss_B,
cyc_A_loss,
cyc_B_loss,
g_loss,
) = cycle_gan(data_A, data_B)
g_loss.backward()
optimizer1.minimize(g_loss)
cycle_gan.clear_gradients()
fake_pool_B = B_pool.pool_image(fake_B).numpy()
fake_pool_B = np.array(
[fake_pool_B[0].reshape(3, IMAGE_SIZE, IMAGE_SIZE)]
).astype("float32")
fake_pool_B = paddle.to_tensor(fake_pool_B)
fake_pool_A = A_pool.pool_image(fake_A).numpy()
fake_pool_A = np.array(
[fake_pool_A[0].reshape(3, IMAGE_SIZE, IMAGE_SIZE)]
).astype("float32")
fake_pool_A = paddle.to_tensor(fake_pool_A)
# optimize the d_A network
discriminatorA_to_static = paddle.jit.to_static(
cycle_gan.discriminatorA
)
rec_B, fake_pool_rec_B = discriminatorA_to_static(
data_B, fake_pool_B
)
d_loss_A = (
paddle.square(fake_pool_rec_B) + paddle.square(rec_B - 1)
) / 2.0
d_loss_A = paddle.mean(d_loss_A)
d_loss_A.backward()
optimizer2.minimize(d_loss_A)
cycle_gan.clear_gradients()
# optimize the d_B network
discriminatorB_to_static = paddle.jit.to_static(
cycle_gan.discriminatorB
)
rec_A, fake_pool_rec_A = discriminatorB_to_static(
data_A, fake_pool_A
)
d_loss_B = (
paddle.square(fake_pool_rec_A) + paddle.square(rec_A - 1)
) / 2.0
d_loss_B = paddle.mean(d_loss_B)
d_loss_B.backward()
optimizer3.minimize(d_loss_B)
cycle_gan.clear_gradients()
# Log generator loss and discriminator loss
cur_batch_loss = [
g_loss,
d_loss_A,
d_loss_B,
g_A_loss,
cyc_A_loss,
idt_loss_A,
g_B_loss,
cyc_B_loss,
idt_loss_B,
]
cur_batch_loss = [float(x) for x in cur_batch_loss]
batch_time = time.time() - s_time
t_time += batch_time
if batch_id % args.log_step == 0:
print(
"batch: {}\t Batch_time_cost: {}\n g_loss: {}\t d_A_loss: {}\t d_B_loss:{}\n g_A_loss: {}\t g_A_cyc_loss: {}\t g_A_idt_loss: {}\n g_B_loss: {}\t g_B_cyc_loss: {}\t g_B_idt_loss: {}".format(
batch_id, batch_time, *cur_batch_loss
)
)
if batch_id > args.train_step:
break
loss_data.append(cur_batch_loss)
return np.array(loss_data)
class TestCycleGANModel(Dy2StTestBase):
def setUp(self):
self.args = Args()
def train(self, to_static):
with enable_to_static_guard(to_static):
out = train(self.args)
return out
def test_train(self):
st_out = self.train(to_static=True)
dy_out = self.train(to_static=False)
# Note(Aurelius84): Because we disable BN on GPU,
# but here we enhance the check on CPU by `np.array_equal`
# which means the dy_out and st_out shall be exactly same.
if not base.is_compiled_with_cuda():
np.testing.assert_array_equal(dy_out, st_out)
else:
np.testing.assert_allclose(dy_out, st_out, rtol=1e-5, atol=1e-8)
if __name__ == "__main__":
unittest.main()