65.9K
CodeProject 正在变化。 阅读更多。
Home

构建和训练深度伪造自编码器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2021年3月30日

CPOL

6分钟阅读

viewsIcon

11677

在本文中,我们将构建和训练我们的模型。

深度伪造——利用深度学习将一个人的脸替换到视频中的另一个人身上——是当今人工智能最有趣令人担忧的应用方式之一。

尽管深度伪造可以用于合法目的,但它也可以用于虚假信息传播。能够轻易地将某人的面部替换到任何视频中,我们还能真正相信我们眼睛看到的东西吗?一段看起来真实的政治家或演员做出或说出令人震惊的事情的视频,可能根本就不是真的。

在本系列文章中,我们将展示深度伪造是如何工作的,以及如何从头开始实现它们。然后,我们将研究 DeepFaceLab,这是常用于创建逼真深度伪造的、基于 Tensorflow 的一体化工具。

上一篇文章 中,我们通过将四个视频转换为两个面部数据集来预处理了数据。在本文中,我们将构建将使用这些面部数据集的自编码器。在我个人的情况下,我从一开始就在 Kaggle Notebooks 上工作,所以我将使用前一个 Notebook 的输出作为我将要使用的 Notebook 的输入。我将使用这个 Notebook 来调整模型的超参数,但实际繁重的训练将在下一个主题中进行,届时我们将在 Google AI Platform 上使用 Docker 容器 来训练我们的模型。

如果您不知道如何将前一个 Notebook 的输出用作新 Notebook 的输入,只需创建一个新 Notebook,然后点击右上角的“添加数据”按钮。

点击“Notebook 输出文件”,然后点击“您的数据集”。

最后,点击您要导入的输出文件旁边的“添加”。

如果您在本地运行此项目,您将使用保存在 results_1results_2 文件夹中的图像。导入所需数据后,让我们开始编写实际代码!

在 Notebook 中设置基础知识

让我们开始导入所需的库。

import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import cv2
from tensorflow.keras.models import load_model
from sklearn.model_selection import train_test_split
import keras
from keras import layers
from tensorflow.keras.callbacks import ModelCheckpoint
import tensorflow as tf

现在让我们加载将要使用的面部数据集并进行拆分以开始训练。

数据集创建与拆分

让我们创建一个函数来加载模型可以理解的形状的数据。为此,我将创建一个函数来帮助我们完成这项任务。

def create_dataset(path):
    images = []
    for dirname, _, filenames in os.walk(path):
        for filename in filenames:
            image = cv2.imread(os.path.join(dirname, filename))
            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
            image = image.astype('float32')
            image /= 255.0
            images.append(image)
    images = np.array(images)
return images

本质上,这个函数将每个文件夹中的所有图像读取为整数数组,将颜色通道从 BGR 校正为 RGB,将数组数据类型转换为浮点数,并对其进行归一化,将值范围设置为 0 到 1,而不是 0 到 255。这将有助于我们未来的模型更容易收敛。

要调用该函数并开始加载数据,请运行

faces_1 = create_dataset('/kaggle/input/presidentsfacesdataset/trump/')
faces_2 = create_dataset('/kaggle/input/presidentsfacesdataset/biden/')

现在我们终于准备好拆分我们的数据集了。

X_train_a, X_test_a, y_train_a, y_test_a = train_test_split(faces_1, faces_1, test_size=0.20, random_state=0)
X_train_b, X_test_b, y_train_b, y_test_b = train_test_split(faces_2, faces_2, test_size=0.15, random_state=0)

创建和训练自编码器

我们已经到达了本系列的重点!您可能还记得,我们需要创建和训练两个自编码器,然后交换它们的元素以获得将为我们进行面部转换的最终模型。我们将使用 Keras Functional API 来构建我们的模型。

发出以下命令来创建编码器结构。

