素人の言語処理100本ノック:53


[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の挑戦記録です。環境はUbuntu 16.04 LTS ＋ Python 3.5.2 \:\: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧は[こちら](http://qiita.com/segavvy/items/fb50ba8097d59475f760)からどうぞ。

## 第6章: 英語テキストの処理
>英語のテキスト（nlp.txt）に対して，以下の処理を実行せよ．

###53. Tokenization
>[Stanford Core NLP](http://stanfordnlp.github.io/CoreNLP/)を用い，入力テキストの解析結果をXML形式で得よ．また，このXMLファイルを読み込み，入力テキストを1行1単語の形式で出力せよ．

####出来上がったコード：

```python:main.py
# coding: utf-8
import os
import subprocess
import xml.etree.ElementTree as ET

fname = 'nlp.txt'
fname_parsed = 'nlp.txt.xml'


def parse_nlp():
	'''nlp.txtをStanford Core NLPで解析しxmlファイルへ出力
	すでに結果ファイルが存在する場合は実行しない
	'''
	if not os.path.exists(fname_parsed):

		# StanfordCoreNLP実行、標準エラーはparse.outへ出力
		subprocess.run(
			'java -cp "/usr/local/lib/stanford-corenlp-full-2016-10-31/*"'
			' -Xmx2g'
			' edu.stanford.nlp.pipeline.StanfordCoreNLP'
			' -annotators tokenize,ssplit,pos,lemma,ner,parse,dcoref'
			' -file ' + fname + ' 2>parse.out',
			shell=True,		# shellで実行
			check=True		# エラーチェックあり
		)


# nlp.txtを解析
parse_nlp()

# 解析結果のxmlをパース
root = ET.parse(fname_parsed)

# wordのみ取り出し
for word in root.iter('word'):
	print(word.text)
```

####実行結果：

長いので先頭部分の抜粋です。

```console:端末：先頭部分
Natural
language
processing
From
Wikipedia
,
the
free
encyclopedia
Natural
language
processing
-LRB-
NLP
-RRB-
is
a
field
of
computer
science
,
artificial
intelligence
,
and
linguistics
concerned
with
the
interactions
between
computers
and
human
-LRB-
natural
-RRB-
languages
.
As
such
,
NLP
is
related
to
the
area
of
humani-computer
interaction
.
```

全体の結果は[GitHub](https://github.com/segavvy/nlp100_Python/tree/master/53)にアップしています。

なお、Stanford Core NLPの実行時は標準エラーに次のようなメッセージが出力されます。今回のプログラムではparse.outにリダイレクトさせました。

```console:端末：parse.out
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator tokenize
[main] INFO edu.stanford.nlp.pipeline.TokenizerAnnotator - No tokenizer type provided. Defaulting to PTBTokenizer.
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator ssplit
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator pos
[main] INFO edu.stanford.nlp.tagger.maxent.MaxentTagger - Reading POS tagger model from edu/stanford/nlp/models/pos-tagger/english-left3words/english-left3words-distsim.tagger ... done [2.6 sec].
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator lemma
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator ner
[main] INFO edu.stanford.nlp.ie.AbstractSequenceClassifier - Loading classifier from edu/stanford/nlp/models/ner/english.all.3class.distsim.crf.ser.gz ... done [5.3 sec].
[main] INFO edu.stanford.nlp.ie.AbstractSequenceClassifier - Loading classifier from edu/stanford/nlp/models/ner/english.muc.7class.distsim.crf.ser.gz ... done [3.7 sec].
[main] INFO edu.stanford.nlp.ie.AbstractSequenceClassifier - Loading classifier from edu/stanford/nlp/models/ner/english.conll.4class.distsim.crf.ser.gz ... done [4.9 sec].
[main] INFO edu.stanford.nlp.time.JollyDayHolidays - Initializing JollyDayHoliday for SUTime from classpath edu/stanford/nlp/models/sutime/jollyday/Holidays_sutime.xml as sutime.binder.1.
[main] INFO edu.stanford.nlp.ling.tokensregex.CoreMapExpressionExtractor - Reading TokensRegex rules from edu/stanford/nlp/models/sutime/defs.sutime.txt
[main] INFO edu.stanford.nlp.ling.tokensregex.CoreMapExpressionExtractor - Read 84 rules
[main] INFO edu.stanford.nlp.ling.tokensregex.CoreMapExpressionExtractor - Reading TokensRegex rules from edu/stanford/nlp/models/sutime/english.sutime.txt
[main] INFO edu.stanford.nlp.ling.tokensregex.CoreMapExpressionExtractor - Read 267 rules
[main] INFO edu.stanford.nlp.ling.tokensregex.CoreMapExpressionExtractor - Reading TokensRegex rules from edu/stanford/nlp/models/sutime/english.holidays.sutime.txt
[main] INFO edu.stanford.nlp.ling.tokensregex.CoreMapExpressionExtractor - Read 25 rules
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator parse
[main] INFO edu.stanford.nlp.parser.common.ParserGrammar - Loading parser from serialized file edu/stanford/nlp/models/lexparser/englishPCFG.ser.gz ... done [0.6 sec].
[main] INFO edu.stanford.nlp.pipeline.StanfordCoreNLP - Adding annotator dcoref

Processing file /home/segavvy/ドキュメント/言語処理100本ノック2015/53/nlp.txt ... writing to /home/segavvy/ドキュメント/言語処理100本ノック2015/53/nlp.txt.xml
Annotating file /home/segavvy/ドキュメント/言語処理100本ノック2015/53/nlp.txt ... done [20.6 sec].

Annotation pipeline timing information:
TokenizerAnnotator: 0.2 sec.
WordsToSentencesAnnotator: 0.0 sec.
POSTaggerAnnotator: 0.2 sec.
MorphaAnnotator: 0.1 sec.
NERCombinerAnnotator: 2.6 sec.
ParserAnnotator: 12.8 sec.
DeterministicCorefAnnotator: 4.7 sec.
TOTAL: 20.6 sec. for 1452 tokens at 70.5 tokens/sec.
Pipeline setup: 27.2 sec.
Total time for StanfordCoreNLP pipeline: 48.1 sec.
```

特に気にする内容はなさそうです。

###Stanford Core NLPのインストール
プログラミングの前に環境の構築です。
今回は問題で指示されている「Stanford Core NLP」が必要です。オフィシャルサイトは[こちら](http://stanfordnlp.github.io/CoreNLP/)です。
このサイトの「Programming languages and operating systems」のところを見るとJava 1.8以上が必要とありますので、Javaのインストールから始めます。

####Java 1.8のインストール
私はOpenJDK 8のJREを入れました。これはapt一発でいけます。

```console:端末：OpenJDK8インストール
segavvy@ubuntu:~$ sudo apt install openjdk-8-jre
[sudo] segavvy のパスワード: 
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の追加パッケージがインストールされます:
  ca-certificates-java fonts-dejavu-extra java-common libbonobo2-0
  libbonobo2-common libgif7 libgnome-2-0 libgnome2-common libgnomevfs2-0
  libgnomevfs2-common liborbit-2-0 openjdk-8-jre-headless
提案パッケージ:
  default-jre libbonobo2-bin desktop-base libgnomevfs2-bin libgnomevfs2-extra
  gamin | fam gnome-mime-data icedtea-8-plugin openjdk-8-jre-jamvm
  fonts-ipafont-gothic fonts-ipafont-mincho ttf-wqy-microhei | ttf-wqy-zenhei
  fonts-indic
以下のパッケージが新たにインストールされます:
  ca-certificates-java fonts-dejavu-extra java-common libbonobo2-0
  libbonobo2-common libgif7 libgnome-2-0 libgnome2-common libgnomevfs2-0
  libgnomevfs2-common liborbit-2-0 openjdk-8-jre openjdk-8-jre-headless
アップグレード: 0 個、新規インストール: 13 個、削除: 0 個、保留: 169 個。
29.5 MB のアーカイブを取得する必要があります。
この操作後に追加で 111 MB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://us.archive.ubuntu.com/ubuntu xenial/main amd64 libbonobo2-common all 2.32.1-3 [34.7 kB]
取得:2 http://us.archive.ubuntu.com/ubuntu xenial/main amd64 liborbit-2-0 amd64 1:2.14.19-1build1 [140 kB]
取得:3 http://us.archive.ubuntu.com/ubuntu xenial/main amd64 libbonobo2-0 amd64 2.32.1-3 [211 kB]
取得:4 http://us.archive.ubuntu.com/ubuntu xenial/main amd64 ca-certificates-java all 20160321 [12.9 kB]
取得:5 http://us.archive.ubuntu.com/ubuntu xenial/main amd64 java-common all 0.56ubuntu2 [7,742 B]
取得:6 http://us.archive.ubuntu.com/ubuntu xenial-updates/main amd64 openjdk-8-jre-headless amd64 8u111-b14-2ubuntu0.16.04.2 [26.9 MB]
（以下略）
```

終わったら、正しくインストールできているか、バージョン表示で確認してみます。

```console:端末：OpenJDK8インストール
segavvy@ubuntu:~$ java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2ubuntu0.16.04.2-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
```

大丈夫そうですね。

####Stanford Core NLPのインストール

続いてStanford Core NLP本体です。
Stanford Core NLPはオフィシャルサイトの[ダウンロードページ](http://stanfordnlp.github.io/CoreNLP/download.html)からダウンロードできます。zipファイルになっているのでダウンロードして展開します。
私はダウンロード後、/usr/local/lib/に展開しました。

```console:端末：zipファイルの展開
segavvy@ubuntu:~/ダウンロード$ sudo unzip stanford-corenlp-full-2016-10-31.zip -d /usr/local/lib/
[sudo] segavvy のパスワード: 
Archive:  stanford-corenlp-full-2016-10-31.zip
   creating: /usr/local/lib/stanford-corenlp-full-2016-10-31/
  inflating: /usr/local/lib/stanford-corenlp-full-2016-10-31/xom-1.2.10-src.jar  
  inflating: /usr/local/lib/stanford-corenlp-full-2016-10-31/CoreNLP-to-HTML.xsl  
  inflating: /usr/local/lib/stanford-corenlp-full-2016-10-31/README.txt  
  inflating: /usr/local/lib/stanford-corenlp-full-2016-10-31/jollyday-0.4.9-sources.jar  
 （以下略）
```

####Pythonバインディングのインストールは見送り

Stanford Core NLPをPythonで直接使うためには、[corenlp-python](https://pypi.python.org/pypi/corenlp-python)などのライブラリが必要です。

ただ、今回の問題の指示は、解析結果のxmlファイルをまず作り、それを改めて読み込んでから処理しなさいという内容です。解析結果のXMLはStanford Core NLPのコマンドで作成できるため、どうやらPythonからStanford Core NLPを直接使う形は想定していないようです（もうインストールで苦労したくない私の希望的観測^^）。ということで、Stanford Core NLPのPythonバインディングは使わないことにしました。

そのため、今回の環境構築はこれで終わりです。

###Stanford Core NLPを使った解析

上述のように、解析はコマンドでできます。今回は、[Quick start](http://stanfordnlp.github.io/CoreNLP/cmdline.html#quick-start)で解説されているコマンドラインをほぼそのまま使ってxmlファイルを作りました。

なお、解析には少し時間がかかります。そのため、すでにnlp.txt.xmlが存在している場合は解析しないようにしました。解析をやり直したい場合は、nlp.txt.xmlを削除してから実行してください。


###コマンドの実行

Pythonからのコマンドの実行は、[`subprocess.run()`](http://docs.python.jp/3/library/subprocess.html#subprocess.run)を使いました。

最初、`shell=True`の指定をしておらず「No such file or directory」のエラーになって悩みましたが、パスの解決はシェルがやっているため、シェル経由で実行しないとjavaの場所が分からないからですね、おそらく。

あと、`check=True`を指定すると、コマンドがエラーを返した時に例外が起きるようになるので、コマンドの実行失敗に気づけるようになります。

###Stanford Core NLPのXML

今回出力されたXMLの先頭部分は、次のような感じになっています。

```console:端末：nlp.txt.xmlの先頭部分
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="CoreNLP-to-HTML.xsl" type="text/xsl"?>
<root>
  <document>
    <sentences>
      <sentence id="1">
        <tokens>
          <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>
          <token id="2">
            <word>language</word>
            <lemma>language</lemma>
            <CharacterOffsetBegin>8</CharacterOffsetBegin>
            <CharacterOffsetEnd>16</CharacterOffsetEnd>
            <POS>NN</POS>
            <NER>O</NER>
            <Speaker>PER0</Speaker>
          </token>
          <token id="3">
            <word>processing</word>
            <lemma>processing</lemma>
            <CharacterOffsetBegin>17</CharacterOffsetBegin>
            <CharacterOffsetEnd>27</CharacterOffsetEnd>
            <POS>NN</POS>
            <NER>O</NER>
            <Speaker>PER0</Speaker>
          </token>
```

ファイル全体は[GitHub](https://github.com/segavvy/nlp100_Python/tree/master/53)にアップしています。

このXMLはXSLTスタイルシートに従ったフォーマットになっています。xmlファイルの先頭部分にあるCoreNLP-to-HTML.xslというのがそれで、Stanford Core NLPのインストール先（私の場合は/usr/local/lib/stanford-corenlp-full-2016-10-31）に入っています。

このファイルをnlp.txt.xmlと同じ位置に持ってきて、nlp.txt.xmlをブラウザで開くと、次のように中身が確認できるようになります。

![Kobito.Xn43Kb.png](https://qiita-image-store.s3.amazonaws.com/0/139624/a34fca5e-88f2-ecae-88aa-6cb4b74b68bb.png "Kobito.Xn43Kb.png")


今回の問題は単語を抽出すれば良いので、wordタグの内容を取り出せば良さそうです。


###XMLの解析
XMLの解析は[ElementTree XML API](http://docs.python.jp/3/library/xml.etree.elementtree.html)を使いました。ElementTree XML APIの[チュートリアル](http://docs.python.jp/3/library/xml.etree.elementtree.html#tutorial)を適当に真似して書いていますが、指定タグの中身を列挙するだけなら簡単です。

　
54本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<hr>

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第6章で用いているデータのライセンスは[クリエイティブ・コモンズ 表示-継承 3.0 非移植](https://creativecommons.org/licenses/by-sa/3.0/legalcode)（[日本語訳](https://creativecommons.org/licenses/by-sa/3.0/deed.ja)）です。*