Создайте приложение для создания фотореалистичных граней с помощью TensorFlow и Streamlit

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


В статье показано, как сделать приложение для создания фотореалистичных лиц, используя TensorFlow и Streamlit.

Мы покажем вам, как быстро построить приложение Streamlit для синтеза лиц знаменитостей с помощью GANs, Tensorflow и st. cache.

Модели машинного обучения - это черные ящики. Да, вы можете запускать их на тестовых наборах и строить причудливые кривые производительности, но все равно часто бывает трудно ответить на основные вопросы о том, как они работают. Удивительно мощный источник озарения-это просто играть с вашими моделями! Настройка входов. Следите за выходами. Пусть ваши коллеги и менеджеры тоже играют с ними. Такой интерактивный подход - это не только мощный способ обрести интуицию, но и отличный способ заставить людей восхищаться вашей работой.

Создание интерактивных моделей - это один из вариантов использования, который вдохновил Streamlit, фреймворк Python, который делает написание приложений таким же простым, как написание скриптов Python. Этот обзор поможет вам создать приложение Streamlit, чтобы играть с одной из самых волосатых и черных коробок-iest моделей там: глубокая генеративная состязательная сеть (GAN). В этом случае мы визуализируем PG-GAN Nvidia [1], используя TensorFlow для синтеза фотореалистичных человеческих лиц из тонкого воздуха. Затем, используя удивительную модель TL-GAN Shaobo Guan [2], мы создадим приложение, которое даст нам возможность настроить синтезированные GAN лица знаменитостей по таким признакам, как возраст, улыбчивость, мужское сходство и цвет волос. К концу урока вы получите полностью параметрическую модель человека! (Обратите внимание, что мы не создавали атрибуты. Они пришли из набора данных CelebA [3], и некоторые из них могут быть немного странными…)

Начало работы с Streamlit

Если вы еще не установили Streamlit, вы можете сделать это, запустив:

pip install streamlit streamlit hello

И если вы опытный Streamlit-Эр, вам нужно будет быть на 0.57.1 или более поздней версии, так что не забудьте обновить!

pip install --upgrade streamlit

Настройка вашей среды

Прежде чем мы начнем, используйте команды ниже, чтобы проверить РЕПО проекта GitHub и запустить демо-версию Face GAN для себя. Эта демонстрация зависит от Tensorflow 1, который не поддерживает Python 3.7 или 3.8, поэтому вам понадобится Python 3.6. На Mac и Linux мы рекомендуем использовать pyenv для установки Python 3.6 вместе с текущей версией, а затем настроить новую виртуальную среду с помощью venv или virtualenv. В Windows навигатор Anaconda Navigator позволяет выбрать версию Python с помощью интерфейса point-and-click.

Когда все будет готово, откройте окно терминала и введите:

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

Дайте ему минуту, чтобы закончить загрузку обученного GAN, а затем попробуйте поиграть с ползунками, чтобы исследовать различные грани, которые GAN может синтезировать. Довольно круто, правда?

Полный код приложения - это файл, содержащий ~190 строк кода, из которых только 13 являются потоковыми вызовами. Это верно, весь пользовательский интерфейс выше нарисован только из этих 13 строк!

Давайте взглянем на то, как структурировано приложение:

def main():

st.title("Streamlit Face-GAN Demo")

# Step 1. Download models and data files.
for filename in EXTERNAL_DEPENDENCIES.keys():
download_file(filename)

# Step 2. Read in models from the data files.
tl_gan_model, feature_names = load_tl_gan_model()
session, pg_gan_model = load_pg_gan_model()

# Step 3. Draw the sidebar UI.
...
features = ... # Internally, this uses st.sidebar.slider(), etc.

# Step 4. Synthesize the image.
with session.as_default():
image_out = generate_image(session, pg_gan_model, tl_gan_model,
features, feature_names)

# Step 5. Draw the synthesized image.

st.image(image_out, use_column_width=True)

Теперь, когда у вас есть представление о том, как она структурирована, давайте погрузимся в каждый из 5 шагов выше, чтобы увидеть, как они работают.
Шаг 1. Загрузка моделей и файлов данных

Этот шаг загружает необходимые нам файлы: предварительно подготовленную модель PG-GAN и модель TL-GAN, предварительно приспособленную к ней (мы погрузимся в них немного позже!).

