生成对抗网络项目:6~9(1)https://developer.aliyun.com/article/1426897
对抗模型
要创建对抗模型,请同时使用生成器网络和判别器网络,并创建一个新的 Keras 模型。
- 首先创建三个输入层以将输入馈送到网络:
def build_adversarial_model(gen_model, dis_model): input_layer = Input(shape=(1024,)) input_layer2 = Input(shape=(100,)) input_layer3 = Input(shape=(4, 4, 128))
- 然后,使用生成器网络生成低分辨率图像:
# Get output of the generator model x, mean_logsigma = gen_model([input_layer, input_layer2]) # Make discriminator trainable false dis_model.trainable = False
- 接下来,使用判别器网络获得概率:
# Get output of the discriminator models valid = dis_model([x, input_layer3])
- 最后,创建对抗模型,获取三个输入并返回两个输出。
model = Model(inputs=[input_layer, input_layer2, input_layer3], outputs=[valid, mean_logsigma]) return model
现在,我们的对抗模型已经准备就绪。 这种对抗模型是一种端到端的可训练模型。 在本节中,我们研究了 StackGAN 模型的第一阶段中涉及的网络。 在下一部分中,我们将研究 StackGAN 的第二阶段所涉及的网络的实现。
第二阶段
第二阶段 StackGAN 与第一阶段 StackGAN 略有不同。 生成器模型的输入是条件变量(c_hat[0]
)和生成器网络在第一阶段中生成的低分辨率图像。
它包含五个组成部分:
- 文字编码器
- 条件增强网络
- 下采样块
- 残差块
- 上采样块
文本编码器和 CA 网络与之前在第一阶段部分中使用的相似。 现在,我们将介绍生成器网络的三个组件,分别是下采样块,残差块和上采样块。
生成器网络
生成器网络由三个不同的模块组成。 我们将逐一编写每个模块的代码。 让我们从下采样模块开始。
下采样块
该块从第一阶段的生成器获取大小为64x64x3
的低分辨率图像,并将其下采样以生成形状为16x16x512
的张量。 图像经过一系列 2D 卷积块。
在本节中,我们将为降采样模块编写实现。
- 首先创建第一个下采样块。 该块包含一个以
ReLU
作为激活函数的 2D 卷积层。 在应用 2D 卷积之前,请在各侧用零填充输入。 该块中不同层的配置如下:
- 填充大小:
(1, 1)
- 过滤器:
128
- 核大小:
(3, 3)
- 步幅:
1
- 激活:
ReLU
x = ZeroPadding2D(padding=(1, 1))(input_lr_images) x = Conv2D(128, kernel_size=(3, 3), strides=1, use_bias=False)(x) x = ReLU()(x)
- 接下来,添加具有以下配置的第二个卷积块:
- 填充大小:
(1, 1)
- 过滤器:
256
- 核大小:
(4, 4)
- 步幅:
2
- 批量规范化:是
- 激活:
ReLU
x = ZeroPadding2D(padding=(1, 1))(x) x = Conv2D(256, kernel_size=(4, 4), strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x)
- 之后,添加另一个具有以下配置的卷积块:
- 填充大小:
(1, 1)
- 过滤器:
512
- 核大小:
(4, 4)
- 步幅:
2
- 批量规范化:是
- 激活:
ReLU
x = ZeroPadding2D(padding=(1, 1))(x) x = Conv2D(512, kernel_size=(4, 4), strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x)
下采样块生成形状为16x16x512
的张量。 之后,我们有一系列残差块。 在将该张量馈送到残差块之前,我们需要将其连接到文本条件变量。 执行此操作的代码如下:
# This block will extend the text conditioning variable and concatenate it to the encoded images tensor. def joint_block(inputs): c = inputs[0] x = inputs[1] c = K.expand_dims(c, axis=1) c = K.expand_dims(c, axis=1) c = K.tile(c, [1, 16, 16, 1]) return K.concatenate([c, x], axis=3) # This is the lambda layer which we will add to the generator network c_code = Lambda(joint_block)([c, x])
在此,c
的形状为(batch_size, 228)
,x
的形状为(batch_size, 16, 16, 512)
。 c_code
的形状为(batch_size, 640)
。
残差块
残差块包含两个 2D 卷积层,每个层之后是批量归一化层和一个激活函数。
- 让我们定义残差块。 此代码完全描述了残差块:
def residual_block(input): """ Residual block in the generator network :return: """ x = Conv2D(128 * 4, kernel_size=(3, 3), padding='same', strides=1)(input) x = BatchNormalization()(x) x = ReLU()(x) x = Conv2D(128 * 4, kernel_size=(3, 3), strides=1, padding='same')(x) x = BatchNormalization()(x) x = add([x, input]) x = ReLU()(x) return x
初始输入被添加到第二个 2D 卷积层的输出中。 结果张量将是块的输出。
- 接下来,添加具有以下超参数的 2D 卷积块:
- 填充大小:
(1, 1)
- 过滤器:
512
- 核大小:
(3, 3)
- 步幅:
1
- 批量规范化:
Yes
- 激活:
ReLU
x = ZeroPadding2D(padding=(1, 1))(c_code) x = Conv2D(512, kernel_size=(3, 3), strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x)
- 之后,依次添加四个残差块:
x = residual_block(x) x = residual_block(x) x = residual_block(x) x = residual_block(x)
上采样块将从残差块接收该输出张量。 让我们为上采样块编写代码。
上采样块
上采样块包含可提高图像空间分辨率并生成大小256x256x3
的高分辨率图像的层。
让我们为上采样块编写代码:
- 首先,添加一个包含 2D 上采样层,2D 卷积层,批归一化和激活函数的上采样块。 块中使用的不同参数如下:
- 上采样大小:
(2, 2)
- 过滤器:
512
- 核大小:
3
- 填充:
"same"
- 步幅:
1
- 批量规范化:
Yes
- 激活:
ReLU
x = UpSampling2D(size=(2, 2))(x) x = Conv2D(512, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x)
- 接下来,再添加三个上采样块。 该块中使用的超参数可以很容易地从下面给出的代码中推断出来:
x = UpSampling2D(size=(2, 2))(x) x = Conv2D(256, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = UpSampling2D(size=(2, 2))(x) x = Conv2D(128, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = UpSampling2D(size=(2, 2))(x) x = Conv2D(64, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x)
- 添加最后的卷积层。 该层是最后一层,它负责生成高分辨率图像。
x = Conv2D(3, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = Activation('tanh')(x)
最后,使用部分前面的创建生成器模型:
model = Model(inputs=[input_layer, input_lr_images], outputs=[x, mean_logsigma])
现在,我们已经准备好生成器模型,并使用该模型来生成高分辨率图像。 以下是用于生成器网络的完整代码,用于清晰 :
def build_stage2_generator(): """ Create a generator network for Stage-II StackGAN """ # 1\. CA Augementation Network input_layer = Input(shape=(1024,)) input_lr_images = Input(shape=(64, 64, 3)) ca = Dense(256)(input_layer) mean_logsigma = LeakyReLU(alpha=0.2)(ca) c = Lambda(generate_c)(mean_logsigma) # 2\. Image Encoder x = ZeroPadding2D(padding=(1, 1))(input_lr_images) x = Conv2D(128, kernel_size=(3, 3), strides=1, use_bias=False)(x) x = ReLU()(x) x = ZeroPadding2D(padding=(1, 1))(x) x = Conv2D(256, kernel_size=(4, 4), strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = ZeroPadding2D(padding=(1, 1))(x) x = Conv2D(512, kernel_size=(4, 4), strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) # Concatenation block c_code = Lambda(joint_block)([c, x]) # 3\. Residual Blocks x = ZeroPadding2D(padding=(1, 1))(c_code) x = Conv2D(512, kernel_size=(3, 3), strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = residual_block(x) x = residual_block(x) x = residual_block(x) x = residual_block(x) # 4\. Upsampling blocks x = UpSampling2D(size=(2, 2))(x) x = Conv2D(512, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = UpSampling2D(size=(2, 2))(x) x = Conv2D(256, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = UpSampling2D(size=(2, 2))(x) x = Conv2D(128, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = UpSampling2D(size=(2, 2))(x) x = Conv2D(64, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = ReLU()(x) x = Conv2D(3, kernel_size=3, padding="same", strides=1, use_bias=False)(x) x = Activation('tanh')(x) model = Model(inputs=[input_layer, input_lr_images], outputs=[x, mean_logsigma]) return model
判别器网络
第二阶段 StackGAN 的判别器网络是一系列下采样层,然后是连接块,然后是分类器。 让我们为每个块编写代码。
首先创建输入层,如下所示:
input_layer = Input(shape=(256, 256, 3))
下采样块
下采样块具有对图像进行下采样的几层。
首先在下采样模块中添加不同的层。 本节中的代码是非常说明性的,可以轻松理解:
x = Conv2D(64, (4, 4), padding='same', strides=2, input_shape=(256, 256, 3), use_bias=False)(input_layer) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(128, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(256, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(512, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(1024, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(2048, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(1024, (1, 1), padding='same', strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(512, (1, 1), padding='same', strides=1, use_bias=False)(x) x = BatchNormalization()(x) x2 = Conv2D(128, (1, 1), padding='same', strides=1, use_bias=False)(x) x2 = BatchNormalization()(x2) x2 = LeakyReLU(alpha=0.2)(x2) x2 = Conv2D(128, (3, 3), padding='same', strides=1, use_bias=False)(x2) x2 = BatchNormalization()(x2) x2 = LeakyReLU(alpha=0.2)(x2) x2 = Conv2D(512, (3, 3), padding='same', strides=1, use_bias=False)(x2) x2 = BatchNormalization()(x2)
之后,我们有两个输出,分别是x
和x2
。 将这两个张量相加以创建相同形状的张量。 我们还需要应用LeakyReLU
激活函数:
added_x = add([x, x2]) added_x = LeakyReLU(alpha=0.2)(added_x)
连接块
为空间复制和压缩的嵌入创建另一个输入层:
input_layer2 = Input(shape=(4, 4, 128))
将下采样块的输出连接到空间压缩的嵌入:
input_layer2 = Input(shape=(4, 4, 128)) merged_input = concatenate([added_x, input_layer2])
全连接分类器
然后将合并后的输入馈送到具有一个卷积层和一个密集层的块中,以进行分类:
x3 = Conv2D(64 * 8, kernel_size=1, padding="same", strides=1)(merged_input) x3 = BatchNormalization()(x3) x3 = LeakyReLU(alpha=0.2)(x3) x3 = Flatten()(x3) x3 = Dense(1)(x3) x3 = Activation('sigmoid')(x3)
x3
是此判别器网络的输出。 这将输出通过的图像是真实的还是伪造的概率。
最后,创建一个模型:
stage2_dis = Model(inputs=[input_layer, input_layer2], outputs=[x3])
如您所见,此模型采用两个输入并返回一个输出。
判别器网络的完整代码如下:
def build_stage2_discriminator(): input_layer = Input(shape=(256, 256, 3)) x = Conv2D(64, (4, 4), padding='same', strides=2, input_shape=(256, 256, 3), use_bias=False)(input_layer) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(128, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(256, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(512, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(1024, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(2048, (4, 4), padding='same', strides=2, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(1024, (1, 1), padding='same', strides=1, use_bias=False)(x) x = BatchNormalization()(x) x = LeakyReLU(alpha=0.2)(x) x = Conv2D(512, (1, 1), padding='same', strides=1, use_bias=False)(x) x = BatchNormalization()(x) x2 = Conv2D(128, (1, 1), padding='same', strides=1, use_bias=False)(x) x2 = BatchNormalization()(x2) x2 = LeakyReLU(alpha=0.2)(x2) x2 = Conv2D(128, (3, 3), padding='same', strides=1, use_bias=False)(x2) x2 = BatchNormalization()(x2) x2 = LeakyReLU(alpha=0.2)(x2) x2 = Conv2D(512, (3, 3), padding='same', strides=1, use_bias=False)(x2) x2 = BatchNormalization()(x2) added_x = add([x, x2]) added_x = LeakyReLU(alpha=0.2)(added_x) input_layer2 = Input(shape=(4, 4, 128)) # Concatenation block merged_input = concatenate([added_x, input_layer2]) x3 = Conv2D(64 * 8, kernel_size=1, padding="same", strides=1)(merged_input) x3 = BatchNormalization()(x3) x3 = LeakyReLU(alpha=0.2)(x3) x3 = Flatten()(x3) x3 = Dense(1)(x3) x3 = Activation('sigmoid')(x3) stage2_dis = Model(inputs=[input_layer, input_layer2], outputs=[x3]) return stage2_dis
我们现在已经成功地为两个 StackGAN 创建了模型:第一阶段和第二阶段。 让我们继续训练模型。
训练 StackGAN
在本节中,我们将学习如何训练这两个 StackGAN。 在第一小节中,我们将训练第一阶段 StackGAN。 在第二小节中,我们将训练第二阶段 StackGAN。
训练第一阶段 StackGAN
在开始训练之前,我们需要指定基本的超参数。 超参数是在训练过程中不会改变的值。 让我们先这样做:
data_dir = "Specify your dataset directory here/Data/birds" train_dir = data_dir + "/train" test_dir = data_dir + "/test" image_size = 64 batch_size = 64 z_dim = 100 stage1_generator_lr = 0.0002 stage1_discriminator_lr = 0.0002 stage1_lr_decay_step = 600 epochs = 1000 condition_dim = 128 embeddings_file_path_train = train_dir + "/char-CNN-RNN-embeddings.pickle" embeddings_file_path_test = test_dir + "/char-CNN-RNN-embeddings.pickle" filenames_file_path_train = train_dir + "/filenames.pickle" filenames_file_path_test = test_dir + "/filenames.pickle" class_info_file_path_train = train_dir + "/class_info.pickle" class_info_file_path_test = test_dir + "/class_info.pickle" cub_dataset_dir = data_dir + "/CUB_200_2011"
然后,我们需要加载数据集。
加载数据集
加载数据集是一个需要几个步骤的过程。 让我们一步一步地探索每个步骤。
- 第一步是加载存储在 pickle 文件中的类 ID。 以下代码将加载类 ID 并返回所有 ID 的列表:
def load_class_ids(class_info_file_path): """ Load class ids from class_info.pickle file """ with open(class_info_file_path, 'rb') as f: class_ids = pickle.load(f, encoding='latin1') return class_ids
- 接下来,加载文件名,这些文件名也存储在 pickle 文件中。 可以按照以下步骤进行:
def load_filenames(filenames_file_path): """ Load filenames.pickle file and return a list of all file names """ with open(filenames_file_path, 'rb') as f: filenames = pickle.load(f, encoding='latin1') return filenames
- 之后,我们需要加载文本嵌入,这些嵌入也位于 pickle 文件中。 加载文件并检索文本嵌入,如下所示:
def load_embeddings(embeddings_file_path): """ Load embeddings """ with open(embeddings_file_path, 'rb') as f: embeddings = pickle.load(f, encoding='latin1') embeddings = np.array(embeddings) print('embeddings: ', embeddings.shape) return embeddings
- 接下来,获取边界框,该边界框用于从原始图像提取对象。 下面的不言自明的代码显示了如何检索边界框:
def load_bounding_boxes(dataset_dir): """ Load bounding boxes and return a dictionary of file names and corresponding bounding boxes """ # Paths bounding_boxes_path = os.path.join(dataset_dir, 'bounding_boxes.txt') file_paths_path = os.path.join(dataset_dir, 'images.txt') # Read bounding_boxes.txt and images.txt file df_bounding_boxes = pd.read_csv(bounding_boxes_path, delim_whitespace=True, header=None).astype(int) df_file_names = pd.read_csv(file_paths_path, delim_whitespace=True, header=None) # Create a list of file names file_names = df_file_names[1].tolist() # Create a dictionary of file_names and bounding boxes filename_boundingbox_dict = {img_file[:-4]: [] for img_file in file_names[:2]} # Assign a bounding box to the corresponding image for i in range(0, len(file_names)): # Get the bounding box bounding_box = df_bounding_boxes.iloc[i][1:].tolist() key = file_names[i][:-4] filename_boundingbox_dict[key] = bounding_box return filename_boundingbox_dict
- 接下来,编写一种加载和裁剪图像的方法。 以下代码加载图像并将其裁剪在提供的边界框周围。 它还将图像调整为指定大小:
def get_img(img_path, bbox, image_size): """ Load and resize image """ img = Image.open(img_path).convert('RGB') width, height = img.size if bbox is not None: R = int(np.maximum(bbox[2], bbox[3]) * 0.75) center_x = int((2 * bbox[0] + bbox[2]) / 2) center_y = int((2 * bbox[1] + bbox[3]) / 2) y1 = np.maximum(0, center_y - R) y2 = np.minimum(height, center_y + R) x1 = np.maximum(0, center_x - R) x2 = np.minimum(width, center_x + R) img = img.crop([x1, y1, x2, y2]) img = img.resize(image_size, PIL.Image.BILINEAR) return img
- 最后,结合前面所有的方法来获取数据集,这是我们训练所需的。 此代码返回所有图像,其标签和相应的嵌入。 我们需要这些来进行训练:
def load_dataset(filenames_file_path, class_info_file_path, cub_dataset_dir, embeddings_file_path, image_size): filenames = load_filenames(filenames_file_path) class_ids = load_class_ids(class_info_file_path) bounding_boxes = load_bounding_boxes(cub_dataset_dir) all_embeddings = load_embeddings(embeddings_file_path) X, y, embeddings = [], [], [] # TODO: Change filenames indexing for index, filename in enumerate(filenames[:500]): # print(class_ids[index], filenames[index]) bounding_box = bounding_boxes[filename] try: # Load images img_name = '{}/images/{}.jpg'.format(cub_dataset_dir, filename) img = get_img(img_name, bounding_box, image_size) all_embeddings1 = all_embeddings[index, :, :] embedding_ix = random.randint(0, all_embeddings1.shape[0] - 1) embedding = all_embeddings1[embedding_ix, :] X.append(np.array(img)) y.append(class_ids[index]) embeddings.append(embedding) except Exception as e: print(e) X = np.array(X) y = np.array(y) embeddings = np.array(embeddings) return X, y, embeddings
- 最后,加载数据集并将其用于训练:
X_train, y_train, embeddings_train = load_dataset(filenames_file_path=filenames_file_path_train, class_info_file_path=class_info_file_path_train, cub_dataset_dir=cub_dataset_dir, embeddings_file_path=embeddings_file_path_train, image_size=(64, 64)) X_test, y_test, embeddings_test = load_dataset(filenames_file_path=filenames_file_path_test, class_info_file_path=class_info_file_path_test, cub_dataset_dir=cub_dataset_dir, embeddings_file_path=embeddings_file_path_test, image_size=(64, 64))
现在我们已经成功加载了数据集进行训练,让我们创建一些模型。
建立模型
让我们使用“StackGAN 的 Keras 实现”下的“第一阶段 StackGAN”部分中的方法创建模型。 我们将使用四个模型:生成器模型,判别器模型,压缩文本嵌入的压缩器模型以及同时包含生成器和判别器的对抗模型:
- 首先定义训练所需的优化器:
dis_optimizer = Adam(lr=stage1_discriminator_lr, beta_1=0.5, beta_2=0.999) gen_optimizer = Adam(lr=stage1_generator_lr, beta_1=0.5, beta_2=0.999)
- 然后按如下所示构建和编译不同的网络:
ca_model = build_ca_model() ca_model.compile(loss="binary_crossentropy", optimizer="adam") stage1_dis = build_stage1_discriminator() stage1_dis.compile(loss='binary_crossentropy', optimizer=dis_optimizer) stage1_gen = build_stage1_generator() stage1_gen.compile(loss="mse", optimizer=gen_optimizer) embedding_compressor_model = build_embedding_compressor_model() embedding_compressor_model.compile(loss="binary_crossentropy", optimizer="adam") adversarial_model = build_adversarial_model(gen_model=stage1_gen, dis_model=stage1_dis) adversarial_model.compile(loss=['binary_crossentropy', KL_loss], loss_weights=[1, 2.0], optimizer=gen_optimizer, metrics=None)
此处,KL_loss
是自定义损失函数,定义如下:
def KL_loss(y_true, y_pred): mean = y_pred[:, :128] logsigma = y_pred[:, :128] loss = -logsigma + .5 * (-1 + K.exp(2\. * logsigma) + K.square(mean)) loss = K.mean(loss) return loss
现在我们已经准备好数据集和模型,因此我们可以开始训练模型。
另外,添加 TensorBoard 以存储损失以进行可视化,如下所示:
tensorboard = TensorBoard(log_dir="logs/".format(time.time())) tensorboard.set_model(stage1_gen) tensorboard.set_model(stage1_dis) tensorboard.set_model(ca_model) tensorboard.set_model(embedding_compressor_model)
训练模型
训练模型需要几个步骤:
- 用真实和假标签创建两个张量。 在训练生成器和判别器时将需要这些。 使用标签平滑处理,该内容在第 1 章,“生成对抗网络”中介绍:
real_labels = np.ones((batch_size, 1), dtype=float) * 0.9 fake_labels = np.zeros((batch_size, 1), dtype=float) * 0.1
- 接下来,创建一个
for
循环,该循环应运行的次数由周期数指定,如下所示:
for epoch in range(epochs): print("========================================") print("Epoch is:", epoch) print("Number of batches", int(X_train.shape[0] / batch_size)) gen_losses = [] dis_losses = []
- 之后,计算一批批量并编写一个
for
循环,该循环将针对指定数量的批量运行:
number_of_batches = int(X_train.shape[0] / batch_size) for index in range(number_of_batches): print("Batch:{}".format(index+1))
- 为当前迭代采样一批数据(一个小批量)。 创建噪声向量,选择一批图像和一批嵌入物,并对图像进行规范化:
# Create a batch of noise vectors z_noise = np.random.normal(0, 1, size=(batch_size, z_dim)) image_batch = X_train[index * batch_size:(index + 1) * batch_size] embedding_batch = embeddings_train[index * batch_size:(index + 1) * batch_size] # Normalize images image_batch = (image_batch - 127.5) / 127.5
- 接下来,使用生成器模型通过传递
embedding_batch
和z_noise
来生成伪图像:
fake_images, _ = stage1_gen.predict([embedding_batch, z_noise], verbose=3)
这将生成以一组嵌入和一组噪声向量为条件的一组伪图像。
- 使用压缩器模型压缩嵌入。 空间复制它以将其转换为形状为
(batch_size, 4, 4, 128)
的张量:
compressed_embedding = embedding_compressor_model.predict_on_batch(embedding_batch) compressed_embedding = np.reshape(compressed_embedding, (-1, 1, 1, condition_dim)) compressed_embedding = np.tile(compressed_embedding, (1, 4, 4, 1))
- 接下来,在生成器生成的伪图像,真实数据集中的真实图像和错误图像上训练判别器模型:
dis_loss_real = stage1_dis.train_on_batch([image_batch, compressed_embedding], np.reshape(real_labels, (batch_size, 1))) dis_loss_fake = stage1_dis.train_on_batch([fake_images, compressed_embedding], np.reshape(fake_labels, (batch_size, 1))) dis_loss_wrong = stage1_dis.train_on_batch([image_batch[:(batch_size - 1)], compressed_embedding[1:]], np.reshape(fake_labels[1:], (batch_size-1, 1)))
现在,我们已经成功地在三组数据上训练了判别器:真实图像,伪图像和错误图像。 现在让我们训练对抗模型:
- 接下来,训练对抗模型。 为它提供三个输入和相应的真值。 此操作将计算梯度并更新一批数据的权重。
g_loss = adversarial_model.train_on_batch([embedding_batch, z_noise, compressed_embedding],[K.ones((batch_size, 1)) * 0.9, K.ones((batch_size, 256)) * 0.9])
- 接下来,计算损失并将其存储以进行评估。 最好不断打印出不同的损失以跟踪训练:
d_loss = 0.5 * np.add(dis_loss_real, 0.5 * np.add(dis_loss_wrong, dis_loss_fake)) print("d_loss:{}".format(d_loss)) print("g_loss:{}".format(g_loss)) dis_losses.append(d_loss) gen_losses.append(g_loss)
- 在每个周期完成之后,将任何损失存储到 TensorBoard 中:
write_log(tensorboard, 'discriminator_loss', np.mean(dis_losses), epoch) write_log(tensorboard, 'generator_loss', np.mean(gen_losses[0]), epoch)
- 在每个周期之后,要评估进度,生成图像并将其保存在结果目录中。
z_noise2 = np.random.normal(0, 1, size=(batch_size, z_dim)) embedding_batch = embeddings_test[0:batch_size] fake_images, _ = stage1_gen.predict_on_batch([embedding_batch, z_noise2]) # Save images for i, img in enumerate(fake_images[:10]): save_rgb_img(img, "results/gen_{}_{}.png".format(epoch, i))
此处,save_rgb_img()
是一个实用函数,定义如下:
def save_rgb_img(img, path): """ Save a rgb image """ fig = plt.figure() ax = fig.add_subplot(1, 1, 1) ax.imshow(img) ax.axis("off") ax.set_title("Image") plt.savefig(path) plt.close()
- 将每个模型的权重保存在 StackGAN 的第一阶段中。
stage1_gen.save_weights("stage1_gen.h5") stage1_dis.save_weights("stage1_dis.h5")
恭喜,我们已经成功训练了 StackGAN 的第一阶段。 现在,我们拥有训练有素的生成器网络,可以生成大小为64x64x3
的图像。 这些图像将具有基本颜色和原始形状。 在下一部分中,我们将训练第二阶段 StackGAN。
训练第二阶段 StackGAN
请执行以下步骤来训练第二阶段 StackGAN。
首先指定用于训练第二阶段 StackGAN 的超参数:
# Specify hyperparamters data_dir = "Path to the dataset/Data/birds" train_dir = data_dir + "/train" test_dir = data_dir + "/test" hr_image_size = (256, 256) lr_image_size = (64, 64) batch_size = 8 z_dim = 100 stage1_generator_lr = 0.0002 stage1_discriminator_lr = 0.0002 stage1_lr_decay_step = 600 epochs = 10 condition_dim = 128 embeddings_file_path_train = train_dir + "/char-CNN-RNN-embeddings.pickle" embeddings_file_path_test = test_dir + "/char-CNN-RNN-embeddings.pickle" filenames_file_path_train = train_dir + "/filenames.pickle" filenames_file_path_test = test_dir + "/filenames.pickle" class_info_file_path_train = train_dir + "/class_info.pickle" class_info_file_path_test = test_dir + "/class_info.pickle" cub_dataset_dir = data_dir + "/CUB_200_2011"
加载数据集
使用创建第一阶段 StackGAN 时在“加载数据集”部分中定义的方法。 分别加载高分辨率和低分辨率数据集。 另外,分别加载训练和测试数据集:
X_hr_train, y_hr_train, embeddings_train = load_dataset(filenames_file_path=filenames_file_path_train, class_info_file_path=class_info_file_path_train, cub_dataset_dir=cub_dataset_dir, embeddings_file_path=embeddings_file_path_train, image_size=(256, 256)) X_hr_test, y_hr_test, embeddings_test = load_dataset(filenames_file_path=filenames_file_path_test, class_info_file_path=class_info_file_path_test, cub_dataset_dir=cub_dataset_dir, embeddings_file_path=embeddings_file_path_test, image_size=(256, 256)) X_lr_train, y_lr_train, _ = load_dataset(filenames_file_path=filenames_file_path_train, class_info_file_path=class_info_file_path_train, cub_dataset_dir=cub_dataset_dir, embeddings_file_path=embeddings_file_path_train, image_size=(64, 64)) X_lr_test, y_lr_test, _ = load_dataset(filenames_file_path=filenames_file_path_test, class_info_file_path=class_info_file_path_test, cub_dataset_dir=cub_dataset_dir, embeddings_file_path=embeddings_file_path_test, image_size=(64, 64))
建立模型
与之前一样创建 Keras 模型,这些模型在“StackGAN 的 Keras 实现”的“第一阶段 StackGAN”部分中指定:
首先定义训练所需的优化器:
dis_optimizer = Adam(lr=stage1_discriminator_lr, beta_1=0.5, beta_2=0.999) gen_optimizer = Adam(lr=stage1_generator_lr, beta_1=0.5, beta_2=0.999)
我们将使用 Adam optimizer
,其学习率等于0.0002
,beta_1
值等于0.5
,beta_2
等于0.999
。
现在构建和创建模型:
stage2_dis = build_stage2_discriminator() stage2_dis.compile(loss='binary_crossentropy', optimizer=dis_optimizer) stage1_gen = build_stage1_generator() stage1_gen.compile(loss="binary_crossentropy", optimizer=gen_optimizer) stage1_gen.load_weights("stage1_gen.h5") stage2_gen = build_stage2_generator() stage2_gen.compile(loss="binary_crossentropy", optimizer=gen_optimizer) embedding_compressor_model = build_embedding_compressor_model() embedding_compressor_model.compile(loss='binary_crossentropy', optimizer='adam') adversarial_model = build_adversarial_model(stage2_gen, stage2_dis, stage1_gen) adversarial_model.compile(loss=['binary_crossentropy', KL_loss], loss_weights=[1.0, 2.0], optimizer=gen_optimizer, metrics=None)
KL_loss
是自定义损失函数,在“训练第一阶段 StackGAN”部分中指定。
现在,我们已经为第二阶段 StackGAN 准备了数据集和模型。 让我们训练模型。
训练模型
让我们逐步进行此过程。
- 首先添加 TensorBoard 来存储损失:
tensorboard = TensorBoard(log_dir="logs/".format(time.time())) tensorboard.set_model(stage2_gen) tensorboard.set_model(stage2_dis)
- 然后,创建两个张量分别为
real
和fake
的张量。 在训练生成器和判别器时将需要这些。 使用标签平滑处理,第 1 章,“生成对抗网络”对此进行了介绍:
real_labels = np.ones((batch_size, 1), dtype=float) * 0.9 fake_labels = np.zeros((batch_size, 1), dtype=float) * 0.1
- 接下来,创建一个
for
循环,该循环应运行由周期数指定的次数,如下所示:
for epoch in range(epochs): print("========================================") print("Epoch is:", epoch) gen_losses = [] dis_losses = []
- 在周期循环内创建另一个循环,该循环将运行指定的批量数量:
print("Number of batches:{}".format(number_of_batches)) for index in range(number_of_batches): print("Batch:{}".format(index))
- 采样训练所需的数据:
# Create a mini-batch of noise vectors z_noise = np.random.normal(0, 1, size=(batch_size, z_dim)) X_hr_train_batch = X_hr_train[index * batch_size:(index + 1) * batch_size] embedding_batch = embeddings_train[index * batch_size:(index + 1) * batch_size] # Normalize images X_hr_train_batch = (X_hr_train_batch - 127.5) / 127.5
- 接下来,使用生成器网络生成大小为
256x256x2
的伪图像。 在此步骤中,我们首先使用第一阶段的生成器网络生成低分辨率的伪图像。 然后,我们在第二阶段中使用生成器网络生成以低分辨率图像为条件的高分辨率图像。
lr_fake_images, _ = stage1_gen.predict([embedding_batch, z_noise], verbose=3) hr_fake_images, _ = stage2_gen.predict([embedding_batch, lr_fake_images], verbose=3)
- 使用压缩器模型压缩嵌入。 空间复制它以将其转换为形状为
(batch_size, 4, 4, 128)
的张量
compressed_embedding = embedding_compressor_model.predict_on_batch(embedding_batch) compressed_embedding = np.reshape(compressed_embedding, (-1, 1, 1, condition_dim)) compressed_embedding = np.tile(compressed_embedding, (1, 4, 4, 1))
- 之后,在伪图像,真实图像和错误图像上训练判别器模型:
dis_loss_real = stage2_dis.train_on_batch([X_hr_train_batch, compressed_embedding], np.reshape(real_labels, (batch_size, 1))) dis_loss_fake = stage2_dis.train_on_batch([hr_fake_images, compressed_embedding], np.reshape(fake_labels, (batch_size, 1))) dis_loss_wrong = stage2_dis.train_on_batch([X_hr_train_batch[:(batch_size - 1)], compressed_embedding[1:]], np.reshape(fake_labels[1:], (batch_size-1, 1)))
- 接下来,训练对抗模型。 这是生成器模型和判别器模型的组合。 我们为它提供三个输入和相应的真值:
g_loss = adversarial_model.train_on_batch([embedding_batch, z_noise, compressed_embedding],[K.ones((batch_size, 1)) * 0.9, K.ones((batch_size, 256)) * 0.9])
- 计算损失并将其保存以进行评估:
d_loss = 0.5 * np.add(dis_loss_real, 0.5 * np.add(dis_loss_wrong, dis_loss_fake)) print("d_loss:{}".format(d_loss)) print("g_loss:{}".format(g_loss))
在每个周期之后,将损失保存到 TensorBoard 中:
write_log(tensorboard, 'discriminator_loss', np.mean(dis_losses), epoch) write_log(tensorboard, 'generator_loss', np.mean(gen_losses)[0], epoch)
- 在每个周期之后,要评估进度,生成图像并将其保存在结果目录中。 在以下代码中,我们仅保存第一个生成的图像。 相应地更改此设置以适当保存图像。
# Generate and save images after every 2nd epoch if epoch % 2 == 0: # z_noise2 = np.random.uniform(-1, 1, size=(batch_size, z_dim)) z_noise2 = np.random.normal(0, 1, size=(batch_size, z_dim)) embedding_batch = embeddings_test[0:batch_size] lr_fake_images, _ = stage1_gen.predict([embedding_batch, z_noise2], verbose=3) hr_fake_images, _ = stage2_gen.predict([embedding_batch, lr_fake_images], verbose=3) # Save images for i, img in enumerate(hr_fake_images[:10]): save_rgb_img(img, "results2/gen_{}_{}.png".format(epoch, i))
此处,save_rgb_img()
是工具函数,在“训练第一阶段 StackGAN 部分”中定义。
- 最后,保存模型或其权重值:
# Saving the models stage2_gen.save_weights("stage2_gen.h5") stage2_dis.save_weights("stage2_dis.h5")
恭喜,我们现在已经成功完成了第二阶段 StackGAN 的训练。 现在,我们有了一个生成器网络,可以生成大小 256x256x3
的逼真的图像。 如果为生成器网络提供文本嵌入和噪声变量,它将生成 256x256x3
分辨率图像。 让我们可视化网络的损失图。
可视化生成的图像
在将网络训练了 500 个时间段后,网络将开始生成如下所示的体面图像。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bja8c9Vd-1681652906146)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/gan-proj/img/030151f2-b1cf-4a2a-9d44-d55bc65f9c7c.png)]
由 StackGAN 网络的第一阶段和第二阶段生成的图像
我建议您将网络训练 1000 个周期。 如果一切顺利,则在 1000 个周期之后,生成器网络将开始生成逼真的图像。
生成对抗网络项目:6~9(3)https://developer.aliyun.com/article/1426899