Streamlitを使って数理最適化のGUIを実装する


はじめに

今回は誰でも簡単に使えると評判のStreamlitを使って単純な数理最適化問題のGUIを作成してみたいと思います。
製造業等、ITの専門家やSEが社内にほとんどいない業種でデータサイエンスを担う部署を作って人材を集めたとしても、ユーザーにちょっと使ってもらうためのプロトタイピングで頓挫することがあると思いますが(私はありました)、そういった非IT、SEの方々でも簡単に作れるプロトタイピングの参考になれば幸いです。

Streamlitを使うまでの準備

Streamlit公式サイトのこちらの手順に従って実施します。
最初何も考えずにAnaconda環境でpip install streamlitで試してみましたが、ライブラリがうまく読み込めない沼にハマってしましましたので、新しくStreamlit用の環境を作って対応することにしました。
※以下はMacOS環境で実行しています。

pipenvのインストール
# 準備その1
sudo easy_install pip
# 準備その2
pip3 install pipenv

次にStreamlit用の環境を作ります。

Streamlit用の環境を作る
#”myproject”というフォルダを作ってそこにディレクトリを変更
cd myproject
#新たらしい環境を構築。以下を実行すると”Popfile”ファイルがmyprojectの中に生成される。
pipenv shell

最後に必要なライブラリをインストールして準備完了です。

Streamlitと必要なライブラリを環境にインストールする
# Streamlitをインストールする
pipnev install streamlit
# 後で数理最適化に使うpulpをインストール
pipenv install pulp

今回使う数理最適化問題とPythonコード

数理最適化問題(ナップサック問題)

今回は以前書いたこちらの記事で使用したごくごく簡単なナップサック問題を解いてみようと思います。
以下の図のようなナップサックに牛乳、おにぎり、サンドイッチを、容量(制約条件)を満たす範囲で価値が最大化するように詰め込むという問題です。
今回は簡単な例として、ナップサックの容量をGUIで変えられるようにして最終的な結果(詰め込む品目、最大価値)がどう変わるかをシミュレーションできるようにプロトタイプを作っていこうと思います。

streamlitを使った実装

app.pyというファイルを作成し、Streamlitを使ってGUIを作るコードを以下のように作成しました。
今回は単純化のためにGUI上では制約条件としてナップサックの容量のみ変更できるようにしましたが、同様に複数の条件をユーザーがGUI上で設定して、数理最適化問題を解くことも可能です。
また、条件設定が完了してチェックボックスを押したら数理最適化計算が走るように設定しました。
※数理最適化の場合、問題が大規模、制約条件が100以上になる場合には処理時間もかかり始めるので、あくまで小規模なプロトタイピングとして使うのが良いと思います。

app.py
#必要なライブラリをインポート
import streamlit as st
import numpy as np
import pandas as pd
import pulp
from pulp import LpProblem, LpVariable, LpMaximize, LpBinary, LpStatus

#GUI上に表示するタイトルと、文章を設定
st.title("ナップサック問題をGUIで解く")
st.write("制約条件を入れてね")

#品物リスト
lt=["牛乳", "おにぎり", "サンドイッチ"]
#牛乳、おにぎり、サンドイッチの容量のリスト。それぞれ一個ずつある。
w=[1, 0.7, 0.5]
#牛乳、おにぎり、サンドイッチの値段のリスト
v=[200, 150, 120]
#制約条件:ナップサックの容量。GUI上で操作できるようにst.number_input関数を使用して設定。
W = st.number_input("ナップサックの容量を入れてね", min_value=1.0, max_value=10.0, value=1.0, step=1.0)

#後で使うfor loopのためにリストの長さを変数rに格納
r = len(w)
#数理モデルの箱を作成。今回は最大化問題。
problem = pulp.LpProblem(sense = pulp.LpMaximize)
#変数の設定。xというリストを使う。
x = [pulp.LpVariable("x%d"%i, cat = LpBinary) for i in range(r)]
#目的関数を設定
problem += pulp.lpDot(v, x)
#制約条件を設定
problem += pulp.lpDot(w, x) <= W

#実行ボタン。チェックボックスをGUI上でクリックしたら計算スタート。
agree = st.checkbox('条件設定が終わったらチェックしてね')
if agree:
    status = problem.solve()
    st.write("状態", pulp.LpStatus[status])
    tmp=[xs.value() for xs in x]
    st.write([lt[a] for a in range(len(lt)) if tmp[a] == 1])
    st.write("最大価値",problem.objective.value())

上記のコードを”app.py”と名前をつけて"myproject"に格納します。
これまでのコードを実行したあとのmyprojectの中は以下のようになっています。

以下のコマンドをターミナルに入力してStreamlitを起動します。

 Streamlit起動!
streamlit run app.py

StreamlitのGUIの確認

画像ではお試しでナップサックの容量を2で設定し、条件設定後のチェックボックスを押して最適化計算実行後の結果が出た状態になっています。
ナップサックの容量が2の条件では、単価の高い牛乳とおにぎりを1個づつ入れると容量合計が1.7になって他のものが入れられなくなるので、このときが最適値で、最大価値が350円になります。
試しにナップサックの容量を大きくしていくと、サンドイッチを入れる余裕も出てきて、GUI側のナップサックの中身のリストと最大価値が更新されるのが確認できると思います。

無事実装できましたね!
実際に作っていて非常に便利だと感じたのは、app.pyのコードを編集して保存するたびにGUI側も更新することができるので(常にRerunするみたいな設定をGUI側で設定できます)、コードを直しながらGUIの状態を見れる点です。
今回作っているときはの上図のように、エディターとStreamlitのGUI画面を並べながら、GUI側での動作を確認しながらGUIを使った数理最適化のプロトタイプを作っていくことができました。
今回始めてStreamlitでGUIを作りましたが、これは便利ですね。

おわりに

私の所属する会社(製造業)では社内にITの専門家もアプリの専門家もいないため基本的にGUIを手作りできないため、データサイエンスや数理最適化の検討をしてもなかなか現場で使ってもらうことが難しい、という状況がありました。
今回は簡単な例でしたが、こういう簡単なプロトタイピングができ、ユーザーが簡単に触れるようになれば日本の製造業でももう少し数理最適化や機械学習を使ったトライアルがしやすくなるのではないかと考えています。
この記事が誰かの検討の参考になれば幸いです。

P.S.
GUI作成ツールについては、データラーニングギルドの方々に大変多くの助言と候補となるツールを教えていただきました。この場を通じて感謝いたします。
今回はStreamlitを使いましたが、他にも以下のようなツールもあるそうです。ご参考。
- Dash
- pyQt
- Node-RED
- Bubble
- Adalo

参考文献