Служебная функция download_file немного умнее, чем чистый загрузчик:

Он проверяет, есть ли файл уже в локальном каталоге, поэтому загружает его только в случае необходимости. Он также проверяет, соответствует ли размер загруженного файла тому, что мы ожидали, поэтому он может исправить прерванные загрузки. Это отличный образец для подражания!

# If the file exists and has the expected size, return.

if os.path.exists(file_path):
if "size" not in EXTERNAL_DEPENDENCIES[file_path]:
return
elif os.path.getsize(file_path) == EXTERNAL_DEPENDENCIES[file_path]["size"]:

return

Он использует st. progress () и st.warning (), Чтобы показать хороший пользовательский интерфейс пользователю во время загрузки файла. А потом он звонит .пустой () на этих элементах пользовательского интерфейса, чтобы скрыть их, когда это будет сделано.

# Draw UI elements.

weights_warning = st.warning("Downloading %s..." % file_path)
progress_bar = st.progress(0)

with open(file_path, "wb") as output_file:
with urllib.request.urlopen(...) as response:

...

while True:

... # Save downloaded bytes to file here.

# Update UI elements.
weights_warning.warning(
"Downloading %s... (%6.2f/%6.2f MB)" %
(file_path, downloaded_size))
progress_bar.progress(downloaded_ratio)

...

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

progress_bar.empty()

Шаг 2. Загрузка моделей в память

Следующим шагом является загрузка этих моделей в память. Вот код для загрузки модели PG-GAN:

@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(MODEL_FILE_GPU if USE_GPU else MODEL_FILE_CPU, 'rb') as f:
G = pickle.load(f)

return session, G

Обратите внимание на декоратор @ st.cache в начале load_pg_gan_model (). Обычно в Python вы можете просто запустить load_pg_gan_model () и повторно использовать эту переменную снова и снова. Однако модель выполнения Streamlit уникальна тем, что каждый раз, когда пользователь взаимодействует с виджетом пользовательского интерфейса, ваш сценарий снова выполняется полностью, сверху донизу. Добавляя @st. cache к дорогостоящим функциям загрузки модели, мы говорим Streamlit, чтобы он запускал эти функции только при первом выполнении скрипта — и просто повторно использовал выходные данные кэша для каждого последующего выполнения. Это одна из самых фундаментальных функций Streamlit, поскольку она позволяет эффективно запускать сценарии, кэшируя результаты вызовов функций. Таким образом, большие подогнанные модели GAN будут загружены в память ровно один раз; и точно так же наш сеанс TensorFlow будет создан также точно один раз. (См. нашу статью запустить для повышения квалификации по модели исполнения Streamlit по.)

[Figure 1. How caching works in Streamlit’s execution model]

Однако есть одна загвоздка: объект сеанса TensorFlow может изменяться внутренне, когда мы используем его для выполнения различных вычислений. Обычно мы не хотим, чтобы кэшированные объекты мутировали, так как это может привести к неожиданным результатам. Поэтому, когда Streamlit обнаруживает такие мутации, он выдает предупреждение пользователю. Однако в этом случае мы случайно узнаем, что это нормально, если объект сеанса TensorFlow мутирует, поэтому мы обходим предупреждение, устанавливая allow_output_mutation=True.

Шаг 3. Нарисуйте пользовательский интерфейс боковой панели

Если это первый раз, когда вы видите API Streamlit для рисования виджетов, вот 30-секундный аварийный курс:

Вы добавляете виджеты, вызывая API-методы, такие как st.slider() и st.checkbox().
Возвращаемое значение этих методов - это значение, показанное в пользовательском интерфейсе. Например, когда пользователь перемещает ползунок в позицию 42, ваш скрипт будет повторно выполнен, и в этом выполнении возвращаемое значение этого st. slider () будет равно 42.
Вы можете поместить в боковую панель все, что угодно, добавив к ней st. sidebar. Например, ул. боковая.флажок().

Поэтому, чтобы добавить слайдер в боковую панель, например-слайдер, позволяющий пользователю настроить параметр brown_hair, вы просто добавите:

brown_hair = st.sidebar.slider("Brown Hair", 0, 100, 50, step=5)

# Translation: Draw a slider from 0 to 100 with steps of size 5.

