astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

CoreNLP を使ってみる(1)

山縣です。

 

今回は 自然言語処理ツールである Stanford CoreNLPについて書きたいと思います。

Stanford CoreNLPとは

Stanford CoreNLP は自然言語処理ツールのひとつです。スタンフォード大学がオープンソース(GPL3) で公開しています。 英語、中国語など主要な言語をサポートしています。が残念ながら日本語は対応していません。

日本語以外の自然言語処理ツールについては先日、白木が Polyglot について記事を書いていましたが、こちらは Python 用です。私は Scala で書くことが多いので、Java/Scala で使えるものとして CoreNLP を使っています。

CoreNLP を使うには CLI として呼び出す方法、サーバとして起動して呼び出す方法、プログラムからライブラリとして使用する方法があります。

今回は CLI とサーバとして使う方法について解説したいと思います。

CLI として使ってみる

インストール

Windows10 の bash on windows 上にインストールしてみました。

CoreNLP は Java で作られていますのでJavaの実行環境が必要です。Java のバージョンは Java8 になります。

CoreNLPのインストールは、まずDownload | Stanford CoreNLP よりファイルをダウンロードします。

この記事を書いた時点でCoreNLPのバージョンは 3.8.0 です。

"Download CoreNLP 3.8.0" ボタンを押して、本体をダウンロードします。 ダウンロードした、stanford-corenlp-full-YYYY-MM-DD.zip を展開します。

$ unzip stanford-corenlp-full-2017-06-09.zip
$ cd stanford-corenlp-full-2017-06-09

次に各言語固有の モデルデータ(JARファイル) をダウンロードします。今回は英語を使います。本体と同じページに各言語用のモデルデータがあるので、そちらから"English" を選び英語用のモデルデータ(stanford-english-corenlp-2017-06-09-models.jar) をダウンロードして本体のディレクトリに置きます。

なお英語で使用する場合は、実際には追加のモデルデータを取得しなくても大方の機能が動くようです。本体に含まれる stanford-corenlp-3.8.0-models.jar に基本的なモデルデータが含まれているようです。

インタラクティブに使う

とりあえずこれでセットアップが完了したので、試しに実行してみます。 付属している corenlp.sh を使ってCoreNLP を起動します。

$ ./corenlp.sh -annotators tokenize,ssplit,pos,lemma,ner
java -mx5g -cp "./*" edu.stanford.nlp.pipeline.StanfordCoreNLP -annotators tokenize,ssplit,pos,lemma,ner
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator tokenize
...

Entering interactive shell. Type q RETURN or EOF to quit.
NLP>

入力ファイルを指定しなかった場合、インタラクティブシェルが立ち上がるので、ここに処理したい文章を入れると結果が返ります。

NLP> How can I get to Tokyo station? I need to get on the bullet train at that station.

