Введение в Gradio Blocks
В предыдущих разделах мы изучили и создали демо, используя класс Interface
. В этом разделе мы представим наш свеже разработанный низкоуровневый API под названием gradio.Blocks
.
Итак, в чем разница между Interface
и Blocks
?
⚡
Interface
: высокоуровневый API, который позволяет создать полноценное демо машинного обучения, просто предоставив список входных и выходных данных.🧱
Blocks
: низкоуровневый API, который позволяет вам полностью контролировать потоки данных и компоновку вашего приложения. Вы можете создавать очень сложные, многоступенчатые приложения, используяBlocks
(как “строительные блоки”).
Почему Blocks 🧱?
Как мы видели в предыдущих разделах, класс Interface
позволяет легко создавать полноценные демо машинного обучения с помощью всего нескольких строк кода. API Interface
чрезвычайно прост в использовании, но ему не хватает гибкости, которую обеспечивает API Blocks
. Например, вы можете захотеть:
- Сгруппировать связанные демо в виде нескольких вкладок в одном веб-приложении
- Изменить макет вашего демо, например, указать, где расположены входные и выходные компоненты
- Многоступенчатые интерфейсы, в которых выход одной модели становится входом для следующей модели, или более гибкие потоки данных в целом
- Изменить свойства компонента (например, выбор в выпадающем списке) или его видимость на основе пользовательского ввода
Ниже мы рассмотрим все эти понятия.
Создание простого демо с помощью блоков
После того как вы установили Gradio, запустите приведенный ниже код в виде сценария на Python, блокнота Jupyter или блокнота Colab.
import gradio as gr
def flip_text(x):
return x[::-1]
demo = gr.Blocks()
with demo:
gr.Markdown(
"""
# Flip Text!
Start typing below to see the output.
"""
)
input = gr.Textbox(placeholder="Flip this text")
output = gr.Textbox()
input.change(fn=flip_text, inputs=input, outputs=output)
demo.launch()
В этом простом примере представлены 4 концепции, которые лежат в основе блоков:
- Блоки позволяют создавать веб-приложения, сочетающие в себе разметку, HTML, кнопки и интерактивные компоненты, просто инстанцируя объекты на Python в контексте
with gradio.Blocks
.
Порядок, в котором вы инстанцируете компоненты, имеет значение, поскольку каждый элемент отображается в веб-приложении в том порядке, в котором он был создан. (Более сложные макеты рассматриваются ниже)
Вы можете определять обычные функции Python в любом месте вашего кода и запускать их с пользовательским вводом с помощью
Blocks
. В нашем примере мы используем простую функцию, которая “переворачивает” введенный текст, но вы можете написать любую функцию Python, от простого вычисления до обработки прогнозов модели машинного обучения.Вы можете назначить события для любого компонента
Blocks
. Это позволит запускать вашу функцию при нажатии на компонент, его изменении и т. д. Когда вы назначаете событие, вы передаете три параметра:fn
: функция, которая должна быть вызвана,inputs
: (список) входных компонентов, иoutputs
: (список) выходных компонентов, которые должны быть вызваны.В примере выше мы запускаем функцию
flip_text()
, когда значение вTextbox
с названиемinput
изменяется. Событие считывает значение вinput
, передает его в качестве именнованного параметра вflip_text()
, которая затем возвращает значение, которое присваивается нашему второмуTextbox
с именемoutput
.Список событий, которые поддерживает каждый компонент, можно найти в документации Gradio.
Блоки автоматически определяют, должен ли компонент быть интерактивным (принимать пользовательский ввод) или нет, основываясь на определенных вами триггерах событий. В нашем примере первый textbox является интерактивным, поскольку его значение используется функцией
flip_text()
. Второй textbox не является интерактивным, поскольку его значение никогда не используется в качестве входа. В некоторых случаях вы можете переопределить это, передав булево значение в параметрinteractive
компонента (например,gr.Textbox(placeholder="Flip this text", interactive=True)
).
Настройка макета вашего демо
Как мы можем использовать Blocks
для настройки макета нашего демо? По умолчанию Blocks
отображает созданные вами компоненты вертикально в одной колонке. Вы можете изменить это, создав дополнительные колонки с помощью gradio.Column():
или строки с помощью gradio.Row():
и создав компоненты в этих контекстах.
Вот что следует иметь в виду: все компоненты, созданные в Column
(это также значение по умолчанию), будут располагаться вертикально. Любой компонент, созданный в Row
, будет располагаться горизонтально, аналогично модели flexbox в веб-разработке.
Наконец, вы можете создавать вкладки для демо с помощью контекстного менеджера with gradio.Tabs()
. В этом контексте вы можете создать несколько вкладок, указав дочерние вкладки в with gradio.TabItem(name_of_tab):
. Любой компонент, созданный внутри контекста with gradio.TabItem(name_of_tab):
, отображается на этой вкладке.
Теперь давайте добавим функцию flip_image()
в наше демо и добавим новую вкладку, которая будет переворачивать изображения. Ниже приведен пример с 2 вкладками, в котором также используется Row:
import numpy as np
import gradio as gr
demo = gr.Blocks()
def flip_text(x):
return x[::-1]
def flip_image(x):
return np.fliplr(x)
with demo:
gr.Markdown("Flip text or image files using this demo.")
with gr.Tabs():
with gr.TabItem("Flip Text"):
with gr.Row():
text_input = gr.Textbox()
text_output = gr.Textbox()
text_button = gr.Button("Flip")
with gr.TabItem("Flip Image"):
with gr.Row():
image_input = gr.Image()
image_output = gr.Image()
image_button = gr.Button("Flip")
text_button.click(flip_text, inputs=text_input, outputs=text_output)
image_button.click(flip_image, inputs=image_input, outputs=image_output)
demo.launch()
Вы заметите, что в этом примере мы также создали компонент Button
на каждой вкладке и назначили событие click для каждой кнопки, что, собственно, и запускает функцию.
Изучение событий и состояния
Так же, как вы можете управлять макетом, Blocks
дает вам тонкий контроль над тем, какие события вызывают вызовы функций. Каждый компонент и многие макеты имеют определенные события, которые они поддерживают.
Например, у компонента Textbox
есть 2 события: change()
(когда меняется значение внутри текстового поля) и submit()
(когда пользователь нажимает клавишу ввода, будучи сфокусированным на текстовом поле). Более сложные компоненты могут иметь еще больше событий: например, компонент Audio
также имеет отдельные события для воспроизведения аудиофайла, очистки, приостановки и так далее. О том, какие события поддерживает каждый компонент, читайте в документации.
Вы можете прикрепить триггер события ни к одному, одному или нескольким из этих событий. Вы создаете триггер события, вызывая имя события на экземпляре компонента в виде функции - например, textbox.change(...)
или btn.click(...)
. Функция принимает три параметра, как говорилось выше:
fn
: функция для выполненияinputs
: (список (list)) компонент(ов), значения которых должны быть переданы в качестве входных параметров функции. Значение каждого компонента по порядку сопоставляется с соответствующим параметром функции. Этот параметр может иметь значение None, если функция не принимает никаких параметров.outputs
: (список (list)) компонентов, значения которых должны быть обновлены на основе значений, возвращаемых функцией. Каждое возвращаемое значение устанавливает значение соответствующего компонента по порядку. Этот параметр может быть None, если функция ничего не возвращает.
Вы даже можете сделать так, чтобы компонент ввода и компонент вывода были одним и тем же компонентом, как это сделано в данном примере, где используется модель GPT для дополнения текста:
import gradio as gr
api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")
def complete_with_gpt(text):
# Используем последние 50 символов текста в качестве контекста
return text[:-50] + api(text[-50:])
with gr.Blocks() as demo:
textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4)
btn = gr.Button("Generate")
btn.click(complete_with_gpt, textbox, textbox)
demo.launch()
Создание многоступенчатых демо
В некоторых случаях вам может понадобиться многоступенчатое демо, в котором вы повторно используете выход одной функции в качестве входа для следующей. Это очень легко сделать с помощью Blocks
, так как вы можете использовать компонент в качестве входа для одного триггера события, но выхода для другого. Посмотрите на компонент text в примере ниже: его значение является результатом работы модели преобразования речи в текст, но также передается в модель анализа настроений:
from transformers import pipeline
import gradio as gr
asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
classifier = pipeline("text-classification")
def speech_to_text(speech):
text = asr(speech)["text"]
return text
def text_to_sentiment(text):
return classifier(text)[0]["label"]
demo = gr.Blocks()
with demo:
audio_file = gr.Audio(type="filepath")
text = gr.Textbox()
label = gr.Label()
b1 = gr.Button("Recognize Speech")
b2 = gr.Button("Classify Sentiment")
b1.click(speech_to_text, inputs=audio_file, outputs=text)
b2.click(text_to_sentiment, inputs=text, outputs=label)
demo.launch()
Обновление свойств компонента
До сих пор мы видели, как создавать события для обновления значения другого компонента. Но что делать, если вы хотите изменить другие свойства компонента, например видимость текстового поля или варианты выбора в группе радио кнопок? Вы можете сделать это, вернув метод класса компонента update()
вместо обычного возвращаемого значения из вашей функции.
Это легче всего проиллюстрировать на примере:
import gradio as gr
def change_textbox(choice):
if choice == "short":
return gr.Textbox.update(lines=2, visible=True)
elif choice == "long":
return gr.Textbox.update(lines=8, visible=True)
else:
return gr.Textbox.update(visible=False)
with gr.Blocks() as block:
radio = gr.Radio(
["short", "long", "none"], label="What kind of essay would you like to write?"
)
text = gr.Textbox(lines=2, interactive=True)
radio.change(fn=change_textbox, inputs=radio, outputs=text)
block.launch()
Мы только что изучили все основные концепции Blocks
! Как и в случае с Interfaces
, вы можете создавать классные демо, которыми можно поделиться, используя share=True
в методе launch()
или развернуть на Hugging Face Spaces.