pythonのnltkライブラリを使ってstanford parserを導入する

# はじめに
備忘録的な感じで残しておきます．
仕様の変更により操作が変わるかもしれませんがご了承ください．

### 使用環境
・nltk 3.5
・java jre1.8.0_261
・windows

### 経緯
テキストから機能概念を抽出する系の論文に，構文解析的なアプローチが用いられており
そこで，色々調べた結果論文でも用いられていたstanford parserの導入に至りました．

構文解析では
英語だとstanford parser，日本語だとsyntaxnetなどが良さそうです．

# 事前準備
事前に以下のurlより

・ スタンフォードパーサー本体
・ 解析言語のモデル

の２点をダウンロードする必要があります．


The Stanford Natural Language Processing Group

https://nlp.stanford.edu/software/lex-parser.shtml#Download

Downloadの欄に行き，以下を参考に
1.スタンフォードパーサー本体
2.解析言語のモデル（解析したい該当言語のみ）
の2つをダウンロードしてください．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1679717/34f7b4ce-0466-8ae1-1993-da87377d9509.png)

参考までに本体モデルが174MB，英語の言語モデルが638MBでした．

# stanford parserの使用

以下のコードをコピペします．
コード内では，先ほどダウンロードしたスタンフォードパーサー本体と解析言語モデルのパスを指定してください．

