LoginSignup
2
2

More than 5 years have passed since last update.

Play Framework と Stanfordの固有表現抽出器を用いた NER API サーバー

Last updated at Posted at 2016-08-02

概要

Play FrameworkStanfordの固有表現抽出器を使って、固有表現抽出してくれる JSON の API サーバーを作りました。

コードは以下に公開しています。
https://github.com/sosuke-k/ner-play-server

動機

Python で自然言語処理をしていて、英語の固有表現抽出をする際にStanfordの固有表現抽出器を使う機会がありました。

nltkのラッパー があるのですが、文毎に java が実行されていて、おそらくその度に crf のモデルがロードがされているっぽくて、とても遅かったです。
実際に、ウィキペディアの長めの記事のものに 10分 近くかかっていました。

そこで、 java のウェブサーバーで固有表現抽出し、結果を返すことにしました。

バージョン

You’re using Play 2.5.4

ルーティング

/nerGET で モデルを初期化します。( classifier をパラメータで受け取りモデルを変更できます。)
/nerPOST で固有表現抽出した結果を返してくれます。

routes
GET     /ner                        controllers.NERController.index(classifier : String ?= "english.all.3class.distsim.crf.ser.gz")
POST    /ner                        controllers.NERController.tag

実装

依存ライブラリ

build.sbtlibraryDependencies で指定しています。

build.sbt
libraryDependencies ++= Seq(
  ...
  "edu.stanford.nlp" % "stanford-corenlp" % "3.6.0",
  "edu.stanford.nlp" % "stanford-parser" % "3.6.0"
)

モデルのロード (固有表現抽出器の初期化)

public フォルダ以下に置くことで、URL指定で読み込んでいます。
gzip されたものを読み込むのに、単純な InputStream でエラーが出てしまって、つまづきました。

$ wget http://nlp.stanford.edu/software/stanford-ner-2015-12-09.zip
$ unzip stanford-ner-2015-12-09.zip
$ cp -r ./stanford-ner-2015-12-09/classifiers/ ./public/classifiers/
routes
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)
NERController.java
public class NERController extends Controller {
  ...
  private boolean initialize(String classifier) {
    try {      
      URL url = new URL("http://" + this.request().host() + "/assets/classifiers/" + classifier);
      HttpURLConnection connect = (HttpURLConnection) url.openConnection();
      connect.setRequestMethod("GET");
      connect.setRequestProperty("Accept-Encoding", "gzip");
      GZIPInputStream gzin = new GZIPInputStream(connect.getInputStream());

      this.classifier = CRFClassifier.getClassifier(gzin);

ポストのデータをJSONで受け取る

ここ調べ不足なんですが、強引に文字列に直した後、 ObjectMappertext の値を取得しています。

NERController.java
  public Result tag() {
    ...
    JsonNode json = request().body().asJson();
    // String text = json.findPath("text").getTextValue();
    String jsonstring = Json.stringify(json);

    ObjectMapper mapper = new ObjectMapper();
    Doc doc = null;
    try {
      doc  = mapper.readValue(jsonstring, Doc.class);
    } catch ( IOException e) {
      System.out.println( "IOException: could not mapper.readValue" );
    }

    return ok(Json.toJson(this.tag_sent(doc.text)));
  }
Doc.java
public class Doc {
  public String text;
}

固有表現抽出

アンサーラベルを取得するのに、適当なメソッドが見つからず、ソースコードを見て強引に取得しています。
CoreLabel (Stanford JavaNLP API)

NERController.java
  private Tokens tag_sent(String text) {
    Tokens tokens = new Tokens();
    for (List<CoreLabel> lcl : this.classifier.classify(text)) {
      for (CoreLabel cl : lcl) {
        tokens.add(new Token(cl.originalText(), cl.get(AnswerAnnotation.class)));
      }
    }
    return tokens;
  }

classify するコード自体は stanford-ner-2015-12-09.zipNERDemo.java をコピペしました。

JSONを返す

NERController.java
...
  private Tokens tag_sent(String text) {
    tokens tokens = new Tokens();
    ...
    return tokens;
  }
  ...
  public Result tag() {
    ...
    return ok(Json.toJson(this.tag_sent(doc.text)));
  }
...
Tokens.java
public class Tokens {
  public List<Token> tokens;

  public Tokens() {
    this.tokens = new ArrayList<Token>();
  }

  public void add(Token token) {
    this.tokens.add(token);
  }
}
Token.java
public class Token {
  public String text;
  public String label;

  public Token(String text, String label) {
    this.text = text;
    this.label = label;
  }
}

こんな感じ

初期化して(ここはすこじ時間がかかります)

$ curl http://localhost:9000/ner | jq
{
  "status": "available",
  "current": "english.all.3class.distsim.crf.ser.gz",
  "message": ""
}

文をポスト

$ curl --header "Content-type: application/json" --request POST --data '{"text":"I live in Japan."}' http://localhost:9000/ner | jq
{
  "tokens": [
    {
      "text": "I",
      "label": "O"
    },
    {
      "text": "live",
      "label": "O"
    },
    {
      "text": "in",
      "label": "O"
    },
    {
      "text": "Japan",
      "label": "LOCATION"
    },
    {
      "text": ".",
      "label": "O"
    }
  ]
}

jq コマンドは linux系とかだと使いかた違うかもしれないです。

結果

python からこのサーバーを叩くようにした結果、 10分 近くかかっていた記事の固有表現抽出が、 5秒 で終わりました。
元の python のコードが良くなかったのかな?と思っていますが、とりあえず、よかったです。

※おまけ
テストなどしてないですが、ビルドしたものをあげておきました。。汗
https://github.com/sosuke-k/ner-play-server/releases

$ wget https://github.com/sosuke-k/ner-play-server/releases/download/v_1.0.0/ner-play-server-1.0-SNAPSHOT.zip
$ unzip ner-play-server-1.0-SNAPSHOT.zip
$ chmod +x ner-play-server-1.0-SNAPSHOT/bin/ner-play-server
$ ner-play-server-1.0-SNAPSHOT/bin/ner-play-server
...
2
2
0

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
2