Sentence #1 (8 tokens):
How can I get to Tokyo station?
[Text=How CharacterOffsetBegin=0 CharacterOffsetEnd=3 PartOfSpeech=WRB Lemma=how NamedEntityTag=O]
[Text=can CharacterOffsetBegin=4 CharacterOffsetEnd=7 PartOfSpeech=MD Lemma=can NamedEntityTag=O]
[Text=I CharacterOffsetBegin=8 CharacterOffsetEnd=9 PartOfSpeech=PRP Lemma=I NamedEntityTag=O]
[Text=get CharacterOffsetBegin=10 CharacterOffsetEnd=13 PartOfSpeech=VB Lemma=get NamedEntityTag=O]
[Text=to CharacterOffsetBegin=14 CharacterOffsetEnd=16 PartOfSpeech=TO Lemma=to NamedEntityTag=O]
[Text=Tokyo CharacterOffsetBegin=17 CharacterOffsetEnd=22 PartOfSpeech=NNP Lemma=Tokyo NamedEntityTag=LOCATION]
[Text=station CharacterOffsetBegin=23 CharacterOffsetEnd=30 PartOfSpeech=NN Lemma=station NamedEntityTag=O]
[Text=? CharacterOffsetBegin=30 CharacterOffsetEnd=31 PartOfSpeech=. Lemma=? NamedEntityTag=O]
Sentence #2 (12 tokens):
I need to get on the bullet train at that station.
[Text=I CharacterOffsetBegin=32 CharacterOffsetEnd=33 PartOfSpeech=PRP Lemma=I NamedEntityTag=O]
[Text=need CharacterOffsetBegin=34 CharacterOffsetEnd=38 PartOfSpeech=VBP Lemma=need NamedEntityTag=O]
[Text=to CharacterOffsetBegin=39 CharacterOffsetEnd=41 PartOfSpeech=TO Lemma=to NamedEntityTag=O]
[Text=get CharacterOffsetBegin=42 CharacterOffsetEnd=45 PartOfSpeech=VB Lemma=get NamedEntityTag=O]
[Text=on CharacterOffsetBegin=46 CharacterOffsetEnd=48 PartOfSpeech=IN Lemma=on NamedEntityTag=O]
[Text=the CharacterOffsetBegin=49 CharacterOffsetEnd=52 PartOfSpeech=DT Lemma=the NamedEntityTag=O]
[Text=bullet CharacterOffsetBegin=53 CharacterOffsetEnd=59 PartOfSpeech=NN Lemma=bullet NamedEntityTag=O]
[Text=train CharacterOffsetBegin=60 CharacterOffsetEnd=65 PartOfSpeech=NN Lemma=train NamedEntityTag=O]
[Text=at CharacterOffsetBegin=66 CharacterOffsetEnd=68 PartOfSpeech=IN Lemma=at NamedEntityTag=O]
[Text=that CharacterOffsetBegin=69 CharacterOffsetEnd=73 PartOfSpeech=DT Lemma=that NamedEntityTag=O]
[Text=station CharacterOffsetBegin=74 CharacterOffsetEnd=81 PartOfSpeech=NN Lemma=station NamedEntityTag=O]
[Text=. CharacterOffsetBegin=81 CharacterOffsetEnd=82 PartOfSpeech=. Lemma=. NamedEntityTag=O]
NLP>

入力したテキストを二つのSentenceに分割したうえで、各トークンについての解析結果を表示しています。

上記の出力形式はデフォルトの 'text' というhuman readable なフォーマットになっています。

表示の意味はText がそのトークン、CharacterOffsetBegin/End がテキストの中での位置を、 PartOfSpeech が品詞を、Lemmaがレンマを NamedEntityTag が固有表現を表しています。

アノテータ

CoreNLPに処理させる機能(アノテータ)は立ち上げ時に -annotators オプションでカンマ区切りで指定しています。今回は "tokenize,ssplit,pos,lemma,ner" を指定しています。

tokenize はテキストをトークン(単語)に分ける処理を, ssplit はトークン群を文に分割する処理を、pos(part of speech) は品詞の判別、lemmaはレンマ化, ner(named entity recognition) は固有表現抽出を行います。

使用できるアノテータの一覧は Annotators | Stanford CoreNLP にあります。

またアノテータには依存関係があり、依存しているものを先に指定する必要があります。たとえば ssplit は tokenize に依存しているので tokenizeを先に(左に)指定する必要があります。また lemma は tokenize,ssplit,pos に依存しています。各アノテータの依存関係は Annotator dependencies | Stanford CoreNLP に記載されています。

posで出力されるタグ記号の一覧はこちらのタグ一覧が参考になるようです。

ner では、名称(PERSON, LOCATION, ORGANIZATION, MISC)、数字(MONEY, NUMBER, ORDINAL, PERCENT)、時間(DATE, TIME, DURATION, SET)を認識することができます。

Ieyasu Tokugawa was the founder of Edo shogunate. He lived to be 73 years old. He died on January 31,1543.

上記のテキストのNERを見ると以下のような結果になりました。 (見やすいように必要な情報だけに加工してあります)

Ieyasu PERSON
Tokugawa  PERSON
...
Edo O
shogunate O
...
73 DURATION
years DURATION
old DURATION
...
January DATE
31,1543 DATE

上記のように "Ieyasu Tokugawa" をPERSON, "73 years old" をDURATION, "January 31,1543" を DATE と認識できています。一方 "Edo shogunate" は認識できませんでした。

入力ファイルを指定する

先ほどまではインタラクティブにテキストを入力していましたが、 入力ファイルを指定することでバッチ的に処理をすることが可能です。

$ cat input.txt
Stanford University is located in California. It is a great university, founded in 1891.

$ ./corenlp.sh -annotators tokenize,ssplit,pos,lemma,ner -file ./input.txt

-file で入力ファイルを指定します。出力形式を指定しない場合 xml がデフォルトになります。

