简单五步,教你用TensorFlow和Streamlit构建人脸合成程序

简单五步,教你用TensorFlow和Streamlit构建人脸合成程序

机器学习模型是黑匣子。你可以在测试集上运行它们并绘制出奇特的性能曲线,但是通常还是很难回答关于它们如何运行的基本问题。一个惊人且强大的来源是简单地玩转模型:调整输入--观察输出。

创建交互模型是Streamlit的灵感来源之一,Streamlit是一个Python框架,它使得编写应用程序和编写Python脚本一样4 / ; J u )简单。` o B s这个概述将引导你创建一个streamlight应用程序,黑匣子最多的模型之一:一个深度生成的对抗网络(GAN)。

在本例中,我们将可视化Nvidia的PG-GAN使用TensorFlow,通过极少的素材来合成非常有真实e s i V L j / h感的人脸。然后,利用 TL-GAN 模型,创建一个应用程序,根据年龄、微笑、男性相似性和头发颜色等属性调整GAN合成的名人脸。在本教程结束时,你将拥有一个完全参数化的人类模型!

Streamlit入门

如果你还没有安装Streamlit,可以运A T ( A a P w行以下命令:

pip install streamlit
streamlit hello

如果你是一个经验丰富的Streamlit er,你将需要在0.57.1或更高版本,所以请确保升级!

pip install --upgrad$ t j n Fe streams W ^ k j u F B 0lit

设置环境

在我们开始之前,使用下面的命令检查项目的GitHub repo,并为自己运行Face G= W M i ` AN演示。这个演示依赖于TensorFlowe f 0 F H1,它不支持Pythonx p % 7 l3.7或3.8,所以您需要PythL i m + r [ pon3.6。在Mac和Linux上,我们建议使用pyenv在当前版本的同时安装Python 3.6,然后使用venv或virtualenv设置一个新的虚拟环境。在Wiu N Q p Q ( O 8 &ndows上,Anaconda导航器允许你使用点击界面选择Python版本。

全部设置好后,打开终端窗口并键入:

git clone https://github.com/streamlit/demo-face-gan.git
cd demo-face-gan
pip install -n I } jr requirements.txt
streamlit run app.py

给它一分钟下载完训练的GAN,然Z p / J后试着用滑块来探索GAN可以合成的不同人脸。很酷,对吧?

简单五步,教你用TensorFlow和Streamlit构建人脸合成程序

完整的应用程序代码是一个大约190行代码的文件,其中只有13行是Streamlit调用。没错,上面的整个用户界面W T { w ; n都是从这13行画出来的!

让我们看看应用程序的结构:n y - d R [ +

def main():
st.title(\"Streamlit Face-GAN Demo\")

# Step 1. Download; Z 9 W 9 O models an, m 3d data fil3 Y E Q ` | C K $es.
for filename in EXTERNAL_DEPENDENCIES.keys():
download_file(filename)

# Step 2.: X ] U d ` W Z G Read in modee x e ? p _ls from the data files.
tl_gan_model, feature_^ 4 + ` 2 e f pnames = load_tl_gan_model()
session, pg_gan_model = load_pg_gan_model()

# Step 3.h 5 H F u Draw the7 { [ z - h . E sidebar UI.
.j % o s..
features = ... # Internally, this usZ + oes st.sidebar.slider(), etc.

# Step 4. Synthesize the image.
with session.as_default():
image_out = generate_Q ) X U } 1 i B &image(session, pg_gan_model,H . Q S tl_gan_model,
features, feature_names)

# Step 5. Draw the synthesized image.
st.im# B ! a d C K .age(image_out, use_col# f 7umn_width=True)

既然你已经了解了它的结构,那么让我们深入了解上面5! ( # 7 M个步骤中的每一个,看看它们是如何工作的。

第一步:下载模型和数据文件

这一步下载我们G - o需要的文件:一个预先训练过的PG-GAN模型和一个预先安装到其中的TL-) , } 4 fGAN模型(稍后我们将深入研究这些模型)。

下载文件实用程序功能比纯下载程序v F + ] l 2要聪明一点:

  • 它检查文件是否已经存在于本地目录中,因此只在需要时下载。它还检查下载文件的大小是否达到R , /我们预期的大小,因此它能够修复中断的下载。
#  If the file exists and has thev O 6 expected size, return.
if os.path.exists(file_path):
if \"size\" not in EXTERNAL_DEPEn j 1 U ( ]NDENCIES[file_path]:
return
elif os.path.getsize(file_path) == EXTERNAL_DEPENDENCIES[file_path][\"size\"]:
return

它使用 st.progress()和 st.warning()在文件下载时向用户显示一个漂亮的UI。然后它r 9 o . b z 5调用那些UI元素上的.empty()来在完成时隐藏它们。

# Drl ; K Yaw UI elements.
weights_warning = st.warning(\"Downloading %s...\" % file_path)
progA $ } $ @ 4 ! Wress_bar = st.progress(0)

with open(file_path, \"wb\") as output_file:
with urllib.request.urlopen(...) as rc F Hesponse:

...

while True:

... # Save downloaded bytes to file here.

# Update UI elements.
weights_w1 u k 1 RarniQ J f E 0 Eng.warning(
\"Downloading %s... (%6.2f/%6.2f MB)\" %
(f3 $ Q c K ) Wile_path, downloaded_size))
progress_bar0 l 7 D ) s 0 (.progress(downloaded_ratio)

...

# Clear UI elements when done.
weights_warning.empty()
progress_bar.empty()

第二步:将模型加载到E * 8 B C $ z内存中

下一步是将这些模型加载到内存中。下面是加载PG-GANf ) , X f 0模型的代码S Z 1 Z 8

@st.cache(allow_output_mutation=True, hash_funcs=TL_GAN_HASH_FUNCS)
def load_pg_gan_model():
\"\"\"
Create the tensorflow session.
\"\"\"
config = tf.ConfigProto(allow_soft_placement=True)
session = tf.Session(config=config)

with session.as_default():
with open(MODELw * v % p # X 1_FILE_GPU if USE_GPU else MODEL_FILE_CPU, \'rbG | Q ; N h _ E ,\') as f:
G = pic0 [ ` o D ^ kle.load(f)
return session, G

注意 load_pg_gan_model()开头的 @st.cache decorator。通常e : V 0 D = ` m G在Python中,只需运行load_pg d E y z E v_gan_model()并反复重用该变量。然而,Streamlit 的执行模型是独一无二的,因为每次用户与UI小部件交互时,脚本都会从上到下重新完整地执行。通过将 @s{ 9 G g Q ] T nt.cache 添加到代价高昂的模型加载函数中,我们告诉StreamW j x Clit只在脚本第一次执行时运行这些函数,然后在之后的每次执行中重用缓存输出。这是Strea# ` B } o 8 E + wmlit最基本的特性之一,因为它允许你通过缓存函数调用的结果来高效 E %地运行脚H N l本。这样,大型拟合GAN模型将只加载到内存中一次;同样,我们的T! m ,ensorFA F , h 6 Plow会话也将只创建一次。

简单五步,教你用TensorFlow和Streamlit构建人脸合成程序

不过,这里有一个问题:TensorFlow会话对象在我们d 0 D 1使用它运行不同的计算时可能会在内部发生变化。通常,我们不希望缓存对象发生变化,因为这会导} 1 X , I % N ,致意外的结果。所以当Streamlit检测到这种突变时,它会向用户发出警告。但是,在这种情况下` O u 2 ~,我们碰巧知道,如果TensorFlow会话对象发生变化,则可以,因此我们通过设置 allow_output_mutation=True.{ a & F 7 O ;

第三步R o f f ` ; Q % .:绘8 + S U U } ; }制边栏UI