# Then set the default value to 50.

В нашем приложении мы хотим немного пофантазировать, чтобы показать, насколько легко сделать сам пользовательский интерфейс модифицируемым в Streamlit! Мы хотим, чтобы пользователи могли сначала использовать виджет multiselect для выбора набора функций, которыми они хотят управлять в созданном изображении, а это означает, что наш пользовательский интерфейс должен быть нарисован программно:

С Streamlit код для этого на самом деле довольно прост:

st.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.sidebar.checkbox('Show advanced options'):
# Randomly initialize feature values.
features = get_random_features(feature_names, seed)

# Let the user pick which features to control with sliders.
control_features = st.sidebar.multiselect(
'Control which features?',
sorted(features), default_control_features)

else:
features = get_random_features(feature_names, seed)

# Don't let the user pick feature values to control.
control_features = default_control_features

# Insert user-controlled values from sliders into the feature vector.
for feature in control_features:

features[feature] = st.sidebar.slider(feature, 0, 100, 50, 5)

Шаг 4. Синтезировать изображение

Теперь, когда у нас есть набор признаков, говорящих нам, какое лицо синтезировать, нам нужно сделать тяжелую работу по синтезированию лица. Мы сделаем это, передав объекты в TL-GAN, чтобы создать вектор в латентном пространстве PG-GAN, а затем передать этот вектор в PG-GAN. Если это предложение не имеет для вас никакого смысла, давайте сделаем крюк и поговорим о том, как работают наши две нейронные сети.
Объезд в Ганс

Чтобы понять, как вышеприведенное приложение генерирует грани из значений слайдера, вам сначала нужно понять кое-что о том, как работают PG-GAN и TL — GAN-но не волнуйтесь, вы можете пропустить этот раздел и все равно понять, как приложение работает на более высоком уровне!

PG-GAN, как и любой GAN, по сути своей представляет собой пару нейронных сетей, одну генеративную и одну различительную, которые натренированы друг против друга, навсегда сцепившись в смертельной схватке. Генеративная сеть отвечает за синтез изображений, которые, по ее мнению, выглядят как лица, а дискриминирующая сеть отвечает за принятие решения о том, действительно ли эти изображения являются лицами. Эти две сети итеративно обучаются против выходных данных друг друга, поэтому каждая из них делает все возможное, чтобы научиться обманывать другую сеть. Конечным результатом является то, что конечная генеративная сеть способна синтезировать реалистичные лица, даже если в начале обучения все, что она могла синтезировать, было случайным шумом. Это действительно очень удивительно! В этом случае GAN, генерирующий лицо, который мы используем, был обучен на лицах знаменитостей Каррасом и др., Используя их прогрессивный алгоритм роста GANs (PG-GAN), который обучает GANs, используя изображения с прогрессивно более высоким разрешением. [1]

Входным сигналом для PG-GAN является высокомерный вектор, принадлежащий его так называемому латентному пространству. Латентное пространство-это в основном пространство всех возможных граней, которые может генерировать сеть, поэтому каждый случайный вектор в этом пространстве соответствует уникальной грани (или, по крайней мере, должен! Иногда вы получаете странные результаты...) обычно вы используете GAN, чтобы дать ему случайный вектор, а затем проверить, какое лицо синтезируется (Рис. 2.ля).

Однако это звучит немного скучно, и мы предпочли бы иметь немного больше контроля над выходом. Мы хотели бы сказать PG-GAN “создать образ мужчины с бородой “или”создать образ женщины с каштановыми волосами". Вот тут-то и появляется ТЛ-Ган.

TL-GAN - это еще одна нейронная сеть, которая обучается путем ввода случайных векторов в PG-GAN, взятия сгенерированных лиц и прогона их через классификаторы для таких атрибутов, как “молодо выглядит”, “бородат”, “шатен” и т. д. На этапе обучения TL-GAN помечает тысячи граней из PG-GAN с помощью этих классификаторов и определяет направления в скрытом пространстве, соответствующие изменениям в метках, о которых мы заботимся. В результате TL-GAN учится сопоставлять эти классы (т. е. “молодой на вид”, “бородатый”, "шатен") в соответствующий вектор случайного вида, который должен быть введен в PG-GAN для создания лица с этими характеристиками (Рис.2.б).