$ cat input.txt.xml
...
<root>
  <document>
    <sentences>
      <sentence id="1">
        <tokens>
          <token id="1">
            <word>Stanford</word>
            <lemma>Stanford</lemma>
            <CharacterOffsetBegin>0</CharacterOffsetBegin>
            <CharacterOffsetEnd>8</CharacterOffsetEnd>
            <POS>NNP</POS>
            <NER>ORGANIZATION</NER>
          </token>
          <token id="2">

出力ファイル名は <入力ファイル名>.xml になります。

-filelist オプションでファイルリストを指定することで複数のファイルを一括して処理することもできます。 CoreNLP CLI の起動は Java であることやモデルデータのロードに時間がかかるので多数のフィルを処理するときは -filelist で一括して処理したほうが効率的だと思います。

出力フォーマットを指定する

デフォルトの出力フォーマットを変更するには -outputFormat を指定します。 指定できるフォーマットは text, json, xml, conll, conllu, serialized です。指定するフォーマットによって出力ファイルの拡張子が変わります。 詳細は Output options で確認してください。

プロパティファイル

今まで corenlp.sh 実行時に引数でパラメータを指定していましたが、プロパティファイルを用意することでパラメータを指定することが出来ます。

$ cat my.properties
annotators = tokenize,ssplit,pos,lemma,ner,parse,mention,coref
outputExtension = .output
outputFormat = text
file = test.txt

$ cat test.txt
I bought an apple at the supermaket.
I am going to eat it later.

上記のようなプロパティファイルと入力ファイルを用意して実行します。

$ ./corenlp.sh -props ./my.properties
java -mx5g -cp "./*" edu.stanford.nlp.pipeline.StanfordCoreNLP -props ./my.properties
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator tokenize
...

$ cat test.txt.output
Sentence #1 (8 tokens):
I bought an apple at the supermaket.
[Text=I CharacterOffsetBegin=0 CharacterOffsetEnd=1 PartOfSpeech=PRP Lemma=I NamedEntityTag=O]
...
Coreference set:
        (2,6,[6,7]) -> (1,4,[3,5]), that is: "it" -> "an apple"
Coreference set:
        (2,1,[1,2]) -> (1,1,[1,2]), that is: "I" -> "I"

test.txt.output に解析結果が出力されました。coref アノテータを指定したので共参照の解析結果が表示され、 2つ目の文の 'it' が 1つ目の文の 'an apple' を指していることなどが解析できています。

なお、プロパティファイルを指定しなかった場合、CoreNLP はclasspath 上の StanfordCoreNLP.properties を、それが無ければ .jar に含まれるedu/stanford/nlp/pipeline/StanfordCoreNLP.properties を読み込んでいます。

逆に言えば -props で指定してしまうとデフォルトの StanfordCoreNLP.properties が読み込まれなくなります。 このjar に組み込まれている edu/stanford/nlp/pipeline/StanfordCoreNLP.properties の中を確認してみると

$ less edu/stanford/nlp/pipeline/StanfordCoreNLP.properties
annotators = tokenize, ssplit, pos, lemma, ner, depparse, mention, coref

tokenize.language = en
...
#pos.model = /u/nlp/data/pos-tagger/wsj3t0-18-left3words/left3words-distsim-wsj-0-18.tagger
...
#ner.model = ...
#ner.model.3class = /u/nlp/data/ner/goodClassifiers/all.3class.distsim.crf.ser.gz

上記のように annotators と tokenize.language 以外のモデルデータの指定などはすべてコメントになっており、デフォルト値が呼ばれているだけだとわかります。 したがって -props で自前のプロパティファイルでモデルデータの指定をしなくても挙動は変わりません。

一方他の言語の場合は違ってきます。例えばドイツ語の言語モデルに含まれている StanfordCoreNLP-german.properties にはドイツ語の解析に必要なモデルデータの指定等が記載されています。

$ cat StanfordCoreNLP-german.properties
annotators = tokenize, ssplit, pos, ner, parse

tokenize.language = de

pos.model = edu/stanford/nlp/models/pos-tagger/german/german-hgc.tagger

ner.model = edu/stanford/nlp/models/ner/german.conll.hgc_175m_600.crf.ser.gz
ner.applyNumericClassifiers = false
ner.useSUTime = false

parse.model = edu/stanford/nlp/models/lexparser/germanFactored.ser.gz
...

