目录
深度伪造——使用深度学习在视频中将一个人的脸换成另一个人的脸——是当今使用人工智能的最有趣和最可怕的方式之一。
虽然深度伪造可用于合法目的,但它们也可用于虚假信息。能够轻松地将某人的脸换成任何视频,我们真的可以相信我们的眼睛告诉我们的吗?政治家或演员做或说令人震惊的事情的真实视频可能根本不是真实的。
在本系列文章中,我们将展示深度伪造的工作原理,并展示如何从头开始实现它们。然后我们将看看DeepFaceLab,它是一种多合一的Tensorflow驱动的工具,通常用于创建令人信服的深度伪造。
在上一篇文章中,我们通过将四个视频转换为两个面集来预处理我们的数据。在本文中,我们将构建将使用人脸集合的自动编码器。对于我来说,我从一开始就在开发Kaggle Notebook,所以我将使用之前的Notebook的输出作为我现在要使用的这个的输入。我将使用此Notebook来调整模型的超参数,但实际的繁重训练将在下一个主题中运行,我们将使用Docker容器在Google AI 平台上训练我们的模型。
如果您不知道如何使用以前的notebook的输出作为新notebook的输入,只需创建一个新notebook,然后单击右上角的“添加数据”按钮:
单击“Notebook输出文件”,然后单击“您的数据集”:
最后,单击要导入的输出文件旁边的“添加”:
如果您在本地运行此项目,您将使用保存在results_1和results_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(平均误差)作为要减少的损失(因为这个任务可以建模为回归)。
模型创建已完成。是时候开始训练src和dst自动编码器了。为此,您可以像训练常规深度学习模型一样使用fit方法。在这种特殊情况下,我会用ModelCheckpoint实现从Keras保存在训练中取得的最好模式。要开始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个epochs,但是为了获得可接受的结果,你应该训练这个模型至少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()
正如我在之前的自动编码器中所说的,仅仅因为这种重建看起来不错并不意味着它们都会这样。实际上,由于此损失更高,因此此自动编码器的性能比前一个更差。如果我们需要减少损失,一个好的方法是用更多的帧来增加这人脸集合,使用数据增强或训练更多的epochs。
在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 是转换后的图像。
有了这个,你刚刚完成了最困难的部分。看你的下一篇文章中,我将在其中跳跃到如何在谷歌AI平台训练你的模型之前,提供关于Docker容器的简要介绍。
https://www.codeproject.com/Articles/5298025/Building-and-Training-Deep-Fake-Autoencoders