#
Введение в 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 концепции, которые лежат в основе блоков:

1. Блоки позволяют создавать веб-приложения, сочетающие в себе разметку, HTML, кнопки и интерактивные компоненты, просто инстанцируя объекты на Python в контексте `with gradio.Blocks`.

> 🙋Если вы не знакомы с оператором `with` в Python, рекомендуем ознакомиться с отличным [руководством](https://realpython.com/python-with-statement/) от Real Python. Возвращайтесь сюда после его прочтения 🤗

Порядок, в котором вы инстанцируете компоненты, имеет значение, поскольку каждый элемент отображается в веб-приложении в том порядке, в котором он был создан. (Более сложные макеты рассматриваются ниже)

2. Вы можете определять обычные функции Python в любом месте вашего кода и запускать их с пользовательским вводом с помощью `Blocks`. В нашем примере мы используем простую функцию, которая “переворачивает” введенный текст, но вы можете написать любую функцию Python, от простого вычисления до обработки прогнозов модели машинного обучения.

3. Вы можете назначить события для любого компонента `Blocks`. Это позволит запускать вашу функцию при нажатии на компонент, его изменении и т. д. Когда вы назначаете событие, вы передаете три параметра: `fn`: функция, которая должна быть вызвана, `inputs`: (список) входных компонентов, и `outputs`: (список) выходных компонентов, которые должны быть вызваны.

   В примере выше мы запускаем функцию `flip_text()`, когда значение в `Textbox` с названием `input` изменяется. Событие считывает значение в `input`, передает его в качестве именнованного параметра в `flip_text()`, которая затем возвращает значение, которое присваивается нашему второму `Textbox` с именем `output`.

   Список событий, которые поддерживает каждый компонент, можно найти в документации Gradio.

4. Блоки автоматически определяют, должен ли компонент быть интерактивным (принимать пользовательский ввод) или нет, основываясь на определенных вами триггерах событий. В нашем примере первый 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.

Update on GitHub