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

文字列を文単位で区切る

2021-11-08 22:36:57 | 自然言語処理
文字列を文単位で区切る方法のメモ。

「。」、「?」、「!」を文の区切り文字として、文字列を文毎に区切ります。
import re;

str1 = '文です。文です。。。文です??文です?!文です'
sents = re.findall('(?:[^。?!]+[。?!]*|[^。?!]*[。?!]+)', str1)
print(sents)

実行結果
['文です。', '文です。。。', '文です??', '文です?!', '文です']



Elasticsearch で role、user を作成する方法

2021-11-03 23:56:29 | elasticsearch
Elasticsearch で role、user を作成する方法のメモ。

role、user を有効にするには、config/elasticsearch.yml に以下を設定してから
elasticsearch を起動します。
xpack.security.enabled: true

全インデックスに対する read 権限を持つ read-role を作成。
curl "http://localhost:9200/_security/role/read-role" \
     -u elastic:espasswd \
     -X PUT \
     -H 'Content-Type: application/json' \
     -d "
{
  \"indices\": [
  {
    \"names\": [\"*\"],
    \"privileges\": [\"read\"]
  }
  ]
}"

上記の names でインデックス名を指定すると、そのインデックスに対してのみ読み込みを許可することができます。

read-role を持つ read-user を作成。
url 'http://localhost:9200/_security/user/read-user' \
     -u elastic:espasswd \
     -X PUT \
     -H 'Content-Type: application/json' \
     --data '
{
        "enabled": true,
        "password": "es-read-user-passwd",
        "roles": ["read-role"]
}'


Node.js で axios による http リクエスト

2021-11-02 21:39:30 | Node.js
Node.js で axios による http リクエストのメモ。
axios では、レスポンスステータスが 404 などの場合、
例外をキャッチして処理する必要があります。
const axios = require('axios');

async function http_get(url, params) {
  console.log(url);

  try {
    const response = await axios.get(url, params);
    console.log('[success]');
    console.log('[status]', response.status);
    console.log('[data]', response.data.substr(0, 40));
  } catch (e) {
    const response = e.response;
    console.log('[error]');
    console.log('[status]', response.status);
    console.log('[data]', response.data.substr(0, 40));
  }
}

(async () => {
  const params = {
    MT: '検索',
    IE: 'UTF-8',
    OE: 'UTF-8',
  };

  // 200 OK                                                                     
  await http_get('https://search.goo.ne.jp/web.jsp', params);

  // 404 not found                                                              
  await http_get('https://search.goo.ne.jp/dummy.jsp', params);
})();

実行結果
https://search.goo.ne.jp/web.jsp
[success]
[status] 200
[data] <!DOCTYPE HTML> <html lang="ja" class="seatchTopHtml"> <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#"> <meta charset="UTF-8"/> <meta name="referrer" content="origin-when-cross-origin"> 
https://search.goo.ne.jp/dummy.jsp
[error]
[status] 404
[data] <!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex,follow">
<title>404 - gooウェブ検索</title>
<meta name="viewport" content="width=device-width,initial-sca