今回は、Stanford CoreNLP という言語処理ツールを使う。Java で CoreNLP を動かす際にヒープスペースのエラーが出て対処した。それから、Python で XML 形式を読むためのライブラリ ElementTree を初めて使った。
言語処理100本ノック #053
53. Tokenization
Stanford Core NLPを用い,入力テキストの解析結果をXML形式で得よ.また,このXMLファイルを読み込み,入力テキストを1行1単語の形式で出力せよ.
まず、Java と Stanford 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 追記:アノテータオプションでルールベースの共参照解析を指定すると、ここまで時間がかからない。それでもヒープサイズの指定は必要。
解答
nlp.txt を CoreNLP にかける。 使い方によると、CoreNLP を置いてるディレクトリ以外のディレクトリで実行する場合は、次のようにパスを指定する。
$ java -cp "/Users/me/corenlp/*" edu.stanford.nlp.pipeline.StanfordCoreNLP -file inputFile
/Users/me/corenlp/
を自分の置いたところに合わせて、
inputFile
を nlp.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行ずつ読んでいけばいいかな…と思ったが、Python で xml を読むためのライブラリ 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's
はMoore
と's
に分かれるhand-written
のようにハイフンでつながった単語は1語として扱われる