アイソモカ

知の遊牧民の開発記録

開発記録 200126 Mon (100本ノック #053)

今回は、Stanford CoreNLP という言語処理ツールを使う。Java で CoreNLP を動かす際にヒープスペースのエラーが出て対処した。それから、PythonXML 形式を読むためのライブラリ ElementTree を初めて使った。  

 

言語処理100本ノック #053

53. Tokenization

Stanford Core NLPを用い,入力テキストの解析結果をXML形式で得よ.また,このXMLファイルを読み込み,入力テキストを1行1単語の形式で出力せよ.

まず、JavaStanford CoreNLP を準備する。それから XML ファイルを読む Python コードを書いた。

Java をインストール

Java を使ったことがない。 Java for Mac OS Xのダウンロード からダウンロードしてインストールした。

Stanford CoreNLP をダウンロードして使う → Java heap space エラー

Stanford CoreNLP から Core NLP 3.9.2 をダウンロードした。

Using Stanford CoreNLP from the command line | Stanford CoreNLP にある使い方を見ながら動かしてみる。

サンプルテキスト (input.txt)

Stanford University is located in California. It is a great university, founded in 1891.

が用意されているので、これを解析する。 解凍したディレクトリで

$ java -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLP -file input.txt

と実行すると、少し待った後 input.txt.xml ができて……ない。

input.txt.xml は最初から入っているので、ぬか喜びしないように、ファイルの変更日を確認しよう。

実行時のメッセージはずらずらと書いてあるが、中盤にエラー文が出ている。ヒープスペースの問題らしい。

...
[main] INFO edu.stanford.nlp.pipeline.TokensRegexNERAnnotator - ner.fine.regexner: Read 585573 unique entries from 2 files
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at edu.stanford.nlp.ling.tokensregex.parser.TokenSequenceParser.<init>(TokenSequenceParser.java:3560)
    at edu.stanford.nlp.ling.tokensregex.TokenSequencePattern.getNewEnv(TokenSequencePattern.java:158)
...

ヒープサイズを指定して実行 → できた

ヒープスペースが一体何なのか分からなくて怖くなったが、コンピュータのメモリのうち、プログラムが使ってよい領域のことらしい。メモリが足りなくて、計算できなくなったと思われる。

Java のヒープサイズを確認する。参考 👉 JVMのヒープサイズとコンテナ時代のチューニング - Folioscope

$ java -XX:+PrintFlagsFinal -version 2>/dev/null | grep -w MaxHeapSize
   size_t MaxHeapSize                              = 1073741824                               {product} {ergonomic}

どうやら、僕の環境では Java のヒープサイズはデフォルトで 1GB になっていて、MacBook Air のメモリが 4 GB なので、その4分の1となっている。

対処として、ヒープサイズを指定して実行する。Java 実行時のオプションに -Xms1024m -Xmx3072m と付けて、初期サイズ = 1024MB、最大サイズ = 3072 MB としてみた。

参考 👉 Javaでヒープ領域不足のエラーJava heap spaceを解決する方法 | TechAcademyマガジン

$ java -Xms1024m -Xmx3072m -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLP -file input.txt

...
Annotation pipeline timing information:
TokenizerAnnotator: 0.3 sec.
WordsToSentencesAnnotator: 0.0 sec.
POSTaggerAnnotator: 0.3 sec.
MorphaAnnotator: 1.0 sec.
NERCombinerAnnotator: 2.5 sec.
DependencyParseAnnotator: 1.3 sec.
CorefAnnotator: 0.4 sec.
TOTAL: 5.8 sec. for 17 tokens at 2.9 tokens/sec.
Pipeline setup: 569.4 sec.
Total time for StanfordCoreNLP pipeline: 575.6 sec.

できた!!! 調べて解決できたので嬉しい。

 

2020/02/06 追記:アノテータオプションでルールベースの共参照解析を指定すると、ここまで時間がかからない。それでもヒープサイズの指定は必要。

isomocha.hatenablog.com

 

解答

nlp.txt を CoreNLP にかける。 使い方によると、CoreNLP を置いてるディレクトリ以外のディレクトリで実行する場合は、次のようにパスを指定する。

$ java -cp "/Users/me/corenlp/*" edu.stanford.nlp.pipeline.StanfordCoreNLP -file inputFile

/Users/me/corenlp/ を自分の置いたところに合わせて、 inputFilenlp.txt とすれば、入力テキストの解析結果がXML形式で得られる。

# nlp.txt.xml
...
          <token id="1">
            <word>Natural</word>
            <lemma>natural</lemma>
            <CharacterOffsetBegin>0</CharacterOffsetBegin>
            <CharacterOffsetEnd>7</CharacterOffsetEnd>
            <POS>JJ</POS>
            <NER>O</NER>
            <Speaker>PER0</Speaker>
          </token>
...

<word></word> に挟まれた部分が単語なので、これを出力したい。

1行ずつ読んでいけばいいかな…と思ったが、Pythonxml を読むためのライブラリ ElementTree があるので、チュートリアル を見ながら使ってみた。

# knock_053.py

import xml.etree.ElementTree as ET
xmlfile = 'nlp.txt.xml'

if __name__ == '__main__':
    tree = ET.parse(xmlfile)
    root = tree.getroot()

    for token in root.findall('./document/sentences/sentence/tokens/token'):
        word = token.find('word').text 
        print(word)

結果

# python knock_053.py >> knock_053.txt (20行目まで)
Natural
language
processing
From
Wikipedia
,
the
free
encyclopedia
Natural
language
processing
-LRB-
NLP
-RRB-
is
a
field
of
computer

気づいたことメモ

  • ()-LRB--RRB- と表現されている
  • クォテーションの左側はバッククォテーションに置き換わっていて、ダブルクオテーションはシングルクォテーション2個になっている
  • Moore'sMoore'sに分かれる
  • hand-written のようにハイフンでつながった単語は1語として扱われる

 

f:id:piijey:20200126212527j:plain
花茶