Dash(+Docker)で機械学習アプリを作ってみたpart2 ~Dashの基本的な書き方~


はじめに

 PythonのWebアプリフレームワークDashを用いて簡単な機械学習アプリを作成したため、その学習記録として本記事を書きました(成果物はこちら)。前記事(part1)では、DockerでのDashの環境構築について紹介しました。本記事ではそれに続き、Dashアプリのメインとなるapp.pyの書き方について、二大要素であるLayout(アプリの外観を設定する部分)とCallback(対話的な動きを設定する部分)を中心に紹介していきます。基本的には公式のチュートリアルからの抜粋になるため、英語に抵抗のない方はそちらを読んでいただければと思います。また、HTMLやCSSについて、基本的な知識(Progateの無料で受講できる範囲程度)があると理解しやすいかと思います。

Layoutの書き方

 Layout部分ではアプリの外観を作っていきます。他のフレームワークではhtmlファイルを別で作成する場合が多いですが、Dashでは基本的に1シート(app.py)で作っていきます。Dashをインストールすると、HTMLタグを提供するパッケージdash_html_components、描画に必要なUIを提供するパッケージdash_core_componentsが自動的にインストールされ、これらを使ってLayoutを書いていきます。例として、環境構築で使用したものと同じapp.pyファイルを示します。

app.py
# ライブラリをインポート
import dash
import dash_core_components as dcc  #描画に必要なUIを提供するパッケージ
import dash_html_components as html #HTMLタグを提供するパッケージ

# 外部CSSのパスを指定
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

# アプリの起動。アプリ名を指定できる(この場合__name__=appになる)。外部CSSもここで指定する。
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)


######################### Layout部分 ####################################
app.layout = html.Div(children=[

    html.H1(children='Hello Dash'),
    html.Div(children='Dash: A web application framework for Python.'),
    dcc.Graph(
        id='example-graph',
        figure={
            'data':[{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                    {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'}],
            'layout':{'title': 'Dash Data Visualization'}
        }
    )
])
########################################################################

# サーバーの立ち上げ
if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=5050, debug=True)

 dash_html_componentsには、HTMLのタグ(div, H1など)に相当する同じ名前のものが存在するため、それらを使ってHTMLライクに書いていきます。上のapp.pyのLayout部分はHTMLで書くと、下のようになります。

<div>
    <h1>Hello Dash</h1>
    <div>Dash: A web application framework for Python.</div>
    <div id="exampl-graph" class="dash-graph">
        <!-- 描画内容 -->
    </div>
</div>

 引数childrenに、表示させるテキスト部分(HTMLでタグに挟まれる部分)を与えます。また、この引数にchildren=[html.H3(), html.H5(), ...]のようにリストを渡すことで、HTMLの入れ子構造を作ることができます。引数idではHTMLと同様に任意でidを指定することでき、idは主に後で紹介するCallbackの際に使用します。
 CSSの記述も同じシート上で行い、引数styleに辞書型で指定します。例えば、'Hello Dash'の部分に

html.H1(children='Hello Dash', style={'color':'red'})

のように追記すると、'Hello Dash'が赤く表示されます。
 プルダウン、チェックボックス、ボタンなどを実装するにはdash_core_componentsを使用します。全て紹介するとキリがないため割愛しますが、公式サイトのコード例を見ればとても簡単に実装できます。
 グラフの描画も同様にdash_core_componentsを使いdcc.Graph(figure=...)のように作っていきます。Plotlyの図を使用したい場合は、こちらにコード例が多く載っていますので、参考にしながら引数figureで指定します。その際、Plotlyが提供するパッケージのimportを記述する必要があるので注意してください。インストールはDashのインストール時にすでにされていると思います。

Callbackの書き方

 Callback部分では、 対話的な動きを設定していきます。例として、テキストボックスに入力された文字を、文章に反映させる簡単なアプリを取り上げます。

app.py
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash()

######################### Layout部分 ####################################
app.layout = html.Div([
    html.H6("Change the value in the text box to see callbacks in action!"),
    html.Div(['Input:',
        # テキストボックス部分
        dcc.Input(
            id='my-id', 
            value='initial value',  #初期値(指定しなくても可)
            type='text'
        )
    ]),
    # 文章を表示させる部分
    html.Div(id='my-div')
])
########################################################################

######################### Callback部分 ##################################
@app.callback(
    Output(component_id='my-div', component_property='children'),
    [Input(component_id='my-id', component_property='value')]
)
def update_output_div(input_value):
    return 'Output: "{}"'.format(input_value)
########################################################################

if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=5050, debug=True)

 CallbackはLayoutの下に、@app.callbackというデコレーターを使って書いていきます。デコレーターが何かわからなくても問題ありませんが、勉強したい方はこちらの記事がわかりやすいです。また、Callbackで使用するInputOutputもimportしておきます。

from dash.dependencies import Input, Output

Callbackの動きはとてもシンプルで、
   ①Inputで指定した要素がその下で定義した関数の引数として渡される
   ②その関数の出力がOutputで指定した要素に渡される
という2ステップです。InputとOutputの指定は、idとpropertyを指定することで行います。今回の例だと、①idが'my-id'のvalueの値、即ちテキストボックス部分であるdc.Input()のvalueの値(=UI操作で入力された値)がupdate_output_div関数の引数(input_value)として渡され、②この関数の出力結果('Output:〜')がid='my-div'であるhtml.Div()のchildrenに渡されます。これにより、テキストボックスに入力された文字列が下の'Output:'以降に反映させることができます。

※Callbackの注意点
 1つのCallbackにつき、Outputは1つしか設定できません。あるInputを複数のOutputに反映させたい場合は、その数だけCallbackを記述する必要があります。一方で、Inputは下のように複数受け取ることができます。

@app.callback(
    Output(component_id='id_3', component_property='children'),
    [Input(component_id='id_1', component_property='value'),
     Input(component_id='id_2', component_property='value')]
)
def func(input1, input2):
    return ...

おわりに

 Dashの基本的な書き方を紹介しました。一見複雑そうなアプリも基本的にはこのCallbackを組み合わせることで作ることができ、逆にこの仕組みさえ理解すれば、あとは公式のサンプルコードを見ながら様々なことに応用することができると思います。次の記事では応用例として、実際に私が作成した機械学習アプリを部分的に抜粋しながら紹介したいと思います。