PythonによるStanfordNLP入門

## StanfordNLPとCoreNLP

StanfordNLPとは53言語に対応したPythonで使えるNLPライブラリで、日本語もその中に含まれています。詳しくは[ここ](https://dev.classmethod.jp/machine-learning/python-stanford-nlp/)。

StanfordNLPはpipでインストール可能

```
pip install stanfordnlp
```

一方、同様にスタンフォード大が提供するCoreNLPというライブラリもあります。日本語非対応（後述）で確かめていませんが、設定は次のような手順（らしい）です。参考にした元記事は[これ](https://www.analyticsvidhya.com/blog/2019/02/stanfordnlp-nlp-library-python/)で、この投稿の多くの記述がリンク先の記事をベースにしたものです。英語ですが、よくまとまっていて必見。
1. CoreNLPパッケージをダウンロード

```
wget http://nlp.stanford.edu/software/stanford-corenlp-full-2018-10-05.zip
```

2. アンジップ

```
unzip stanford-corenlp-full-2018-10-05.zip
```

3. CoreNLPサーバを開始

```
java -mx4g -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer -port 9000 -timeout 15000
```



## StanfordNLPとCoreNLPの違いは何？
では、pipコマンドで簡単にインストールできるStanfordNLPがあるのに、CoreNLPを使う理由とは？ざっくり違いを並べたらこんな感じ。参照したのは[こちら](https://stackoverflow.com/questions/56205454/whats-the-difference-between-the-new-stanfordnlp-native-python-package-and-the)と[こちら](https://meta.stackoverflow.com/questions/345593/whats-the-difference-between-stanford-nlp-and-corenlp)、そして公式ドキュメント。

StanfordNLP
* 53言語対応で、日本語にも使える
* センテンス分割（トークン化）、係り受け解析、普遍的な構文（解析）リソースの取得（exploiting UD resources＝定訳不明）が__可能__
* NER（固有表現抽出）、共参照（coreference）、関係抽出（relation extraction）、オープン情報抽出（OPEN IE＝訳が存在しない？）、手書き文字のパターンマッチングは__無理__

CoreNLP
* 対応言語はアラビア語、中国語、英語、フランス語、ドイツ語、スペイン語。日本語は__NG__
* StanfordNLPで無理だった上記の処理が__可能__。
* トレーニングに使えるデータの幅が広い。

つまり、英語など対応言語を分析する場合はCoreNLPのほうが適していますが、日本語など非対応言語の分析や、ラッパーを使うのが面倒くさい場合はStanfordNLPを使うと良いです。


## ニューラルパイプラインの実行とProcessorsの内訳
今回は、日本語の言語モデルを利用します。

```py:日本語の言語モデルダウンロード
import stanfordnlp
stanfordnlp.download('ja')
```

```py:日本語でデフォルトのパイプライン設定
nlp = stanfordnlp.Pipeline(processors= 'tokenize,mwt,pos,lemma,depparse', lang='ja')
```
5つのプロセッサはデフォルトになっているため、特定のプロセッサのみ必要という場合以外は、実際には明示的に指定する必要はありません。プロセッサの機能はそれぞれ以下の通り。公式ドキュメントを[参照](https://stanfordnlp.github.io/stanfordnlp/processors.html)。
1. tokenize
    * トークナイザー
    * DocumentをSentenceに分割。各SentenceはTokenのリストを含む。複数ワードのトークンも予測。
2. mwt
    * tokenizerが予測した複数ワードのトークン（multi-word token=MWT）を拡張
3. lemma
    * コーパス内の全トークンのレンマ（見出し語）を生成
    * Word.textとWord.uposを用いてWordにレンマ化を施す。出来たものはWord.lemmaでアクセス可能
4. pos
    *  universal POS (UPOS) tags、treebank-specific POS (XPOS) tags、universal morphological features (UFeats)でトークンにラベル付与
    * UPOS, XPOS, UFeatsはそれぞれWord.pos, Word.xpos, Word.ufeatsでアクセス可能
5. depparse
    * 統語的依存構造解析（syntactic dependency parser）
    * センテンスの各単語の主要部（syntactic head）と、２単語間の係り受け関係（dependency relation）を決める。Word.governorとWord.dependency_relation属性でアクセス可能。


## テキスト分析
利用したテキストは青空文庫にあるオーウェルの『[詩とマイクロホン](https://www.aozora.gr.jp/cards/002035/files/59408_67530.html)』の冒頭です。
The Creative CATさんによる翻訳。感謝。
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/"><img alt="クリエイティブ・コモンズ・ライセンス" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/88x31.png" /></a><br />この 作品 は <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">クリエイティブ・コモンズ 表示 3.0 非移植 ライセンス</a>の下に提供されています。

```py:トークン出力
doc = nlp('一年ほど前、私は何人かと共にインドに向けて文学作品を放送する事業に携わっていた。種々のものをとりあげた中で、かなりの部分が現代ないしそれに近い時代の英国作家の韻文だった――例えばエリオット、ハーバート・リード、オーデン、スペンダー、ディラン・トーマス、ヘンリー・トリース、アレックス・コンフォート、ロバート・ブリッジズ、エドムンド・ブルンデン、Ｄ・Ｈ・ローレンス。詩の実作者に参加してもらえる場合はいつでもそうしていた。何故にこういう特殊な番組（ラジオ戦争における遠方からのささやかな側面攻撃だ）が始められることになったかは改めて説明するまでもないが、インド人聴衆に向けた放送である、という事実によって、我々の技法がある程度まで規定されていたという点には触れる必要があるだろう。要点はこうだ。我々の文芸番組はインド大学の学生たちをターゲットにしていた。彼らは少数かつ敵対的な聴衆で、英国のプロパガンダと表現しうるものは一つとして届かなかった。あらかじめ、聴取者は多めに見積もっても数千人を越すことはないだろうということがわかっていた。これが通常オンエアできる範囲を超えて「ハイブロウ」な番組を作るための口実になったのだ。')

doc.sentences[0].print_tokens()
```

```py:実行結果
<Token index=1;words=[<Word index=1;text=一年;lemma=一年;upos=NOUN;xpos=_;feats=_;governor=3;dependency_relation=nmod>]>
<Token index=2;words=[<Word index=2;text=ほど;lemma=ほど;upos=ADP;xpos=_;feats=_;governor=1;dependency_relation=case>]>
<Token index=3;words=[<Word index=3;text=前;lemma=前;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=obl>]>
<Token index=4;words=[<Word index=4;text=、;lemma=、;upos=PUNCT;xpos=_;feats=_;governor=3;dependency_relation=punct>]>
<Token index=5;words=[<Word index=5;text=私;lemma=私;upos=PRON;xpos=_;feats=_;governor=21;dependency_relation=nsubj>]>
<Token index=6;words=[<Word index=6;text=は;lemma=は;upos=ADP;xpos=_;feats=_;governor=5;dependency_relation=case>]>
<Token index=7;words=[<Word index=7;text=何人;lemma=何人;upos=ADV;xpos=_;feats=_;governor=17;dependency_relation=advmod>]>
<Token index=8;words=[<Word index=8;text=か;lemma=か;upos=PART;xpos=_;feats=_;governor=7;dependency_relation=mark>]>
<Token index=9;words=[<Word index=9;text=と共に;lemma=とともに;upos=PART;xpos=_;feats=_;governor=7;dependency_relation=case>]>
<Token index=10;words=[<Word index=10;text=インド;lemma=インド;upos=PROPN;xpos=_;feats=_;governor=12;dependency_relation=iobj>]>
<Token index=11;words=[<Word index=11;text=に;lemma=に;upos=ADP;xpos=_;feats=_;governor=10;dependency_relation=case>]>
<Token index=12;words=[<Word index=12;text=向け;lemma=向ける;upos=VERB;xpos=_;feats=_;governor=17;dependency_relation=acl>]>
<Token index=13;words=[<Word index=13;text=て;lemma=て;upos=SCONJ;xpos=_;feats=_;governor=12;dependency_relation=mark>]>
<Token index=14;words=[<Word index=14;text=文学;lemma=文学;upos=NOUN;xpos=_;feats=_;governor=15;dependency_relation=compound>]>
<Token index=15;words=[<Word index=15;text=作品;lemma=作品;upos=NOUN;xpos=_;feats=_;governor=17;dependency_relation=obj>]>
<Token index=16;words=[<Word index=16;text=を;lemma=を;upos=ADP;xpos=_;feats=_;governor=15;dependency_relation=case>]>
<Token index=17;words=[<Word index=17;text=放送;lemma=放送;upos=VERB;xpos=_;feats=_;governor=19;dependency_relation=acl>]>
<Token index=18;words=[<Word index=18;text=する;lemma=する;upos=AUX;xpos=_;feats=_;governor=17;dependency_relation=aux>]>
<Token index=19;words=[<Word index=19;text=事業;lemma=事業;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=iobj>]>
<Token index=20;words=[<Word index=20;text=に;lemma=に;upos=ADP;xpos=_;feats=_;governor=19;dependency_relation=case>]>
<Token index=21;words=[<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>]>
<Token index=22;words=[<Word index=22;text=て;lemma=て;upos=SCONJ;xpos=_;feats=_;governor=21;dependency_relation=mark>]>
<Token index=23;words=[<Word index=23;text=い;lemma=いる;upos=AUX;xpos=_;feats=_;governor=21;dependency_relation=aux>]>
<Token index=24;words=[<Word index=24;text=た;lemma=た;upos=AUX;xpos=_;feats=_;governor=21;dependency_relation=aux>]>
<Token index=25;words=[<Word index=25;text=。;lemma=。;upos=PUNCT;xpos=_;feats=_;governor=21;dependency_relation=punct>]>
```

Document.sentencesはSentence、つまり１文が集まったリストなので、複数の文がある場合にはインデックスを指定するか、for文で回すなりする必要があります。

Sentenceオブジェクトにはwords, tokens, dependenciesプロパティに加え、これらを出力してすぐ手軽に確認するためprint_を加えたメソッドのprint_words, print_tokens, print_dependenciesがあります。

また、日本語では上記実行結果にあるように、xposとfeatsが表示されないようです。

## レンマ化と品詞タグ付け、係り受け解析
以下の２つのコードは[この記事](https://www.analyticsvidhya.com/blog/2019/02/stanfordnlp-nlp-library-python/)からやや省略して持ってきたものです。説明など元記事が詳しいので推奨。実行結果は長いので一部だけ記載しています。

```py:レンマ化
# Lemmatization


import pandas as pd

# extract lemma
def extract_lemma(doc):
  parsed_text = {'word':[], 'lemma':[]}
  for sent in doc.sentences:
    for wrd in sent.words:
      #extract text and lemma
      parsed_text['word'].append(wrd.text)
      parsed_text['lemma'].append(wrd.lemma)
  # return a dataframe
  return pd.DataFrame(parsed_text)
  
  
extract_lemma(doc)
```
|  | word | lemma |
|--:|:--|:--|
| 0 | 一年 | 一年 |
| 1 | ほど | ほど |
| 2 | 前 | 前 |
| 3 | 、 | 、 |
| 4 | 私 | 私 |
| 5 | は | は |
| 6 | 何人 | 何人 |
| 7 | か | か |
| 8 | と共に | とともに |
| 9 | インド | インド |
| 10 | に | に |
| 11 | 向け | 向ける |
| 12 | て | て |
| 13 | 文学 | 文学 |
| 14 | 作品 | 作品 |
| 15 | を | を |
| 16 | 放送 | 放送 |
| 17 | する | する |
| 18 | 事業 | 事業 |
| 19 | に | に |
| 20 | 携わっ | 携わる |
| 21 | て | て |
| 22 | い | いる |
| 23 | た | た |
| 24 | 。 | 。 |




```py:品詞タグ付け
# Parts of Speech (PoS) Tagging

def extract_pos(doc):
  parsed_text = {'word':[], 'pos':[]}
  for sent in doc.sentences:
    for wrd in sent.words:
      parsed_text['word'].append(wrd.text)
      parsed_text['pos'].append(wrd.upos)
  return pd.DataFrame(parsed_text)

extract_pos(doc)
```
|  | word | pos |
|--:|:--|:--|
| 0 | 一年 | NOUN |
| 1 | ほど | ADP |
| 2 | 前 | NOUN |
| 3 | 、 | PUNCT |
| 4 | 私 | PRON |
| 5 | は | ADP |
| 6 | 何人 | ADV |
| 7 | か | PART |
| 8 | と共に | PART |
| 9 | インド | PROPN |
| 10 | に | ADP |
| 11 | 向け | VERB |
| 12 | て | SCONJ |
| 13 | 文学 | NOUN |
| 14 | 作品 | NOUN |
| 15 | を | ADP |
| 16 | 放送 | VERB |
| 17 | する | AUX |
| 18 | 事業 | NOUN |
| 19 | に | ADP |
| 20 | 携わっ | VERB |
| 21 | て | SCONJ |
| 22 | い | AUX |
| 23 | た | AUX |
| 24 | 。 | PUNCT |




```py:係り受け解析
for sent in doc.sentences:
  for wrd in sent.dependencies:
    print(wrd)
```

```py:実行結果
(<Word index=3;text=前;lemma=前;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=obl>, 'nmod', <Word index=1;text=一年;lemma=一年;upos=NOUN;xpos=_;feats=_;governor=3;dependency_relation=nmod>)
(<Word index=1;text=一年;lemma=一年;upos=NOUN;xpos=_;feats=_;governor=3;dependency_relation=nmod>, 'case', <Word index=2;text=ほど;lemma=ほど;upos=ADP;xpos=_;feats=_;governor=1;dependency_relation=case>)
(<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>, 'obl', <Word index=3;text=前;lemma=前;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=obl>)
(<Word index=3;text=前;lemma=前;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=obl>, 'punct', <Word index=4;text=、;lemma=、;upos=PUNCT;xpos=_;feats=_;governor=3;dependency_relation=punct>)
(<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>, 'nsubj', <Word index=5;text=私;lemma=私;upos=PRON;xpos=_;feats=_;governor=21;dependency_relation=nsubj>)
(<Word index=5;text=私;lemma=私;upos=PRON;xpos=_;feats=_;governor=21;dependency_relation=nsubj>, 'case', <Word index=6;text=は;lemma=は;upos=ADP;xpos=_;feats=_;governor=5;dependency_relation=case>)
(<Word index=17;text=放送;lemma=放送;upos=VERB;xpos=_;feats=_;governor=19;dependency_relation=acl>, 'advmod', <Word index=7;text=何人;lemma=何人;upos=ADV;xpos=_;feats=_;governor=17;dependency_relation=advmod>)
(<Word index=7;text=何人;lemma=何人;upos=ADV;xpos=_;feats=_;governor=17;dependency_relation=advmod>, 'mark', <Word index=8;text=か;lemma=か;upos=PART;xpos=_;feats=_;governor=7;dependency_relation=mark>)
(<Word index=7;text=何人;lemma=何人;upos=ADV;xpos=_;feats=_;governor=17;dependency_relation=advmod>, 'case', <Word index=9;text=と共に;lemma=とともに;upos=PART;xpos=_;feats=_;governor=7;dependency_relation=case>)
(<Word index=12;text=向け;lemma=向ける;upos=VERB;xpos=_;feats=_;governor=17;dependency_relation=acl>, 'iobj', <Word index=10;text=インド;lemma=インド;upos=PROPN;xpos=_;feats=_;governor=12;dependency_relation=iobj>)
(<Word index=10;text=インド;lemma=インド;upos=PROPN;xpos=_;feats=_;governor=12;dependency_relation=iobj>, 'case', <Word index=11;text=に;lemma=に;upos=ADP;xpos=_;feats=_;governor=10;dependency_relation=case>)
(<Word index=17;text=放送;lemma=放送;upos=VERB;xpos=_;feats=_;governor=19;dependency_relation=acl>, 'acl', <Word index=12;text=向け;lemma=向ける;upos=VERB;xpos=_;feats=_;governor=17;dependency_relation=acl>)
(<Word index=12;text=向け;lemma=向ける;upos=VERB;xpos=_;feats=_;governor=17;dependency_relation=acl>, 'mark', <Word index=13;text=て;lemma=て;upos=SCONJ;xpos=_;feats=_;governor=12;dependency_relation=mark>)
(<Word index=15;text=作品;lemma=作品;upos=NOUN;xpos=_;feats=_;governor=17;dependency_relation=obj>, 'compound', <Word index=14;text=文学;lemma=文学;upos=NOUN;xpos=_;feats=_;governor=15;dependency_relation=compound>)
(<Word index=17;text=放送;lemma=放送;upos=VERB;xpos=_;feats=_;governor=19;dependency_relation=acl>, 'obj', <Word index=15;text=作品;lemma=作品;upos=NOUN;xpos=_;feats=_;governor=17;dependency_relation=obj>)
(<Word index=15;text=作品;lemma=作品;upos=NOUN;xpos=_;feats=_;governor=17;dependency_relation=obj>, 'case', <Word index=16;text=を;lemma=を;upos=ADP;xpos=_;feats=_;governor=15;dependency_relation=case>)
(<Word index=19;text=事業;lemma=事業;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=iobj>, 'acl', <Word index=17;text=放送;lemma=放送;upos=VERB;xpos=_;feats=_;governor=19;dependency_relation=acl>)
(<Word index=17;text=放送;lemma=放送;upos=VERB;xpos=_;feats=_;governor=19;dependency_relation=acl>, 'aux', <Word index=18;text=する;lemma=する;upos=AUX;xpos=_;feats=_;governor=17;dependency_relation=aux>)
(<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>, 'iobj', <Word index=19;text=事業;lemma=事業;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=iobj>)
(<Word index=19;text=事業;lemma=事業;upos=NOUN;xpos=_;feats=_;governor=21;dependency_relation=iobj>, 'case', <Word index=20;text=に;lemma=に;upos=ADP;xpos=_;feats=_;governor=19;dependency_relation=case>)
(<Word index=0;text=ROOT>, 'root', <Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>)
(<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>, 'mark', <Word index=22;text=て;lemma=て;upos=SCONJ;xpos=_;feats=_;governor=21;dependency_relation=mark>)
(<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>, 'aux', <Word index=23;text=い;lemma=いる;upos=AUX;xpos=_;feats=_;governor=21;dependency_relation=aux>)
(<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>, 'aux', <Word index=24;text=た;lemma=た;upos=AUX;xpos=_;feats=_;governor=21;dependency_relation=aux>)
(<Word index=21;text=携わっ;lemma=携わる;upos=VERB;xpos=_;feats=_;governor=0;dependency_relation=root>, 'punct', <Word index=25;text=。;lemma=。;upos=PUNCT;xpos=_;feats=_;governor=21;dependency_relation=punct>)
```

## 後記
多言語対応で簡単な処理ができるのはありがたい。各言語処理の際に様々なライブラリを探して作法を覚えずとも使えるのは気軽。

spacyのようなビジュアライザはなさそうなので、係り受け解析などは追加で構造を描く必要がありそう(Graphvizとか？)。

Sentenceオブジェクトのtokensとwordsの使い分け、厳密にはtokensの使いどころがわからない。

```
# <Token index=12;words=[<Word index=12;text=向け;lemma=向ける;upos=VERB;xpos=_;feats=_;governor=17;dependency_relation=acl>]>
# <Word index=12;text=向け;lemma=向ける;upos=VERB;xpos=_;feats=_;governor=17;dependency_relation=acl>
```

間違い、勘違い、曖昧な点が記事にあった場合、ご指摘いただければとても助かります。