Sansan Builders Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

【Techの道も一歩から】第38回「Streamlit で固有表現抽出の結果を表示する」

f:id:kanjirz50:20190104142720j:plain

こんにちは。 DSOC R&D グループの高橋寛治です。

最近、部内で Streamlit による可視化を見かけるようになってきました。 Streamlit は、インタラクティブなデータの可視化に特化した Web アプリケーションを少ないコードで素早く提供することができる Python ライブラリです。

私自身は Web アプリケーションとしてデモ化する際に手慣れた Flask で書くことが多いです。 慣れているとはいえ時間を要するため、同じ結果を爆速で実現できるなら使わないわけにはいきません。 作業効率改善のために、固有表現抽出を題材にして使ってみることにしました。

今回 Streamlit で実現したいこと

テキストエリアに入力されたテキストを解析し、解析結果を表に表示します。

入力エリアを準備する

まずは、Streamlit をインストールし、ファイルを準備します。

$ pip install streamlit
$ touch ner_streamlit.py
$ streamlit run ner_streamlit.py

  You can now view your Streamlit app in your browser.

  Network URL: http://xxx.xxx.xxx.xxx:8501
  External URL: http://yyy.yyy.yyy.yyy:8501

環境によりますが、Network URL をブラウザで開けば、ほぼ真っ白なページが閲覧できるかと思います。

Streamlit での可視化アプリケーションを開発する際は、Python ファイルを更新しブラウザを更新することで、アプリケーションに反映されます。 かなりテンポ良く開発を進めることができます。

まずは、次のコードでテキストエリアを作成します。

import streamlit as st


def main():
    st.title("固有表現抽出")
    text = st.text_area('テキストを入力し、Ctrl+Enterで解析結果を表示します。', max_chars=510)


if __name__ == "__main__":
    main()

コードを保存後、ブラウザで再読み込みします。

f:id:kanjirz50:20210407191152p:plain
見た目の作成

たった数行書くだけで、上記の画面が作成できました。 これだけで、結果が返ってくる気がします(笑)

簡単に説明すると、Streamlit 上で表示したい要素や入力として受け付けたい要素は、st.xxxx() のように与えます。 メソッド名を読むと何をしているかおおよそわかるかと思います。

ここでは、テキストエリアに入力された文字列を固有表現抽出モデルに入力予定です。 モデルの制約である最大トークン数を超えないように、文字数として max_chars で大雑把に制限しています。

実際に入力を処理し、描画する

次に、固有表現抽出モデルを読み込み、推論した結果を表示させます。 コード例中の固有表現抽出(onener)は、Transformers ライブラリの BERT で学習や出力したものを使っています。 各自で適宜、CaboCha や Spacy など置き換えてください。 ここでは、1トークンが辞書型で表現され、トークンのリストが返ってくる想定で、コードを書いています。

import pandas as pd
import streamlit as st

# 固有表現抽出モジュールは各自で適宜置き換えてください
import onener_model_wiki40b
from onener.transformer.ner import BertNERTagger


@st.cache(allow_output_mutation=True)
def load_model():
    tagger = BertNERTagger(onener_model_wiki40b.model_path)
    return tagger


def main():
    st.title("固有表現抽出")
    text = st.text_area('テキストを入力し、Ctrl+Enterで解析結果を表示します。', max_chars=510)

    tagger = load_model()
    out = tagger.predict(text)[0]

    # out の構造
    # out = [{"word": "Sans", "score": 0.9706, "entity": "B-組織名", "index": 1}, {"word": "an", ....

    # 解析器の結果を DataFrame 化する。解析器によっては出力の成形が必要。
    df = pd.DataFrame(out)
    st.table(df)


if __name__ == "__main__":
    main()

ここで、キャッシュの説明です。 Streamlit では、データをブラウザ上から送信する度に、裏側でコードが実行されます。 毎回モデルを読み込んでいると時間がかかりますので、モデルの初期化にはキャッシュを導入します。 allow_output_mutation=True は戻り値の内容の変化のチェックをしなくなります。

ここまで書けたら、ブラウザを再読み込みします。 下記のように、テキストエリアにテキストを入力し、Ctrl+Enter を押すと解析結果が表示されます。

f:id:kanjirz50:20210407193539p:plain
固有表現抽出結果を表示

固有表現に色を付ける

もう一工夫として、固有表現だった場合に表に色を付けます。

pandas の Style 機能を用います。 ノートブックや Streamlit 上で可視化する際に、CSS による装飾が可能です。

今回は、apply メソッドを使い、行単位で装飾を行います。 具体的には、Entity が O タグ以外かどうかで、背景色を設定します。

なお、apply では行内の要素数分だけスタイルを返却する必要があります。

...
def highlight_ner_row(x):
    color = ""
    if x.entity != "O":
        color = "background-color: skyblue;"
    return [color for _ in x]


def main():
    ...
    df = df.style.apply(highlight_ner_row, axis=1)
    st.table(df)
    ...

ブラウザを再読み込みし、テキストを入力して解析結果を可視化します。 固有表現箇所の背景色が変更されました。

f:id:kanjirz50:20210407193757p:plain
固有表現は行の色が変更される

Streamlit があるときとないとき

Streamlit があると、データを美しく可視化ができ、対話的なインターフェイスを簡単にかつ爆速で用意できます。 これは、デモ作成時に非常に役立つと感じます。

Streamlit がないと、Flask を初めとした Webアプリケーションフレームワークでサーバーサイドを準備し、CSSを決め、HTMLを書き、とやることがたくさんあり、単にデモを見せたいというだけでも時間と労力がかかります。

研究開発結果を素早く提示し、直感的に理解・意見をもらうというサイクルが、Streamlitを使うことで早く回せそうなので積極的に使っていきたいと思います。

執筆者プロフィール

高橋寛治 Sansan株式会社 DSOC (Data Strategy & Operation Center) 研究開発部 研究員

阿南工業高等専門学校卒業後に、長岡技術科学大学に編入学。同大学大学院電気電子情報工学専攻修了。在学中は、自然言語処理の研究に取り組み、解析ツールの開発や機械翻訳に関連する研究を行う。大学院を卒業後、2017年にSansan株式会社に入社。キーワード抽出など自然言語処理を生かした研究開発に取り組む。

▼本連載のほかの記事はこちら

buildersbox.corp-sansan.com

© Sansan, Inc.