input_img = layers.Input(shape=(120, 120, 3))
x = layers.Conv2D(256,kernel_size=5, strides=2, padding='same',activation='relu')(input_img)
x = layers.MaxPooling2D((2, 2), padding='same')(x)
x = layers.Conv2D(512,kernel_size=5, strides=2, padding='same',activation='relu')(x)
x = layers.MaxPooling2D((2, 2), padding='same')(x)
x = layers.Conv2D(1024,kernel_size=5, strides=2, padding='same',activation='relu')(x)
x = layers.MaxPooling2D((2, 2), padding='same')(x)
x = layers.Flatten()(x)
x = layers.Dense(9216)(x)
encoded = layers.Reshape((3,3,1024))(x)
encoder = keras.Model(input_img, encoded,name="encoder")

仔细查看上面的代码。解码器的第一个 Conv2D 层需要与最后一个输入深度相同的滤波器数量,因此我们将此模型强制输出形状为 (3,3,1024) 的张量。这样,解码器就可以在第一个 Conv2D 层中使用 1024 个滤波器开始。该模型的最后一个 MaxPooling 层输出一个多维张量,因此我们使用 Flatten 将其展平,然后使用 Dense 和 Reshape 层强制其形状为 (3,3,1024)。最后一行,我们定义了这个模型的输入和输出,并给它命名。让我们创建解码器结构。

decoder_input= layers.Input(shape=((3,3,1024)))
x = layers.Conv2D(1024,kernel_size=5, strides=2, padding='same',activation='relu')(decoder_input)
x = layers.UpSampling2D((2, 2))(x)
x = layers.Conv2D(512,kernel_size=5, strides=2, padding='same',activation='relu')(x)
x = layers.UpSampling2D((2, 2))(x)
x = layers.Conv2D(256,kernel_size=5, strides=2, padding='same',activation='relu')(x)
x = layers.Flatten()(x)
x = layers.Dense(np.prod((120, 120, 3)))(x)
decoded = layers.Reshape((120, 120, 3))(x)
decoder = keras.Model(decoder_input, decoded,name="decoder")

在此代码块中,有一个不寻常的层:UpSampling2D。如果在编码器中我们想减小输入图像的维度并使用 MaxPooling2D 层来获得其潜在表示,那么我们将需要一种方法来恢复这个维度,以便在解码器的末尾获得输入图像。

为了实现这一点,我们实现了一个 UpSampling 层来完成这项任务。本质上,它以最简单的方式进行上采样:它使每一行和每一列加倍。例如,如果我们向此层传递数组 [[1,2],[3,4]],则输出将是 [[1,1,2,2],[1,1,2,2],[3,3,4,4],[3,3,4,4]]。如果进行计算,解码器输入形状为 (3,3,1024),输出为 (120,120,3),这与原始输入图像的尺寸相同。

最后,要编译包含这两个元素的自编码器,请发出以下命令:

auto_input = layers.Input(shape=(120,120,3))
encoded = encoder(auto_input)
decoded = decoder(encoded)
 
autoencoder = keras.Model(auto_input, decoded,name="autoencoder")
autoencoder.compile(optimizer=keras.optimizers.Adam(lr=5e-5, beta_1=0.5, beta_2=0.999), loss='mae')
autoencoder.summary()
Model: "autoencoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_3 (InputLayer)         [(None, 120, 120, 3)]     0         
_________________________________________________________________
encoder (Functional)         (None, 3, 3, 1024)        54162944  
_________________________________________________________________
decoder (Functional)         (None, 120, 120, 3)       86880192  
=================================================================
Total params: 141,043,136
Trainable params: 141,043,136
Non-trainable params: 0
_________________________________________________________________

请注意,我们有大量的模型参数需要从头开始训练,这意味着这个过程肯定需要几个小时甚至几天,所以请做好等待的准备。我将 Adam 作为优化器,并将 MAE(平均绝对误差)作为要减小的损失(因为此任务可以建模为回归)。

模型创建已准备就绪。现在该开始训练 srcdst 自编码器了。要做到这一点,您可以使用 fit 方法,就像训练常规深度学习模型一样。在这种特定情况下,我将使用 KerasModelCheckpoint 实现来保存训练中达到的最佳模型。要开始 src 训练,请发出命令:

checkpoint1 = ModelCheckpoint("/kaggle/working/autoencoder_a.hdf5", monitor='val_loss', verbose=1,save_best_only=True, mode='auto', period=1)
history1 = autoencoder.fit(X_train_a, X_train_a, epochs=2700, batch_size=512, shuffle=True, validation_data=(X_test_a, X_test_a), callbacks=[checkpoint1])

我在此 Notebook 中训练了 2700 个 epoch,但要获得可接受的结果,您应该至少训练此模型 20000 次迭代。在过程结束时,模型获得了 0.01926 的损失,如果您训练的时间更长,还可以进一步降低。为了让您大致了解获得的指标有多好,让我们绘制一个参考图像及其相应的重建图像:

%matplotlib inline
plt.figure()
plt.imshow(X_test_a[30])
plt.show()

加载训练过程中获得的最佳模型并绘制相应的图像重建。

autoencoder_a = load_model("/kaggle/working/autoencoder_a.hdf5")
output_image = autoencoder_a.predict(np.array([X_test_a[30]]))
plt.figure()
plt.imshow(output_image[0])
plt.show()

效果不错,对吧?这是随机选择的一张图像,尽管看起来不错,但损失仍然很高。这意味着这种高质量的重建是碰巧的。当损失很高时,我们通常不会期望有这么好的结果,而且我们不应该依赖它。话虽如此,让我们开始 dst 训练吧。

checkpoint2 = ModelCheckpoint("/kaggle/working/autoencoder_b.hdf5", monitor='val_loss', verbose=1,save_best_only=True, mode='auto', period=1)
history2 = autoencoder.fit(X_train_b, X_train_b,epochs=2700,batch_size=512,shuffle=True,validation_data=(X_test_b, X_test_b),callbacks=[checkpoint2])

训练结束后,最佳自编码器模型获得了 0.02634 的损失,略高于 src 模型。请记住,这个面部数据集更小。为了让我们大致了解它的图像重建效果如何,让我们运行:

plt.figure()
plt.imshow(X_test_b[0])
plt.show()

autoencoder_b = load_model("/kaggle/working/autoencoder_b.hdf5")
output_image = autoencoder_b.predict(np.array([X_test_b[0]]))
plt.figure()
plt.imshow(output_image[0])
plt.show()

就像我与前一个自编码器所说的那样,仅仅因为这次重建看起来不错,并不意味着它们都会如此。实际上,由于此损失较高,因此此自编码器的性能比前一个差。如果我们想降低损失,一个好的方法是为这个面部数据集添加更多帧,使用数据增强,或者训练更多的 epoch。

在 Keras 中交换编码器和解码器

现在我们已经训练了自编码器,是时候交换它们的零件了。如果我们想要获得具有 src 面部表情的 dst 面部,那么我们需要取 src 编码器和 dst 解码器,并将它们组合在一起。如果我们想要相反的效果,那么我们需要组合 dst 编码器和 src 解码器。让我们开始动手编写代码。

# TO LOAD ONLY THE ENCODER A
encoder_a = keras.Model(autoencoder_a.layers[1].input, autoencoder_a.layers[1].output)
# TO LOAD ONLY THE DECODER A
decoder_a = keras.Model(autoencoder_a.layers[2].input, autoencoder_a.layers[2].output)
# TO LOAD ONLY THE ENCODER B
encoder_b = keras.Model(autoencoder_b.layers[1].input, autoencoder_b.layers[1].output)
# TO LOAD ONLY THE DECODER B
decoder_b = keras.Model(autoencoder_b.layers[2].input, autoencoder_b.layers[2].output)
 
# TO TRANSFORM SRC IMAGES
input_test = encoder_a.predict(np.array([X_test_a[30]]))
output_test = decoder_b.predict(input_test)
 
# TO TRANSFORM DST IMAGES
input_test = encoder_b.predict(np.array([X_test_b[30]]))
output_test = decoder_a.predict(input_test)

output_test 是最终转换后的图像。

就这样,您已经完成了最困难的部分。在 下一篇文章 中再见,在那篇文章中,我将简要介绍 Docker 容器,然后再深入介绍如何在 Google AI Platform 上训练您的模型。

© . All rights reserved.