dak ブログ

python、rubyなどのプログラミング、MySQL、サーバーの設定などの備忘録。レゴの写真も。

python からの kuromojji の実行速度を改善

2021-11-13 20:11:33 | 自然言語処理
以前「python から kuromoji を実行」という記事を書きました。
この記事の方法で kuromoji である程度の量の文書を解析すると、実行時間がかなり遅いことに気づきました。

以前の方法では、以下のように token 毎に getSurfaceForm() などの kuromoji のメソッドを呼び出していました。
gw = JavaGateway()
tokenizer = gw.jvm.org.atilika.kuromoji.Tokenizer.builder().build()
jtkns = tokenizer.tokenize('日本語の文字列を解析します。')
tkns = []
for i in range(len(jtkns)):
    jtkn = jtkns[i]
    tkn = {
        'form': jtkn.getSurfaceForm(),
        'base': jtkn.getBaseForm(),
        'pos': jtkn.getBaseForm(),
    }
    tkns.append(tkn)    

この方法だと、java サーバとの通信が 1 token につき形態素解析結果の項目分の通信が発生しているのではないかと考えました。
そこで、java サーバは形態素解析結果を json 文字列を返却するようにすることで実行速度を改善してみました。

java サーバ側では以下のように json 文字列を返すメソッドを用意します。
json ライブラリとして jackson を使用しています。
import py4j.GatewayServer;
import java.util.List;
import org.atilika.kuromoji.Tokenizer;
import org.atilika.kuromoji.Token;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

public class KuromojiJsonGateway {
    public String tokenize(Tokenizer tknzr, String str) {
        List<Token> tkns = tknzr.tokenize(str);

        ObjectMapper mapper = new ObjectMapper();
        ArrayNode arr = mapper.createArrayNode();
        for (int i = 0; i < tkns.size(); i++) {
            Token tkn = (Token)(tkns.get(i));
            ObjectNode obj = mapper.createObjectNode();
            obj.put("form", tkn.getSurfaceForm());
            obj.put("base", tkn.getBaseForm());
            obj.put("read", tkn.getReading());
            obj.put("pos", tkn.getAllFeatures());
            arr.add(obj);
        }

        try {
            String json = mapper.writeValueAsString(arr);
            return json;
        }
        catch (Exception e) {
            return "";
        }
    }

    public static void main(String[] args) {
        KuromojiJsonGateway app = new KuromojiJsonGateway();
        GatewayServer server = new GatewayServer(app);

        server.start();
    }
}

上記のプログラムを以下のようにコンパイルして実行します。
javac -classpath py4j0.10.9.2.jar:kuromoji-0.7.7.jar:jackson-core-2.13.0.jar:jackson-databind-2.13.0.jar:jackson-annotations-2.13.0.jar KuromojiJsonGateway.java
java -classpath py4j0.10.9.2.jar:kuromoji-0.7.7.jar:jackson-core-2.13.0.jar:jackson-databind-2.13.0.jar:jackson-annotations-2.13.0.jar KuromojiJsonGateway

python では、前回と同様 java のメソッドを呼び出す方法と、上記の json を返すメソッドを使う方法とで
100文ずつ解析を行い、実行速度を比較します。
import sys
import time
import json
from py4j.java_gateway import JavaGateway

def convert(jtkns):
    tkns = []
    for i in range(len(jtkns)):
	jtkn = jtkns[i]
	tkn = {
            'form': jtkn.getSurfaceForm(),
            'base': jtkn.getBaseForm(),
            'read': jtkn.getReading(),
            'pos': jtkn.getAllFeatures(),
	}
    return tkns

def tokenize1(gw, str, num):
    tknzr = gw.jvm.org.atilika.kuromoji.Tokenizer.builder().build()
    from_time = time.time()

    for i in range(num):
	jtkns = tknzr.tokenize(str)
        tkns = convert(jtkns)
    to_time = time.time()
    print("tokenize1(): %s" % (to_time - from_time))

def tokenize2(gw, str, num):
    app = gw.entry_point
    tknzr = gw.jvm.org.atilika.kuromoji.Tokenizer.builder().build()
    from_time = time.time()

    for i in range(num):
        tkns_str = app.tokenize(tknzr, str)
        tkns = json.loads(tkns_str)
    to_time = time.time()
    print("tokenize2(): %s" % (to_time - from_time))

def main():
    gw = JavaGateway()
    num = 100
    str = '形態素解析を実行します'
    tokenize1(gw, str, num)
    tokenize2(gw, str, num)
    return 0

if __name__ == '__main__':
    res = main()
    exit(res)

実行結果は以下の通りです。
tokenize1(): 0.7767603397369385
tokenize2(): 0.13006877899169922

java のメソッド呼び出し回数を削減した分がそのまま実行速度に反映されているようです。