🔥

Gradioを使ったデモアプリの作り方

2024/12/17に公開

本記事はCyberAgent AI Lab Advent Calendar 2024の17日目の記事です。

Pythonで機械学習モデルを作っても、コードを共有するだけでは、他の人に試用させるのは難しいです。特に、相手がPythonスクリプトから機械学習モデルを実行する経験がない場合や、モデルを実行するためのリソースがない場合はなおさらです。
本記事では、GradioというPythonパッケージを使った機械学習モデルのデモアプリケーションの簡単な作成方法を紹介します。

Gradioとは

Gradioは機械学習モデルのデモやウェブアプリケーションを素早く構築できるためのオープンソースのPythonパッケージです。Gradioのアプリケーションができたら、Gradioの機能などを使ってアプリを簡単に共有することができます。バックエンド、フロントエンドのエンジニアリングの知識が一切不要です。

簡単なGradioアプリケーションの例

import gradio as gr

def greet(name, intensity):
    return "Hello, " + name + "!" * int(intensity)

demo = gr.Interface(
    fn=greet,
    inputs=["text", "slider"],
    outputs=["text"]
)

demo.launch()

この例をapp.py として保存して、python app.py で実行すると、ブラウザからhttp://localhost:7860 でアクセスできます。

最初のデモを作成する際に、gr.Interfaceクラスを使用しました。このクラスは機械学習モデルのユーザーフレンドリーなデモを作成するために設計されています。gr.Interfaceには、以下の3つの主要なパラメータがあります:

  1. fn: UIと連携するPython関数。
  2. inputs: Gradioの入力コンポーネント(関数のパラメータの数と同じ数)。
  3. outputs: Gradioの出力コンポーネント(関数の戻り値の数と同じ数)。

fnを使えば、シンプルな計算処理から画像生成モデルなどの機械学習モデル実行まで、どんなPython関数でもUIでラップできる、とても柔軟な設計です。
inputsoutputs のパラメータには、1つ以上のGradioコンポーネントを指定できます。Gradioには、gr.Textbox()gr.Image()gr.Dropdown()など、機械学習アプリケーション向けに設計された30種類以上の組み込みコンポーネントが用意されています。

上の例を機械学習っぽい例に書き換えると、次のようなことができます。

import gradio as gr
from scripts.text_summarizer import generate_text_summary

def generate_text_summary_fn(text, brevity):
	summary = generate_text_summary(text, brevity)
    return summary

demo = gr.Interface(
    fn=generate_text_summary_fn,
    inputs=["text", "slider"],
    outputs=["text"]
)

demo.launch()

scripts/text_summarizer.pygenerate_text_summary という関数の存在が想定されているので、この例をそのまま実行できませんが、ユーザーが入力したテキストを要約するgenerate_text_summary という関数があれば、それを使って、要約を生成して、そして出力用のtextboxに表示します。

画像の扱い

画像編集、画像キャプション生成など、画像を入力として使ったAIのタスクが多いので、画像を読み込む機能も必要です。普通のフロントエンドのJavaScriptとかだと、画像をアップロードするための関数を書かないといけないですが、Gradioでは画像のアップロード機能がすでにGradioのコンポーネントとして用意されているので、inputsgr.Image() を呼ぶだけで画像アップロード用の画面がアプリケーションで表示されます。

以下のGradioのアプリで画像を読み込んで、フィルタをかけることができます。

import numpy as np
import gradio as gr

def sepia(input_img):
    sepia_filter = np.array([
        [0.393, 0.769, 0.189],
        [0.349, 0.686, 0.168],
        [0.272, 0.534, 0.131]
    ])
    sepia_img = input_img.dot(sepia_filter.T)
    sepia_img /= sepia_img.max()
    return sepia_img

demo = gr.Interface(sepia, gr.Image(), "image")
demo.launch()

カスタマイズ性を上げる方法

