gradioでAIモデルのデモアプリを作成しながら基本機能を確認していく

この記事はBrainPad Advent Calender 15日目の記事です
# はじめに

今回は機械学習やデータサイエンスのデモに用いることができるwebインターフェイスライブラリーであるgradioを紹介させていただきます

[huggingface spaces](https://huggingface.co/spaces)で公開されている様々な種類の深層学習を使ったアプリはgradioもしくはstreamlitで構築されておりhuggingfaceユーザーには馴染み深いものとなってきています


# Gradioとは

[Gradio](https://gradio.app/)は、PythonベースのWebインターフェースを構築できるオープンソースライブラリです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/b7ebfc06-89c5-f132-a930-3b2103763558.png)


PythonベースのWebインターフェイスライブラリーはgradio以外にもstreamlitやdashなどのライブラリもありますが、gradioの場合には機械学習や深層学習の推論を簡単にデモアプリで利用できるようなコンポーネントが多数用意されています

## Gradioの特徴
gradioは以下のような特徴を持ったwebインターフェイスライブラリーとなります

### Good
- Google Colaboratory上で簡単なデモアプリを作れる
- アプリは72時間は無料でホスティングされる
- 深層学習の推論結果をインタラクティブに確認できる

### Bad
- 現状では恒久的なデプロイ先がhuggingface spacesしか用意されていない
- 機能としてはデモアプリに過ぎないので本番サービスには向かない

## コンポーネント

gradioにとって最も基本的な[コンポーネント](https://www.gradio.app/docs/#components)は`Interface`もしkは`Blocks`となります

どちらも似たような機能ですが、`Blocks`の方がカスタマイズ性が高いので、多くの場合は`Blocks`を用いてインターフェイスを構築することになると思います

複雑な処理(タブごとに画面を分割したり複数の機能を組み合わせたり)が必要ない場合には`Interface`での実装を選択すると良いでしょう


```python:Interfaceで構築
def hello(name):
    return "Hello " + name + "!"

gr.Interface(hello, "textbox", "text").launch()
```
たったこれだけのコードで簡易的なデモアプリをデプロイできます


**出力イメージ**
![hello_interface.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/4689a664-9c3a-0c76-608e-76a4f43be4be.gif)


`Blocks`の場合はBlocks内で複雑な処理を記載することが多いのでwith句を使って表現することが大半です
```python:Blocksで構築
def hello(name):
    return "Hello " + name + "!"

with gr.Blocks() as demo:
    name = gr.Textbox(label="name")
    output = gr.Textbox(label="output")
    greet_btn = gr.Button("Hello")
    greet_btn.click(fn=hello, inputs=name, outputs=output)

demo.launch()
```

**出力イメージ**
![hello_interface.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/cfe7e4b4-2318-ec32-dc3c-02694ce1fd21.gif)


# 具体例を見ていく
gradioが用意している[コンポーネント](https://www.gradio.app/docs/#components)を逐一紹介していくよりも、活用イメージが湧きやすい具体的なデモを実装するかhuggingface spacesで公開されているデモアプリを引用しながら機能を紹介していきます


## 事前準備
本記事ではGoogleColaboratory上で処理を行なっていくことを前提に記載していきます

また、Colaboratoryにはgradioは事前にインストールされていないので、事前にpip installしておくこととします

```
!pip install gradio
```


## 自然言語

### Whisperを用いた文字起こしデモアプリ　【hugginface spaces】 
マイクで音声を収録して、その音声をWhisperを用いて文字起こししてくれるデモアプリを見つつ、音声入力に関するgradioの機能を確認します

https://huggingface.co/spaces/openai/whisper


**ポイント**
:heavy_check_mark:　`Audio`を用いてマイクによる音声入力を受け付ける

```py:音声入力部分の機能を抜粋
audio = gr.Audio(
    label="Input Audio",
    show_label=False,
    source="microphone",
    type="filepath"
)
```

**出力イメージ**
![whisper.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/ec047ff9-f211-7060-ffef-d6ff4d0f06d7.gif)


## 画像
画像に関する自作のデモアプリと公開されているデモアプリを参考にgradioの機能を確認していきます

### 画像のセマンティックセグメンテーションのデモアプリを作成する
facebookのMaskFormerを用いてセマンティックセグメンテーションを行います

```py:MaskFormerによるセマンティックセグメンテーション
!pip install -q gradio git+https://github.com/huggingface/transformers torch

import torch
import random
import gradio as gr
import numpy as np
from transformers import MaskFormerFeatureExtractor, MaskFormerForInstanceSegmentation

device = torch.device("cpu")

model = MaskFormerForInstanceSegmentation.from_pretrained("facebook/maskformer-swin-tiny-ade").to(device)
model.eval()
preprocessor = MaskFormerFeatureExtractor.from_pretrained("facebook/maskformer-swin-tiny-ade")


def input_img(img):
    target_size = (img.shape[0], img.shape[1])
    inputs = preprocessor(images=img, return_tensors="pt")

    with torch.no_grad():
        outputs = model(**inputs)

    outputs.class_queries_logits = outputs.class_queries_logits.cpu()
    outputs.masks_queries_logits = outputs.masks_queries_logits.cpu()
    results = preprocessor.post_process_segmentation(outputs=outputs, target_size=target_size)[0].cpu().detach()
    results = torch.argmax(results, dim=0).numpy()
    results = visualize_segmentated_mask(results)
    return results

def visualize_segmentated_mask(mask_image):
    image = np.zeros((mask_image.shape[0], mask_image.shape[1], 3))
    labels = np.unique(mask_image)
    label_color = {label: (random.randint(0, 1), random.randint(0, 255), random.randint(0, 255)) for label in labels}

    for i in range(image.shape[0]):
      for j in range(image.shape[1]):
        image[i, j, :] = label_color[mask_image[i, j]]

    image = image / 255
    return image

with gr.Blocks() as app:
    input = gr.Image(label="Image")
    output = gr.Image(label="Output")
    submit_btn = gr.Button(label="Submit")
    submit_btn.click(fn=input_img, inputs=input, outputs=output)

app.launch()
```

**ポイント**

:heavy_check_mark:　`Image`を用いて画像の入力を受け付ける

```py:Image部分を抜粋
input = gr.Image(label="Image")
output = gr.Image(label="Output")
```

**出力イメージ**
![semantic.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/5b0b1e37-219b-9779-6839-29b1e585f71e.gif)


### YOLOV7による物体検出デモアプリ　【hugginface spaces】 
YOLOV7を用いた物体検出アプリの公開デモアプリを見つつ、画像の入力機能と物体検出モデルの選択する機能の実装を確認します

https://huggingface.co/spaces/akhaliq/yolov7


**ポイント**
:heavy_check_mark:　`Image`を用いて画像の入力を受け付ける
:heavy_check_mark:　`Dropdown`を用いて利用するモデルを切り替える


```py:Image機能とDropdown機能部分を抜粋
gr.Interface(
detect,[gr.Image(type="pil"),gr.Dropdown(choices=model_names)],
gr.Image(type="pil"),title="Yolov7",examples=[["horses.jpeg", "yolov7"]],
description="demo for <a href='https://github.com/WongKinYiu/yolov7' style='text-decoration: underline' target='_blank'>WongKinYiu/yolov7</a> Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors"
).launch()
```

**出力イメージ**

![yolo7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/4d59633a-60aa-3b0a-be00-4f3517e52d20.gif)



## 予測
最後に予測モデルの推論結果を表示するアプリを作成してきます

### 時系列予測比較デモアプリを作成
Prophetを用いて時系列予測を行い、予測期間を変更しながら時系列予測結果を描画するデモアプリを作成します


```py:Prophetによる時系列予測モデル
import gradio as gr
import pandas as pd


from prophet import Prophet


def plot_forecast(example_name, period):
    df = pd.read_csv(f'https://raw.githubusercontent.com/facebook/prophet/main/examples/example_{example_name}.csv')
    df.columns = ['ds','y']

    m = Prophet()
    m.fit(df)
    future = m.make_future_dataframe(periods=period)
    forecast = m.predict(future)
    fig = m.plot(forecast)
    return fig

with gr.Blocks() as demo:
    gr.Markdown(
    """
    時系列予測モデルの結果
    """)
    with gr.Row():
        example = gr.Dropdown(["air_passengers", "pedestrians_covid", "retail_sales"], label="データソース", value="air_passengers")
        period = gr.Slider(25, 250, 25, step=25, label="予測期間")
 
    plt = gr.Plot()

    example.change(plot_forecast, [example,period], plt, queue=False)
    period.change(plot_forecast, [example,period], plt, queue=False)
    demo.load(plot_forecast, [example,period], plt, queue=False)    

demo.launch()
```

**ポイント**
:heavy_check_mark:　`Markdown`を用いて概要説明を加える
:heavy_check_mark:　`Dropdown`を用いて利用するデータを切り替える
:heavy_check_mark:　`Slider`を用いて予測範囲をインタラクティブに変更する


```py:Markdown,Dropdown,Slider機能を抜粋
gr.Markdown("""時系列予測モデルの結果""")
with gr.Row():
    example = gr.Dropdown(["air_passengers", "pedestrians_covid", "retail_sales"], label="データソース", value="air_passengers")
    period = gr.Slider(25, 250, 25, step=25, label="予測期間")
```

**出力イメージ**
![timesiries.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/eb8c5933-4e2d-dc30-e662-9598f062c251.gif)

# 開発周りの疑問
## デプロイはどうするのか？
現状gradioを永続的にホスティングしたければhuggingface spacesにデプロイするしか方法がありません
ただし、72時間以内であれば`demo.launch(share=True)`でデプロイすればパブリックなURLが発行され、どこからでもアプリにアクセスできます

https://www.gradio.app/sharing_your_app/

## デプロイ時にAuthは設定できるのか？
パブリックなURLが発行される場合にはAuthの設定が欲しいところですが、以下の処理を加えることで簡易的な認証画面を設定できます

```py
# 任意のユーザーIDとパスワードを設定
demo.launch(auth=("user", "password"), share=True)
```
**認証画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55225/f31bee43-06dc-f1e0-a3a9-a6fe33e90dfe.png)

## プライベートな環境でデモアプリを共有できるか？
現状gradioを永続的にデプロイできる環境がhuggingface spacesに限られているため、huggingfaceのPrivate Hubをリクエストしない限りはプライベートな環境でgradioのデモアプリを共有することはできない状況です

https://huggingface.co/platform

---

おしまい

# 参考文献
https://huggingface.co/

https://www.gradio.app/

https://qiita.com/AI_Academy/items/6fcc1bd5f3f540f67ef0

https://qiita.com/hideki/items/61e25b07901f1cdec679

https://cpp-learning.com/gradio/

https://zenn.dev/robes/articles/fb99590d9ec9f2

https://towardsdatascience.com/deploying-gradio-app-on-spaces-using-dagshub-a-beginners-tutorial-a42664abcc14

https://www.freecodecamp.org/news/how-to-deploy-your-machine-learning-model-as-a-web-app-using-gradio/