2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PythonからVaporettoを呼び出して使ってみた話

Last updated at Posted at 2022-03-07

2022/07/05 追記

Vaporetto の開発者の方がPythonラッパーをリリースしてくださりました。

概要

PythonからRustで書かれたトークナイザであるVaporettoを呼び出して使用するクラスを作成しました。
バインディングで動作させている訳ではなく、今回はsubprocessを用いてビルドした実行ファイルを呼び出す形で使用しました。(どっちかっていうと、ラッパー?)
RustはPythonから比較的移行しやすい言語だと思うので(個人の感想です)、バインディング用意しなくてもsubprocessで十分なんじゃないかなぁと思って書いてみました。

環境

windows10
Python 3.8.10

今回作成したコード

import subprocess
from typing import List

class Vaporetto:
    def __init__(
        self, 
        exe_path:str,               # 実行ファイルへのパス
        model_path:str,             # モデルへのパス
        option:List[str]=[],        # 追加のオプション(要らない?)
        display=True                # Vaporettoから出力される標準エラー出力の表示の有無. 最後に出る速度とかの辺
        ) -> None:

        options = ["--model {}".format(model_path)]     # コマンド実行時のオプションを指定
        option.extend(option)

        cmd = exe_path + " " + " ".join(options)        # 実行するコマンドを指定

        # 子プロセスを起動
        if display:
            self.proc = subprocess.Popen(cmd, bufsize=5242880 ,  stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        else:
            self.proc = subprocess.Popen(cmd, bufsize=5242880 ,  stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
    
    # 形態素解析
    def parse(self, text:str):
        text = text.split("\n")
        res = []

        for row in text:
            if row == "":
                continue    
            self.proc.stdin.write((row + "\n").encode())        # バッファに1行分のデータをutf-8でエンコードして書き込む
            self.proc.stdin.flush()                             # バッファの内容を子プロセスに流す

            x = self.proc.stdout.readline().decode().strip()    # 子プロセスから標準出力に出力された内容を読み取る
            res.append(x)

        return res

    def exit(self):             # 子プロセスの終了
        self.proc.stdin.close()

    def __del__(self):          # デストラクタ
        self.proc.kill()

バッファの値は色々試行錯誤してた際に決めたので適当です。最終的に1行ごとに入力する形式になったのでラインバッファでも良い気もします。

使い方

実行ファイルのパスと使用するモデルへのパスを指定してオブジェクトを作成して、parseメソッドでトークナイズできます。
そのまま終了した場合や、exitメソッドで終了した場合は最後に表示される速度等の情報が表示されますが、デストラクタで終了した場合は、子プロセスが強制終了されるためそれらは表示されません。
空行は入力しても無視される仕様になっています。困る場合はparseメソッドを良い感じに書き換えてください。

MeCabと比較して速いのか

Vaporettoの起動に若干の時間がかかる為, 起動に十分な時間をsleepして時間を計測しました。
青空文庫から100個分のテキストを結合したデータを用いました。文字数は2230627文字です。
時間計測はtime.perf_counter()で行いました。

そのまま入力した場合

MeCab:1.571 sec
Vaporetto:1.481 sec
上記の通り、Vaporettoの方が若干早いという結果でした。

行ごとに分割して入力した場合

MeCab:1.239 sec
Vaporetto:1.490 sec
MeCabの方が速いという結果でした。

まとめ

データをエンコードしてパイプでやり取りする必要がある分サブプロセスは不利なのかなぁという印象でした。
Vaporettoの方は行ごとに分割する処理をPythonが行ってから引き渡しているので、そこもRustで書けばもう少し早くなるのかな?とも思いましたが、そこまでするなら普通にRustで書いたほうが楽ですよね。

バインディングなら、もう少し早くなるのかなぁ。(書く予定はないです)

2
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?