✒️

Streamlitを使ったテキストアノテーションツール

2023/01/13に公開

Streamlitを使ったテキストアノテーションツールを作成したので手順をまとめます.今回はテキストに5W1Hのアノテーションを付与するツールを作成します.

先日,仕事中に簡易で良いからテキストのアノテーションをして機械学習モデルに学習させてみたい,という場面に遭遇しました.そこで,最低限の機能だけ持ったアノテーションツールをpythonで作成しました.

Streamlitとは

PythonでWebアプリケーションを作成するためのフレームワークです.コードも実装結果も非常にシンプルで理解しやすいです.
https://streamlit.io/

動作環境

  • Ubuntu20.04
  • Python3.8.10

環境構築

pip3 install streamlit

今回は自分で作成したテキストファイルtoy.txtに対して5W1Hをアノテーションしていく想定で実装します.

toy.txt
今月末の学会で、自分が口頭発表の際に使うためのデモ動画を、最優先で作る。
自分の机の上にある、講義で使う参考書をなるはやで会場まで持ってきてほしい。
自社の代表が出席する取締役会が明日正午から開始される。

実装

TextAnnotatorクラスを実装します.

全体実装 toy.py 43行
import streamlit as st


class TextAnnotator:
    def __init__(self):
        self.path_dataset = f"./dataset/toy.txt"
        self.path_out = f"./dataset/annotated_toy.tsv"
        self.lines = self.load_text()
        return

    def load_text(self):
        with open(self.path_dataset, mode="r", encoding="utf-8") as f:
            return f.readlines()

    def run(self):
        st.title("5W1Hアノテーションツール", anchor=None)

        annots = []
        for idx, line in enumerate(self.lines):
            line = line.replace("\n", "")
            st.header(f"{idx+1}番目\n")
            st.write(line)

            id    = idx+1
            when  = "when_"  + st.text_input('when(いつ)', '-'  , key=id)
            where = "where_" + st.text_input('where(どこで)', '-' ,key=id*300+1)
            who   = "who_" + st.text_input('who(だれが)', '-'   , key=id*300+2)
            why   = "why_"  + st.text_input('why(なんで)', '-'   , key=id*300+3)
            what  = "what_" + st.text_input('what(なにを)', '-'  , key=id*300+4)
            how   = "how_"  + st.text_input('how (どうした)', '-', key=id*300+5)

            annot = f"{line}\t{when},{where},{who},{why},{what},{how}\n"
            annots.append(annot)

        if st.button('完了', key=id*300+7):
            with open(self.path_out, mode="w", encoding="utf-8") as o:
                o.writelines(annots)
        return


if __name__ == "__main__":
    ta = TextAnnotator()
    ta.run()
  • import
    streamlitを読み込みます.
import streamlit
  • __ init __
    アノテーションするテキストが書かれた入力ファイルと出力先ファイルを指定します.その後,入力テキストを読み込みます.
class TextAnnotator:
    def __init__(self):
        self.path_dataset = f"./dataset/toy.txt"
        self.path_out = f"./dataset/annotated_toy.tsv"
        self.lines = self.load_text()
        return
  • load_text
    1行に1文アノテーションしたい文章が書かれていると仮定して,1行毎に読み込みます.
    def load_text(self):
        with open(self.path_dataset, mode="r", encoding="utf-8") as f:
            return f.readlines()
  • run
    streamlitライブラリを使ってGUIを作成していきます.
    def run(self):
        st.title("5W1Hアノテーションツール", anchor=None)

        annots = []
        for idx, line in enumerate(self.lines):
            line = line.replace("\n", "")
            st.header(f"{idx+1}番目\n")
            st.write(line)

            id    = idx+1
            when  = "when_"  + st.text_input('when(いつ)', '-'  , key=id)
            where = "where_" + st.text_input('where(どこで)', '-' ,key=id*300+1)
            who   = "who_" + st.text_input('who(だれが)', '-'   , key=id*300+2)
            why   = "why_"  + st.text_input('why(なんで)', '-'   , key=id*300+3)
            what  = "what_" + st.text_input('what(なにを)', '-'  , key=id*300+4)
            how   = "how_"  + st.text_input('how (どうした)', '-', key=id*300+5)

            annot = f"{line}\t{when},{where},{who},{why},{what},{how}\n"
            annots.append(annot)

        if st.button('完了', key=id*300+7):
            with open(self.path_out, mode="w", encoding="utf-8") as o:
                o.writelines(annots)
        return

主にstreamlitの関数について説明します.

st.title("5W1Hアノテーションツール", anchor=None)
st.write(line)

タイトル(見出し)とその下に書く説明文を作成します.

when  = "when_"  + st.text_input('when(いつ)', '-'  , key=id)

テキストボックスを作成し,入力されたデータを変数whenに格納します.'-'はデフォルトでテキストボックスに入力しておく文字列です.アノテーション結果が空欄だったときに何も入っていないと後処理で面倒になるので入力テキストに出現しないであろう文字列を経験的に入れておきます.keyはテキストボックスに対して固有の値である必要があるため,こちらも他のテキストボックスと被らないような整数を割り当てます.

他の5W1Hについても上記の全体実装に記載があるように実装するため説明は割愛します.

annot = f"{line}\t{when},{where},{who},{why},{what},{how}\n"
annots.append(annot)

入力テキストに対してアノテーションした結果を整形して変数annotに格納し変数annotsに追加していきます.

        if st.button('完了', key=id*300+7):
            with open(self.path_out, mode="w", encoding="utf-8") as o:
                o.writelines(annots)
        return

st.button()でボタンが作成されます.こちらも固有のkeyが必要なため適当な整数を割り当てます.このボタンが押されたら,アノテーション結果を記録していた変数annotsをファイル出力します.

コードを作成した後はターミナルに以下のコマンドを実行するとブラウザにアノテーションツールが起動します.

streamlit run toy.py

結果

実行した結果がこちらです.スクロールすると2番目,3番目のテキストも表示されていきます.

5W1Hテキストアノテーションツールの起動画面
後は入力テキストを手動でコピー&ペーストしていきます.実際の業務では300文程を2.5時間かけてアノテーションしました.もっと賢くできたのかも知れませんが知恵よりも根気が勝ちました.地道な作業ですが1文1文眺めながらテキストをアノテーションしていくのでデータに対する解像度が上がって良い経験になりました.全てのデータに目を通すので予想外のデータが無くなるのが大きいと感じました.

アノテーションするとこのようになります.

1文目のアノテーション

2文目のアノテーション

3文目のアノテーション
全て入力した後に3枚目の左下に書かれている完了ボタンを押すとこちらのアノテーション済みデータセットが出力されます.

5W1Hでアノテーションしたデータセット
あとは特定の5W1Hの項目だけ抜き出して教師データに使ったり,5W1H文の生成に使ったりなど所望の用途に利用します.

まとめ

Pythonでwebアプリケーションを作成するstreamlitを使って非常に簡易なアノテーションツールを作成しました.シンプルなタスクだったゆえに既存のテキストアノテーションツールなどを深く調べずに作成しました.5W1Hなら自動で抽出してくれるソフトなどあるのかも知れませんが,ゼロから自分でも作れるということが分かったので良しとします.できることがまた一つ増えました.

Discussion