Возвращаясь к нашему приложению, на данный момент мы уже загрузили предварительно обученные модели GAN и загрузили их в память,а также захватили вектор функций из пользовательского интерфейса. Так что теперь нам просто нужно ввести эти функции в TL-GAN, а затем PG-GAN, чтобы получить изображение:

@st.cache(show_spinner=False, hash_funcs={tf.Session: id})

def generate_image(session, pg_gan_model, tl_gan_model, features, feature_names):

# Create rescaled feature vector.
feature_values = np.array([features[name] for name in feature_names])
feature_values = (feature_values - 50) / 250

# Multiply by Shaobo's matrix to get the latent variables.
latents = np.dot(tl_gan_model, feature_values)
latents = latents.reshape(1, -1)
dummies = np.zeros([1] + pg_gan_model.input_shapes[1][1:])

# Feed the latent vector to the GAN in TensorFlow.
with session.as_default():
images = pg_gan_model.run(latents, dummies)

# Rescale and reorient the GAN's output to make an image.
images = np.clip(np.rint((images + 1.0) / 2.0 * 255.0),
0.0, 255.0).astype(np.uint8) # [-1,1] => [0,255]

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

return images[0]

Оптимизация производительности

Приведенная выше функция generate_image () может занять некоторое время для выполнения, особенно при работе на центральном процессоре. Чтобы улучшить производительность нашего приложения, было бы здорово, если бы мы могли кэшировать выходные данные этой функции, поэтому нам не нужно повторно синтезировать лица, которые мы уже видели, перемещая ползунок вперед и назад.

Ну, как вы уже могли заметить в приведенном выше фрагменте, решение здесь заключается в том, чтобы еще раз использовать декоратор @st.cache.

Но обратите внимание на два аргумента, которые мы передали в @st. cache в этом случае: show_spinner=False и hash_funcs={tf.Идентификатор сеанса}. А для чего они здесь?

Первый из них легко объяснить: по умолчанию @st.cache показывает поле состояния в пользовательском интерфейсе, сообщая вам, что в данный момент выполняется медленно работающая функция. Мы называем это "вертушкой". Однако в этом случае мы хотели бы избежать его показа, чтобы пользовательский интерфейс не прыгал неожиданно. Поэтому мы установили show_spinner в False.

Следующий вариант решает более сложную проблему: объект сеанса TensorFlow, который передается в качестве аргумента в generate_image (), обычно мутирует внутренними функциями TensorFlow в промежутках между запусками этой кэшированной функции. Это означает, что входные аргументы для generate_image () всегда будут отличаться, и мы никогда не получим попадание в кэш. Другими словами, декоратор @st.cache на самом деле ничего не будет делать! Как мы можем решить эту проблему?

Hash_funcs к спасению

Опция hash_funcs позволяет нам указать пользовательские хэш-функции, которые говорят @st. cache, как он должен интерпретировать различные объекты при проверке, является ли это попадание в кэш или промах в кэше. В этом случае мы будем использовать эту опцию, чтобы сказать Streamlit хэшировать сеанс TensorFlow, вызывая функцию Python id (), а не изучая ее содержимое:

@st.cache(..., hash_funcs={tf.Session: id})

def generate_image(session, ...):

Это работает для нас, потому что объект сеанса в нашем случае фактически является синглетным во всех исполнениях базового кода, так как он исходит из функции @st.cache load_pg_gan_model ().

Для получения дополнительной информации о hash_funcs ознакомьтесь с нашей документацией о передовых методах кэширования.

Шаг 5. Нарисуйте собирательный образ

Теперь, когда у нас есть выходное изображение, нарисовать его-это просто кусок пирога! Просто вызовите функцию St.image Streamlit:

st.image(image_out, use_column_width=True)

И мы закончили!

Сворачивание дела

Итак, у вас есть это: интерактивный синтез лица с TensorFlow в 190-линейном приложении Streamlit и только 13 вызовов функций Streamlit! Получайте удовольствие, исследуя пространство лиц, которые эти два Гана могут нарисовать, и большое спасибо Nvidia и Shaobo Guan за то, что они позволили нам построить свои супер крутые демо-версии. Мы надеемся, что вам будет так же весело создавать приложения и играть с моделями, как и нам.


Источник: towardsdatascience.com

Комментарии: