CCCマーケティング TECH Labの Tech Blog

TECH Labスタッフによる格闘記録やマーケティング界隈についての記事など

PythonのライブラリGradioを使ってMLモデルのアプリを作ってみました。

こんにちは、技術開発ユニットの三浦です。

3月に入り、だいぶ暖かくなり、風も柔らかく感じるようになりました。近所にある小さな梅の木に、緑色をした小鳥が何羽かとまっていました。「ウグイスだなぁ・・・」としみじみ眺めていたのですが、あとで調べたらその小鳥はウグイスではなく「メジロ」という鳥でした。メジロの方が、自分にとっての「ウグイス色」に近い色をしていることが分かりました。

さて、私には気になったネタをあとで調べようと残しておくメモがあるのですが、今日はその中から1つ、気になっていた「Gradio」というPythonのライブラリを取り上げてみたいと思います。

このライブラリを使えば、自分で作った機械学習のモデルを組み込んだ簡単なアプリをあっという間に作れてしまいます

Gradioについて

Gradioは機械学習(ML)のモデルを利用するアプリを簡単に作成することができるライブラリです。

gradio.app

私たちの業務の中で、サービスやシステムに搭載するMLモデルを構築することがあります。

その場合、まず利用可能なデータを使ってモデルの学習と検証を行い、精度による評価基準をパスしたり、あるいは定められた期間が過ぎると今度はモデルをテストするフェーズに移ります。

テストフェーズではサービスやシステムで実際に扱われる、あるいはそれに限りなく近いデータを使って、その利用ユーザーにモデルの性能を評価して頂きます。ここでの評価指標は状況に応じて変わります。

そして評価してみると、モデルが上手く判断できないデータが見つかることがあります。それらのデータを見て、モデルの改善のために必要な方針などを検討し、再度モデルを作り、テストフェーズに移る・・・といったことを繰り返していきます。

テストの際には簡単な画面(UI)を用意して、モデルを利用できるようにすることが多いです。また、ちょっとしたデモで技術のプレゼンをする際も、UIがあった方が内容が断然分かりやすくなります。

しかし、機械学習とUI開発を両方こなそうとするとけっこうな負担になります。普段UI開発を専門にしていない機械学習寄りの人が、簡単にUIを用意できる仕組みがあるといいのにな・・・と思っていました。

Gradioを使うと、Pythonだけで簡単にMLモデルを試せるアプリを作ることが出来ます。今回実際にいくつか作ってみましたのでご紹介します。

手書き数字認識アプリ

まずはこちらをご覧ください。

f:id:miu4930:20220306193051g:plain
手書き数字認識アプリ

左のスケッチに書いた数字を判定するアプリです。手書き数字画像データ「MNIST」を使って学習したモデルがアプリの裏側で動いています。

このアプリ、UIはGradioを使って以下のような非常にシンプルなコードで実装しています。

import gradio as gr

gr.Interface(fn=number_recognision, 
             inputs="sketchpad",
             outputs="label",
            ).launch()

inputs="sketchpad"でアプリの入力、outputs="label"で出力を決めています。入力と出力の間の処理はfn=number_recognisionで指定しているnumber_recognisioin関数で実装しています。

number_recognisionは以下のようになっています。

import tensorflow as tf

#model = ...... #tensorflow.kerasのモデル

def number_recognision(img):
    img = img[tf.newaxis,...,tf.newaxis]
    pred = model.predict(img / 255.0)[0]
    values, indices  = tf.math.top_k(pred, k=3)
    values = values.numpy().tolist()
    indices = indices.numpy().tolist()
    confidences = {str(i): v for i, v in zip(indices, values)}

    return confidences

modelはあらかじめ学習済みのtensorflow.kerasのモデルです。このモデルが出力する予測スコアの上位3つのラベルとスコアを辞書型で返します。この関数の結果を受け取って、Gradioは出力コンポーネント"label"に結果を反映します。

アプリのUIには、デフォルトで「Flag」というボタンが表示されています。このボタンをクリックすると、今表示されている画像をファイルとして保存することが出来ます。例えば上手くモデルが認識出来なかった画像を残しておき、後で確認したい時に使えます。

また、もう少し細かく画像を分けることも出来ます。Interfaceの生成時にオプションflagging_optionsに分類用のラベルリストを指定します。すると、「Flag」クリック時に分類用のラベルを指定することが出来るようになります。

f:id:miu4930:20220306193501p:plain
認識に成功した、失敗した画像を残すことが出来ます。

以下のように指定します。

gr.Interface(fn=number_recognision, 
             inputs="sketchpad",
             outputs="label",
             flagging_options=['合ってる','間違ってる']
            ).launch()

画像と一緒にCSV形式のログファイルも出力されており、そちらにどの画像にどのラベルが付けられたのかが記録されます。

f:id:miu4930:20220306193612p:plain
どの画像にどのラベルが付いたのかを確認出来ます。

Gradioには様々なinputs/outputs用のコンポーネントが用意されています。他にも見ていきましょう。

学習済みモデル評価用アプリ

次に、画像処理の学習済みモデル自体の予測性能を比較することが出来るアプリを作ってみました。

f:id:miu4930:20220306194438g:plain
学習済みモデル評価用アプリ

画像を選び、性能を見たいモデルをラジオボタンで選択するとそのモデルのImageNetデータセットラベルの予測結果を見ることが出来ます。

Examplesに表示された画像リストから選んでもいいし、好きな画像を使って予測結果を見ることも出来ます。

どの学習済みモデルをベースモデルに利用したらよいか、見極めるのに便利かな・・・と思いました。

以下、コードになります。