したがって英語以外の言語で自前のプロパティファイルを使いたい場合はモデルデータに含まれるデフォルトのプロパティファイルを抽出して、そこに記載していく必要があります。

サーバーとして使用

CoreNLP はサーバとして起動して Web API 経由で使用することもできます。

java -mx5g -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer
[main] INFO CoreNLP - --- StanfordCoreNLPServer#main() called ---
...
[main] INFO CoreNLP -     Threads: 8
[main] INFO CoreNLP - Starting server...
[main] INFO CoreNLP - StanfordCoreNLPServer listening at /0:0:0:0:0:0:0:0:9000

上記のように tcp 9000 でサーバが立ち上がります。

以下のようなクエリを投げることで、CLI と同じようにテキストの解析が可能です。

$ wget --post-data 'I bought an apple at the supermaket. I am going to eat it later.' 'localhost:9000/?properties={"annotators":"tokenize,ssp
lit,pos,lemma,ner,parse,mention,coref","outputFormat":"text"}' -O -
...
             Sentence #1 (8 tokens):
I bought an apple at the supermaket.
[Text=I CharacterOffsetBegin=0 CharacterOffsetEnd=1 PartOfSpeech=PRP Lemma=I NamedEntityTag=O]
...
Coreference set:
        (2,6,[6,7]) -> (1,4,[3,5]), that is: "it" -> "an apple"
Coreference set:
        (2,1,[1,2]) -> (1,1,[1,2]), that is: "I" -> "I"

Java以外の言語からの利用

CoreNLPをサーバとして起動すると http 経由で呼び出せるので、Java系以外の言語からも容易に呼び出すことができます。CoreNLP では Web API の Java 用クライアントが提供されていますが、それ以外に様々な言語のバインディングが公開されています。 CoreNLP のこのサイトではそのような言語やパッケージの一覧をリストアップしています。 Python, Ruby, JavaScriptなど多くのメジャー言語用のライブラリが公開されているので、用途に合うものを利用することが出来そうです。

試しに Python 用のライブラリである py-corenlp を使ってみました。

$ sudo pip install pycorenlp
...
  Downloading pycorenlp-0.3.0.tar.gz
...
Installing collected packages: pycorenlp
  Running setup.py install for pycorenlp ... done
Successfully installed pycorenlp-0.3.0

pip でインストールして以下のようなスクリプトを試してみました。

$ cat test_pycorenlp.py
#!/usr/bin/env python

from pycorenlp import StanfordCoreNLP

nlp = StanfordCoreNLP('http://localhost:9000')

text = 'I bought an apple at the supermaket. I am going to eat it later.'

output = nlp.annotate(text, properties={
    'annotators': 'tokenize,ssplit,pos,lemma,ner,parse,mention,coref',
    'outputFormat': 'text'
})

print(output)

実行してみます。

$ ./test_pycorenlp.py
Sentence #1 (8 tokens):
I bought an apple at the supermaket.
[Text=I CharacterOffsetBegin=0 CharacterOffsetEnd=1 PartOfSpeech=PRP Lemma=I NamedEntityTag=O]
[Text=bought CharacterOffsetBegin=2 CharacterOffsetEnd=8 PartOfSpeech=VBD Lemma=buy NamedEntityTag=O]
...
(ROOT
  (S
    (NP (PRP I))
    (VP (VBD bought)
      (NP (DT an) (NN apple))
      (PP (IN at)
        (NP (DT the) (NN supermaket))))
    (. .)))
...
Sentence #2 (8 tokens):
I am going to eat it later.
[Text=I CharacterOffsetBegin=37 CharacterOffsetEnd=38 PartOfSpeech=PRP Lemma=I NamedEntityTag=O]
[Text=am CharacterOffsetBegin=39 CharacterOffsetEnd=41 PartOfSpeech=VBP Lemma=be NamedEntityTag=O]
...
advmod(eat-5, later-7)
punct(going-3, .-8)

Coreference set:
        (2,6,[6,7]) -> (1,4,[3,5]), that is: "it" -> "an apple"
Coreference set:
        (2,1,[1,2]) -> (1,1,[1,2]), that is: "I" -> "I"

問題なく結果を返しています。

以上、CLI および WebAPI として使う方法を紹介してみました。 次回、本命の Java ライブラリを Scala/Spark で使う方法などについて書きたいと思います。

Copyright © astamuse company, ltd. all rights reserved.