また，javaをインストールする必要があります．
インストール後は[javaの環境変数を設定する](https://techacademy.jp/magazine/24230)か，コード内でパスを直接指定してください．

※stanfordparserモデルの読み込みに多少時間がかかります．
※nltk3.5を使用していますが，「Please use nltk.parse.corenlp.CoreNLPParser instead.」の警告が出ます（動きはします）．

```python:stanford_parser.py
from nltk.parse.stanford import *
import nltk
from nltk.tree import *

###javaのパスを指定してください，環境変数が設定されている場合はしなくても可###
import os
java_path = "java.exeのパスを指定(C:/Program Files/Java/jre1.8.0_261/bin/java.exeなど)"
os.environ['JAVAHOME'] = java_path
###ここまでjavaのパス参照###


#ダウンロードしたstanford parser本体と解析言語モデルのパスを指定
#stanfordparserのパス指定
stanfordparser_path =  'stanfordparser本体のパスを指定(stanford-parser.jar)'
#分析言語モデルのパス指定
stanfordparser_model_path = '解析言語のモデルのパスを指定(stanford-corenlp-4.2.0-models-english.jar)'


#POSタグの分析用
p = StanfordParser(path_to_jar=stanfordparser_path, path_to_models_jar = stanfordparser_model_path)

#係り受け関係の分析用
dep_parser = StanfordDependencyParser(path_to_jar=stanfordparser_path, path_to_models_jar = stanfordparser_model_path)


#######################
### stanford parser ###
#######################

# POSタグの分析
def POSTagAnalysis(text):
    
    #POSタグの分析(iterator形式で返ってくる)
    out = p.raw_parse(text)
    #outの型をlist_iteratorからlistへ
    out = list(out)
    
    #Treeを取得 ※テキストは一つと仮定，増えるとout[1]などに格納されるかも
    tree = out[0]
    
    return tree


#係り受け関係の分析
def dependenceAnalysis(text):
    
    #係り受け関係分析 (iterator形式で返ってくる)
    out = dep_parser.raw_parse(text)
    #outの型をlist_iteretorからlistへ
    out = list(out)
    
    #parseを取得 ※テキストは一つと仮定，増えるとout[1]などに格納されるかも
    parse = out[0]

    return parse
```

## POSタグ出力
句構造解析によるPOSタグの分析は以下で行うことができます．

```python:POS_analysis.py
# 分析したいテキスト
text = "This is a pen."

# POSタグの分析（上記で定義した関数）
tree = POSTagAnalysis(text)

# 出力
# POSタグの情報
print(tree)
# 1語ずつのPOSタグ
print(tree.pos())
# 木構造での出力
print(tree.pretty_print())

```

出力結果

```python:POS_analysis_output.py
#出力結果
(ROOT (S (NP (DT This)) (VP (VBZ is) (NP (DT a) (NN pen))) (. .)))
[('This', 'DT'), ('is', 'VBZ'), ('a', 'DT'), ('pen', 'NN'), ('.', '.')]
         ROOT            
          |               
          S              
  ________|____________   
 |        VP           | 
 |     ___|____        |  
 NP   |        NP      | 
 |    |    ____|___    |  
 DT  VBZ  DT       NN  . 
 |    |   |        |   |  
This  is  a       pen  . 

None
```

### 係り受け関係（2語関係の出力）
また，2語の関係を出力したい場合は以下で可能です．


```python:dependence_analysis.py
# 分析したいテキスト
text = "This is a pen."

# 係り受け関係の分析(上記で定義した関数)
parse = dependenceAnalysis(text)

# 出力
# 係り受け関係の情報
print(parse)
#2語関係
print(list(parse.triples()))
```

出力結果

```python:dependence_analysis_output.py
defaultdict(<function DependencyGraph.__init__.<locals>.<lambda> at 0x000001E6DBA4FDC8>,
            {0: {'address': 0,
                 'ctag': 'TOP',
                 'deps': defaultdict(<class 'list'>, {'root': [4]}),
                 'feats': None,
                 'head': None,
                 'lemma': None,
                 'rel': None,
                 'tag': 'TOP',
                 'word': None},
             1: {'address': 1,
                 'ctag': 'DT',
                 'deps': defaultdict(<class 'list'>, {}),
                 'feats': '_',
                 'head': 4,
                 'lemma': '_',
                 'rel': 'nsubj',
                 'tag': 'DT',
                 'word': 'This'},
             2: {'address': 2,
                 'ctag': 'VBZ',
                 'deps': defaultdict(<class 'list'>, {}),
                 'feats': '_',
                 'head': 4,
                 'lemma': '_',
                 'rel': 'cop',
                 'tag': 'VBZ',
                 'word': 'is'},
             3: {'address': 3,
                 'ctag': 'DT',
                 'deps': defaultdict(<class 'list'>, {}),
                 'feats': '_',
                 'head': 4,
                 'lemma': '_',
                 'rel': 'det',
                 'tag': 'DT',
                 'word': 'a'},
             4: {'address': 4,
                 'ctag': 'NN',
                 'deps': defaultdict(<class 'list'>,
                                     {'cop': [2],
                                      'det': [3],
                                      'nsubj': [1]}),
                 'feats': '_',
                 'head': 0,
                 'lemma': '_',
                 'rel': 'root',
                 'tag': 'NN',
                 'word': 'pen'}})
[(('pen', 'NN'), 'nsubj', ('This', 'DT')), (('pen', 'NN'), 'cop', ('is', 'VBZ')), (('pen', 'NN'), 'det', ('a', 'DT'))]
```

# その他の出力形式
上記以外にも様々な出力が可能です．詳しくは[nltk.treeのドキュメント](https://www.nltk.org/_modules/nltk/tree.html)を参考にしてください．

```python:output_example.py
# Treeの出力例
print("tree")
print(tree)

# 階層構造の指定可能な値一覧
print("\n-----\ntree.treepositions()")
print(tree.treepositions())

# 階層構造の出力例
print("\n-----\ntree[0][0]")
print(tree[0][0])

# POSラベルの出力
print("\n-----\ntree[0][0].label()")
print(tree[0][0].label())

# 単語の出力
print("\n-----\ntree.leaves()")
print(tree.leaves())

# 単語のPOSタグ出力
print("\n-----\ntree.pos()")
print(tree.pos())

# 木構造の出力
print("\n-----\ntree[0].pretty_print()")
print(tree.pretty_print())
```

出力結果

```output_example.py
tree
(ROOT (S (NP (DT This)) (VP (VBZ is) (NP (DT a) (NN pen))) (. .)))

-----
tree.treepositions()
[(), (0,), (0, 0), (0, 0, 0), (0, 0, 0, 0), (0, 1), (0, 1, 0), (0, 1, 0, 0), (0, 1, 1), (0, 1, 1, 0), (0, 1, 1, 0, 0), (0, 1, 1, 1), (0, 1, 1, 1, 0), (0, 2), (0, 2, 0)]

-----
tree[0][0]
(NP (DT This))

-----
tree[0][0].label()
NP

-----
tree.leaves()
['This', 'is', 'a', 'pen', '.']

-----
tree.pos()
[('This', 'DT'), ('is', 'VBZ'), ('a', 'DT'), ('pen', 'NN'), ('.', '.')]

-----
tree[0].pretty_print()
         ROOT            
          |               
          S              
  ________|____________   
 |        VP           | 
 |     ___|____        |  
 NP   |        NP      | 
 |    |    ____|___    |  
 DT  VBZ  DT       NN  . 
 |    |   |        |   |  
This  is  a       pen  . 

None
```

# POSタグについて
句構造解析に使用されるようです．
VP，DT，VBZ，DT，NN などのタグによって体系的に係り受け関係などの分析を可能にします．
タグの種類については以下のページが一覧まとめとなっており参考になります．
・[Alphabetical list of part-of-speech tags used in the Penn Treebank Project](https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html)
・[自然言語処理におけるPOSタグと係り受けタグ一覧](https://qiita.com/kei_0324/items/400f639b2f185b39a0cf)

# 参考サイト
ノート/テキストマイニング/NLTK+StanfordPraser-何もしなくてよくなっている！ - 東邦大学理学部情報科学科　山内のサイト
https://pepper.is.sci.toho-u.ac.jp/pepper/index.php?%A5%CE%A1%BC%A5%C8%2F%A5%C6%A5%AD%A5%B9%A5%C8%A5%DE%A5%A4%A5%CB%A5%F3%A5%B0%2FNLTK%2BStanfordPraser-%B2%BF%A4%E2%A4%B7%A4%CA%A4%AF%A4%C6%A4%E8%A4%AF%A4%CA%A4%C3%A4%C6%A4%A4%A4%EB%A1%AA