from tensorflow.keras.applications import mobilenet, resnet50, efficientnet
import gradio as gr

import requests

#評価するモデルの読み込みと前処理の紐づけ
models = {
    'MobileNet':{
        'model':mobilenet.MobileNet(
            weights='imagenet'
        ),
        'preprocess':mobilenet.preprocess_input
    },
    'ResNet50':{
        'model':resnet50.ResNet50(
            weights='imagenet'
        ),
        'preprocess':resnet50.preprocess_input
    },
    'EfficientNetB0':{
        'model':efficientnet.EfficientNetB0(
            weights='imagenet'
        ),
        'preprocess':efficientnet.preprocess_input
    }
}

#ImageNetのラベルデータの取得
response = requests.get("https://git.io/JJkYN")
labels = response.text.split("\n")


#input画像を指定のモデルで推論する
def classify_image(image, model_label):
    image = image.reshape((-1, 224, 224, 3))
    preprocess = models[model_label]['preprocess']
    model = models[model_label]['model']

    prediction = model.predict(preprocess(image))[0]
    values, indices  = tf.math.top_k(prediction, k=5)
    values = values.numpy().tolist()
    indices = indices.numpy().tolist()
    confidences = {labels[i]: v for i, v in zip(indices, values)}

    return confidences

#Radioボタンに表示するモデル名リスト
model_labels = list(models.keys())

gr.Interface(
    classify_image,
    [
        gr.inputs.Image(shape=(224, 224)),
        gr.inputs.Radio(model_labels, type='value', label='BASE MODEL')
    ],
    'label',
    #サンプル画像
    examples=[
        ['image/sample1.jpg'],
        ['image/sample2.jpg'],
        ['image/sample3.jpg'],
    ]
).launch()

inputsはImageとRadioボタンの2つのコンポーネントで構成されています。それらをリストで指定しています。examples=オプションでサンプルの画像パスを指定します。

Chatbot風テキスト生成アプリ

これまで画像を扱うアプリを見てきましたが、Gradioでは自然言語を扱うアプリを作ることも出来ます。以前の記事でTransformersを使ってみた際に試してみたテキスト生成モデルを、Chatbot風に試すことが出来るアプリを作ってみました。

「Transformers」を使ってみた記事はこちらです。 techblog.cccmk.co.jp

使用したモデルは以前と同様、rinna株式会社様が公開している「rinna/japanese-gpt2-medium」というモデルです。

huggingface.co

このようなアプリが出来ました。

f:id:miu4930:20220306194944p:plain
Chat風テキスト生成アプリ

左のテキストボックスにテキストを入力して「Submit」すると、それに続く文章が生成され、右側にChat風に表示されていきます。

コードは以下のようになります。

import gradio as gr
from transformers import T5Tokenizer, AutoModelForCausalLM

model_name = 'rinna/japanese-gpt2-medium'
tokenizer = T5Tokenizer.from_pretrained(model_name)
tokenizer.do_lower_case = True  # due to some bug of tokenizer config loading
model = AutoModelForCausalLM.from_pretrained(model_name)

def text_generator(text, history):
    history = history or []
    input_data = tokenizer(text, padding=True, return_tensors='pt')
    prediction = model.generate(**input_data,
                            pad_token_id=tokenizer.eos_token_id,
                            do_sample=True, 
                            max_length=30, 
                            top_k=50, 
                            top_p=0.95, 
                            num_return_sequences=1
                )
    response = tokenizer.decode(prediction[0])
    #responseに含まれるインプットテキストを除外する
    response = response[len(text + '</s> '):]
    history.append((text,response))
    return history, history

gr.Interface(fn=text_generator, 
             inputs=['text','state'],
             outputs=['chatbot','state'],
             allow_flagging='never'
            ).launch()

inputsoutputs'state'というコンポーネントを指定しています。これはセッション内でデータを保存し続けるための仕組みで、chatbotのこれまでの会話が保存されます。

アプリを共有するには

このように、Gradioを使うと面白いアプリが簡単に作れて楽しいです。

作ったアプリを他のデバイスから利用することも出来ます。いくつか方法はありますが、1つはGradioのInterfaceがlaunch()メソッドを呼び出すときにオプションshare=Trueを指定すると発行される、共有リンクからアクセスする方法です。

仕組みとしてはGradioのサーバ経由でGradioのアプリが動いているデバイスにインターネット経由でアクセスするようです。Google Colaboratoryで実行すると、自動的に共有リンクが生成されます。共有リンクの有効期限は72時間です。

ただしこの共有リンクはインターネットのどこからでもアクセスすることが出来るため、使用する際には十分に注意する必要があります。できる限りデフォルトのshare=Falseの設定で使ったほうが良いと思います。

その場合はローカルの環境でport-forwardingを使って別のデバイスから利用することが出来ます。

f:id:miu4930:20220307104034p:plain
PC側でGradioアプリを動かし、同じWiFiネットワーク上のスマートフォンからport-forwardingを使ってアクセスしてみました

ちなみに「Flag」ボタンをクリックするとGradioアプリが稼働しているデバイス側にデータが保存されます。

まとめ

ということで、今回はMLモデルを試すアプリをGradioというライブラリを使って作ってみました。色々と活用出来るシーンは多いと思います。モデルを利用するだけでなく、画像の前処理をどうしようか、といったことを検討する段階でも使えそうです。たとえば色々なデータオーギュメンテーションの結果を比較してどれを使うかを決める・・・といった使い方です。

Gradioのように、便利なライブラリがたくさん公開されています。どういった動作をするのかをしっかりと調べたうえで、上手に付き合っていきたいなと思います。