通过调用诸如st.slider()和st.checkbox()之类的API方法添加小部件。

这些方法的返回值是UI中显示的值。例如,当用户将滑^ g ` O b O块移动到位置Y C $ $ ;42时,将重新执行脚本,在该执行中,st.slider()的返回值将为42。

你可以把任何东西放在一个侧边栏前加上st.sidebar。例如,st.sidebar.chA { 7 & 2 3eckbox()。% b z

因此,要在侧边栏t w r V g a a %中添加滑块,例如-允许用q q I [ M E S户调整棕色头发参数的滑块,只需_ b : ` u添加:

brown_hair = st.sidebar.slider(\"Brown Hair\", 0, 100, 50, step=5)
# Translation: Draw a` F N ` slider from 0 to 100 with steps of size 5.
# Then set the default value to 50.

在我们的应用程序中,我们想得到一个小小的幻想,展示如何轻松地使用户界面本g P s G & M身可修改在] i y f & 1 % Streamlit!我们希望允许用户首先使用multiselec 9 % -t小部件在生成的图像中选择一组要控制的功能,这意味着我们的UI需要_ ( h L | C % 5 E以编程方式绘制:

简单五步,教你用TensorFlow和Streamlit构建人脸合成程序

使用Streamlit,代码实际上非常简单:

sE n m w - 8 ]t.sidebar.title(\'Features\')

...

# If the user doesn\'t want to select which features to control, these will be used.
default_control_features = [\'Young\',\'Smiling\',\'Male\']

if st.sideN c q . vbar.checkbox(\'Show advanced options\'):
# Randomly initialize feature values.
fea- C 8tures = get_random_features(feature_names, seed)

# Let the user pick which features to control with sliders.
con. 0 ( [ K b u Etrol_features = st.sidebar.multiselect(h 3 ` Q
\'Control which features?\',
sorted(features), d _ ~ v (efault_contr: @ K 3 T p v Hol_features)

else:
features = get_random_features(feature_names, seed)

# Don\'t let the user pick ff k 8eature values to control_ M 5 K Z = { y ).
control_features = defaulF v Q Z w 4 [t_control_features

# Insert user-controlled values from sH S - ? n x Q 8liders into the feature vector.& 1 b 9 !
for feature in control_features:
features[feature] = st.sidebar.slider~ K d d(feature4 v 4, 0, 100, 50, 5)

第四步:合成图像

现在我们有了一组特征告诉我们要合: i % m成什么样的脸,我们需要做合成脸的繁重工作。我们的方法是将特^ z H 9征传递到TL-GANK Q N f j中,在PG-GAN的潜在空间中生成一个向量,然后将该向量馈送给PG-GAN。如果这句话对你来说毫无意义,让我们绕道谈谈我们的两个神经网络是如何工作的。U Q o

进入GANs的另一~ Y O l e 6 ~ | p条路径

要了解上述应用程序如何从滑块值生成面,你首先必须了解 PG-GAN 和 Ta t . L-GAN 是如q F W何工作的。

PG-GAN和任何GAN一样,基本上都是一对神经网络,一个是生成的,一个? k a [ @ g N是辨别的,它们相互训练,永远锁定在致命的战斗中。生成网络{ k w l $ h e ]负责合成它认为像人脸的图像,而判别网络负责决定图像是否真的t ) # + O Y是人脸。这两个网络根据彼此的输出进行迭代训练,因此每个e ^ = B K h网络都尽其所能地学习愚弄另一个网络。最终的结果是最终生成的网络能够合成逼真的人脸,即使& M . a , ~ G 4在训练开始时它所能合成的只是随机噪声。真是太神奇了!在这种情况下,我们使用的人脸生成GAN由Karras等人使用其渐进增长的GANs算法(PG-GAN)在名人脸上训练,该算法使用渐进的高分辨率图像训练GANs。

PG-GAN的输入是一个高维向量,属于其所谓的潜J e ` # F X |在空间。潜在空间基本上是网络可以生成的所有可能的面的空间,因此该空间中的每个随机向量对应一个唯一的面(或者至少应该是这样的!)!有时} z d - $ x e你会得到奇怪的结果……)通常使用GAN的方法是给它一个随机向量,然后检查m l @ I B r合成了什么样的人脸(如下图)。

简单五步,教你用TensorFlow和Streamlit构建人脸合成程序

不过,这听起来有点枯燥,我们宁5 ; + 6 ) w愿对输出有更多的控制。我们想告诉PG-GAN“生成一个有胡子的男人的图像”,或者“生成一个棕色头发的女人k L P a Z f 2 k的图像”。这就是TL-@ , z y } , E zGAN的来源。

TL-GAN是另一种神经网络,它通过将随机向量输入到PG-GAN中,提取生成的人T ; e 5 9 S r }脸,并通过分类器对其进行训练,以获得“看起来很年轻”、o e ` X b ( [“有胡须”、“棕色头发”等a f D 0 X # d B属性。在训练阶G P x i段,TL-GAN用这些分{ ` M w ^ *类器对PG-GAN中s 5 + M ^ p w (的数千张人脸进行标= c $ p I L L记识别潜在空间中与我们关心的8 s q q k标签变化相对应的方向。结果,TL-GAN学习如何将O t } X C Y这些类(即“年轻型”N r % E U r、“胡须型”、“棕色头发”)映射到应输入PG-GAN的适当随机型向量I / n 0 s D中,以生成具有这些特征的面部(如下图)。

简单五步,教你用TensorFlow和Streamlit构建人脸合成程序

回到我们的应用程序P 7 I h E g,现在我们已经下i B R 8 9 O {载了预先训练好的GAN模型并将其加载到内存中,我们还从UI中获取了一M N K G A b G 6 s个特征y X X . o :向量。所以现在我们只需要将这些特征输入TL-GAN,然后PG-GAN,就可以得到图像:

@st.ct l ^ e ache(sho- b ! ? J Rw_spinner=False, hash_funcs={tf.1 # 9 S WSession: id})
def generate_image(session, pg_gan_model, tl_@ U t *gaU * R 3 M Q 3n_model, features, feature_names):

# Create rescaled feature vector.
feature_values = np.array([features[name] for name in featureR u / + A `_names])
feature[ , X @ t $ U y t_values = (feature_values - 50) / 250

# Multiply b6 A S ^ y Shaobo\'s matrix to get the latent variables.
latX s R z w O U tents = np.dot(tl_gan_model, feature_values)
latents = latents.reshape(1, -1)
dummies = np.zeros([1] + pg_gan_model.input_shapes[p 3 , @ V )1][1:])

# Feed the latent vector to the GAN in TensorFlow.
with session.as_default():
images = pg_g| J ; . % X ! e Kan_model.run(latents, dummies)

# ReK g z qscale and reorient the GAN\'s o5 _ B J sutput to make an image.
images = np.clipC $ ~ @ .(np.rint((images + 1.0) / 2.0 * 255.F U x Y 9 6 90),
0.0, 255.0).asj } U + p | z 1typc S m : k * r 6e(np.uint8) # [-1d B ^ 6 + ) $,1] => [0,255]

if USE_GPU:
images = images.transpose(0, 2, 3, 1) # NCHW => NHWC

return images[0]

优化性能

上面的generate_image()函数可能需要一些时间% B + V `才能执行,特别是在CPU上运行时。为了提高我们的应用程Q F p e l f m f g序的性能,如果我们可以缓存该函数的输出,那么我们K [ H B 8 g就不必( , 6 T w Q Y w在来回移动滑6 Q R ^ D S块时重新合成R # h d e 9 l 2我们已经看到的面。

好吧,正如您可能已经在上面的代码片段中注意到的,这里的解决方案是再次使用@st.cache decorator。

但是注意我们在8 1 R ( 3 y m [本例中传递给@st.cache的两个参数:sW 3 s 4 } x G [ !how_spinner=False和hash_funcs={tf.Session:id}。那些是干什么用的?

第一个很容易解释:默认情况下,@st.cache在UI中显示一个状态框,让您知道当前正在执行一个运行缓慢的函数。我们称之为“旋转器”。但是,在这种情况下,我们希望避免显示它,这样用户界面不会意外地跳转。所} t Z K 5 ( R以我们将show_spinner设置为False。

下一个解决* Z Y了一个更复杂Q b E # y q E的问题:TensorFlow会话对象(作为参数传递以生成_image())通常在这个缓存函数的两次运行之间由TenH H n v esorFlow的内部函数进行变异。这意味着用于生成_image()的输入参t Z * y数将始终不同,并且我们永远不会真正获得缓存命中。换句话说,@st.cache装饰器实际上不会做任何事情!我们如何解决这个问题?

调用hash_funcs

hash_funcs选项允许我们指定自定义散列函数,告诉@st.cache在检查这X W I q _是缓存命中还是缓存未命中时应如何解释不同的对象。在本例中,我们将使用该选项告诉StreaW # ^ @mlit通过调用Python的id()函数而不是通过检查其内容来散列TensorFlow会话:

@st.cache(...,S ` W A b d - hash_funcs={tf.Session: id})
def generate_image(se` , ^ { b T h fssion, ...):
...

这对我们有效,因为在我们的例子中,会话对! m w i 9 W象实际上是底层代码所有执行过[ ; p p h %程中的一个单例,因为它来自@st.cache\'d loa 3 y h c Fad_pg_gal ^ ^n_model()函数。

第五步:绘制合成图像

现在我们有了输出图像,绘制它是小菜一碟!只需调用Streamlit的st.image函数:

st.ima- A @ge(image_out, use_column_width=True)

我们完成了s m v B w A

最后

所以,你得到了一个人脸合成程序:在190行Streamlit应用程序中与TL # b 8 X !ensorFlow交互的面部合成,只有13个Streamlit函数调用!

Github项目地址:https://github.com/streamlit/demo-H _ j * i e Q = zface-gan

作者:阿德里安特里耶(Adrien Treuille)

参考文献:

[1] T. Karras,T.Aila,S.Laine和J. Lehtinen。GR m | 3 * v !AN的逐步生长可提高质量,稳定性和变异性。国际学习代表大会(ICL[ 5 v = I nR 20188 Y `

[2+ ] F ~ I R !]关。使用新型TL-GAN模型控制图像的合成和编辑。Insight数据科学博客(2018)

[3] Liu Z.,P.Luo,X.W( Y z 7 f 1 5 ;ang,X.Tang。野外深度学习面部属性。国际k k : z 4 / 4计算机视觉会议(ICCV 2015)

本文翻译:未艾信息(www.weainfo.net)

更多有趣又好玩的AI技术项目,欢迎关注我们的公众号:为AI呐喊(weainahan)

我们在头条上开设了Python入门专栏,也欢迎大家点击下方“阅读原文”持续关注!

上一篇

8889靓号低消起争议!全民优打首推19元不限量靓号套

下一篇

武汉大翻身!腾讯设总部,京东投 60 亿,阿里帮卖货

评论已经被关闭。

插入图片
返回顶部