Gradioは、gr.Blocksクラスを使って、よりカスタマイズ性の高いレイアウトやデータフローを持つWebアプリを作成するための低レベルのアプローチを提供しています。gr.Blocksを使用すると、画面内のコンポーネントの配置を制御したり、複数のデータフローや複雑な操作を行ったり、ユーザーの操作に応じてコンポーネントのプロパティや表示/非表示を動的に更新することができます。

一番最初のデモをBlocksで書き換えるとこうなります。

import gradio as gr

def greet(name, intensity):
    return "Hello, " + name + "!" * int(intensity)

with gr.Blocks() as demo:
    name = gr.Textbox(label="Name")
    intensity = gr.Slider(label="Intensity")
    output = gr.Textbox(label="Output Box")
    greet_btn = gr.Button("Greet")
    greet_btn.click(fn=greet, inputs=[name, intensity], outputs=output, api_name="greet")

demo.launch()

まず、with gr.Blocks() as demo: を使うことでwithブロック内にアプリのコンポーネントやレイアウトが定義されます。
コンポーネントはInterfaceクラスで使用されるものと同じです。ただし、コンストラクタに渡すのではなく、gr.Blocks()withブロック内で作成されると同時にアプリに追加されます。
最後に、click()イベントリスナーについてです。イベントリスナーはアプリ内のデータフローを定義します。この例では、リスナーが2つのTextboxをつなぎ、nameのTextboxが入力として機能し、outputのTextboxがgreetメソッドの出力として機能します。このデータフローは、greet_btnボタンがクリックされたときにトリガーされます。Interfaceと同様に、Blocks内のイベントリスナーも複数の入力や出力を扱うことができます。

Blocksを使ったアプリは、Interfaceのように単一のデータフローに限定されていません。
こちらはマルチステップのデモの例です。一つのモデル(音声認識モデル)の出力がもう一つのモデル(感情分類器)に入力されます。

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"]  

with gr.Blocks() as demo:
    with gr.Row():
        with gr.Column():
            audio_file = gr.Audio(type="filepath")
            text = gr.Textbox()
            b1 = gr.Button("Recognize Speech")
        with gr.Column():
            label = gr.Label()
            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()

さらに、gr.Row()gr.Column() を使ってレイアウトを制御できます。この例ではツーカラムのレイアウトが定義されています。

デモを公開する方法

デフォルト設定ではデモが稼働しているマシンからしかアクセスできません。
launch関数のパラメータで share=True を設定することでデモを一時的に公開できます。

import gradio as gr

def hello_world(name):
    return "Hello World"

demo = gr.Interface(fn=hello_world, inputs="textbox", outputs="textbox")
    
demo.launch(share=True)

上記のやり方で実行したデモはローカルマシンで稼働するけど、共有用のURLが生成されてGradioのproxyサーバー(Gradio Share Server)を通して誰でもアクセスできるようになります。しかし、固定の期限があって、72時間で共有用のURLが無効になります。
Gradioのproxyサーバーの期限は変更できないので、3日間以上デモを共有したい場合は、方法を変えなければいけません。

Gradioのproxyの期限を解消するために、自身のクラウドサーバー上に独自のShare Serverを設定することも可能です。詳しく知りたい方はGradio Share ServerのGithubのレポジトリをご参照ください。

パブリック公開

自分のGradioのアプリを簡単で期限なしで誰でも使えるような形で公開したい場合、Hugging Face Spacesを利用できます。Hugging Face SpacesにGradioアプリをデプロイするには、次の2つの方法があります。

  1. ターミナルから:自分のアプリのディレクトリでgradio deployを実行します。CLIが基本的なメタデータを収集し、アプリを起動します。アプリを更新するには、同じコマンドを再実行するか、Github Actionsオプションを有効にしてgit push時にSpacesのアプリが自動的に更新しるようにします。
  2. ブラウザから:自分のGradioアプリとすべての関連ファイルを含むフォルダをここにドラッグ&ドロップします。

個人的に、定期的に更新する予定がなかったら2つ目の方法の方が楽だと思います。

Discussion