Streamlit in Snowflakeとは
Streamlitは簡単に機械学習/LLM向けのウェブアプリを簡単に作成できるオープンソースのPythonライブラリで、Streamlit in Snowflake(通称SiS)はSnowflakeのプラットフォーム上でStreamlitアプリを構築できる機能です。
SnowflakeのWeb UIであるSnowsight上でStreamlitアプリを開発・動作させることが可能です。
外部パッケージ(Stage Package)の基本
Streamlit in SnowflakeではAnacondaがメンテナンスしている独自のパッケージリポジトリを参照することができますが、以下のような手順で外部パッケージを利用することもできます。
- ステージを作成する(既にあるステージを使ってもいい)
- 作成したステージにPythonファイルをアップロードする
-
EDIT
画面のPackages
>Stage Packages
から、アップロードしたPythonファイルを所定のフォーマットで指定する - Streamlitアプリの画面をリロードする
- Streamlitプログラムから
import
する
具体的な指定ですが、例えばステージ名が mymodule
、Pythonファイル名が hogehoge.py
であれば、以下のようになります。
-
Stage Packages
で指定するPythonファイル名:@mymodule/hogehoge.py
-
import
コマンド:import hogehoge
上記の場合は hogehoge.py
に以下のようなプログラムを書きました。
def hello(name: str) -> str:
return f'Hello, {name}!'
なお、Truestar社のオザワさんの記事に記載されているように、Streamlitアプリそのものが格納されているステージにPythonファイルをアップロードする方法もあります。
外部パッケージ上級編
外部パッケージにはzipファイルを指定することができます。
zipファイルをステージにアップロードして Stage Packages
で指定すると、そのzipファイルに含まれているPythonファイルをimportすることができます。
以降、以下のような構造のzipファイルをサンプルを想定して記載します。
mycat.zip
├ mycat.py
├ hogehoge.py
├ sub/
│ ├ __init__.py
│ └ foobar.py
└ static/
└ images/
└ cat1.jpg
Pythonファイル単体の場合と同様に、以下のような手順でzipファイルを外部パッケージとして利用することができます。
- ステージを作成する(既にあるステージを使ってもいい)
- 作成したステージにzipファイルをアップロードする
-
EDIT
画面のPackages
>Stage Packages
から、アップロードしたzipファイルを所定のフォーマットで指定する - Streamlitアプリの画面をリロードする
- Streamlitプログラムから
import
する
zipファイルの直下にあるPythonファイルは何もしなくても import
できるのですが、サブディレクトリ配下にあるPythonファイルを import
するときは、中身が空でもいいので __init__.py
を含めておく必要があります。
(toru_hiyamaさんに教えていただきました。ありがとうございます!)
Pythonプログラムからは from サブディレクトリ名 import
で import
できます。
import streamlit as st
# 外部パッケージをimportする
import hogehoge
from sub import foobar
message = hogehoge.hello("Kumataro")
st.write(message)
message = foobar.hello("Polar")
st.write(message)
zipファイルに含まれる他のファイルを使いたい
Pythonファイルは上記のように import
できるのですが、それ以外のファイルはそのままだとアクセスできません。
例えば上記のサンプルではstatic/images/cat1.jpgというファイルが含まれていますが、外部パッケージがこのファイルを使う処理をしているとエラーが発生します。
リソースファイルや機械学習モデルがセットになっているモジュールを扱いたいときに困りますね。
これはzipファイルをすべて解凍しているのではなく、Snowflake Python UDFsの機能でzipファイルに含まれるPythonファイルだけを一時的に解凍していることに起因するようです。
以下のINSIGHT LAB様の記事では使っているのはSnowflake Python UDFsですが、これと同様の挙動になります。
解決策は、Stage Packagesで指定したzipファイルを/tmp配下に解凍することです。
Snowsight上で動かすPythonプログラムあるいは外部パッケージのいずれかで、例えば以下のような処理を書けばOKです。
import streamlit as st
import os
import zipfile
import glob
import random
DEFAULT_PATH: str = "/tmp/mycat"
is_deployed = False
def deploy(path=DEFAULT_PATH) -> str:
# "/tmp/appRoot"の直下にあるzipファイルを展開する
# os.getcwd()は呼び出すタイミングによって異なるディレクトリ("/home/udf")を返すことに注意
# zipファイル名は__file__から取得する
dirname = "/tmp/appRoot"
basename = os.path.basename(os.path.dirname(__file__))
archive_path = os.path.join(dirname, basename)
if archive_path.endswith(".zip"):
# 本当はロックした方がいい
with zipfile.ZipFile(archive_path) as z:
z.extractall(path)
# 本当はデプロイ済みかどうかはファイルの存在で判断する
global is_deployed
is_deployed = True
return path
# モジュールを読み込んだときにデプロイする
if not is_deployed:
deploy()
# ランダムな画像をStreamlitで表示する
def show_cat(path=DEFAULT_PATH):
lst = glob.glob(path + "/static/images/*.jpg", recursive=True)
# ランダムで選ぶ
img_path = random.choice(lst)
# 画像を表示
st.image(img_path)
上記ではモジュールを読み込んだとき( import
したとき)にzipファイルを解凍する処理を書いたので、リソースファイルを参照する関数をサッと呼び出せました。
解凍する先が/tmp配下に限定されているため、パスを解決できるものに限られますが、外部パッケージの幅が広がりそうですね!