pythonでお手軽に日本語の構文解析をする

# 結論
- 大量に処理したいとき　jagger + jdepp
- 導入の手間とコード量を惜しみたいとき　spacy + sudachi

あたりが現代的でよさそう。
ただし、spacyはプラグインによっては裏でBERTを使いだすので注意。

# 何がしたかったのか
大量のOCR文章を処理するにあたり、ページの区切りなどでぶった切られた文章を綺麗にしたい。
そのために構文解析を活用できるのでは？と考えたが、なかなかコレというものが見つからず手間取ったので書き記す。

## 対象とする文章の例
> つの機能のみに分解されるのと似ている。　ＳＦや、昔の未来予測が描いてきたように、丸薬やせいぜいがビスケットタイプの完全機能食といったものが発売されて、個人の生活において買い物や調理、食事の時間短縮と健康管理のために、簡便に摂取すればすむような生活を、わたしたちは望むであろうか。おそらく、そうはならないだろう。　化学調味料やコピー食品というものが開発されてきたのは、経済的原理、すなわちコスト削減や大量生産のために、という要因が大きいのだけれども、それでもなお、味を似せる、ということに力点がかけられているのである。確かに、ビスケットタイプや飲料タイプの機能食品は数

PDFなどから抽出した文章は、段組みやページ区切りにより、不自然な部分で切れる場合がある。
前後のページで連結すればいいのでは？と考えるも、段組みや図表、注釈が入っているとそもそも正しい順序で取れているかも怪しかったりする。
よって一旦は確実に「始まりと終わりがある」文章を取得したいと考える。

この文章をLLMの学習などに用いることを考えると
> つの機能のみに分解されるのと似ている。
> 確かに、ビスケットタイプや飲料タイプの機能食品は数

といった文章は捨てたい。
文末側の文章は、句読点で終わっていないのでルールベースで除外できそうだが、文頭の方は、ルールベースでは難しそう。
文章のパターンを眺めてみた結果、SVO（主語・述語・目的語）の係り受けが失われている文章を捨てればいいのでは？となった

# 前提
- python、pipでインストールを済ませたい
- 処理は軽い方がいいので、BERTなどは使いたくない
- 正確性よりも、多少間違っていても手軽に、早く使えるものにしたい

古の知識で調べたら、MeCabとかCaboChaとかがヒットしたが、インストール手順がクソめんどくさいので却下。
CaboChaとかGoogleDriveで配布って正気か？！ 石器時代ですかぁ？？？
そもそもメンテされてないものはあまり使いたくない。



# ステップ１：文分割
とりあえず文章だけ切り分けてみる。
最初は[Bunkai \(日本語文境界判定器\)](https://github.com/megagonlabs/bunkai) がよいのでは？と思ったが、カギ括弧が入ると一気にポンコツだった。

ポンコツな例。
> 「しかし、知らない人間が見たら、どう思うかな。
君はそう、腕っ節の強いほうでもなさそうだし、ひったくりや強盗に目をつけられたら、お手上げだろう。
」　青年は小さく笑った。私の額に開いた穴をとおして、どこか遠くの風景でも見ているような、年寄りじみた笑いだった。
笑っただけで、べつに返事はしなかった。
「ま、いいだろう。
」私も負けずに、声をたてて笑い、額に手をあてがって相手の視線

これくらいならカギ括弧の処理を自分で追加実装すればどうにかなりそうだが、そもそもこんなプリミティブなことで手を加えたくないので、却下。

次に見つけたのが [ja\_sentence\_segmenter](https://github.com/wwwcojp/ja_sentence_segmenter)
こちらはかなりいい感じ。
正規表現ルール程度ならパイプライン的に付け足せそうなのもよいので、基本こちら採用。

> 「しかし、知らない人間が見たら、どう思うかな。君はそう、腕っ節の強いほうでもなさそうだし、ひったくりや強盗に目をつけられたら、お手上げだろう。」青年は小さく笑った。 
私の額に開いた穴をとおして、どこか遠くの風景でも見ているような、年寄りじみた笑いだった。 
笑っただけで、べつに返事はしなかった。 
「ま、いいだろう。」私も負けずに、声をたてて笑い、額に手をあてがって相手の視線

# ステップ２：形態素解析＆構文解析

ここでだいぶ時間を無駄にした。
まず情報がない。正確に言えば、新しい情報がない。
2019とかの情報だったり、とりあえず使えればいいとばかりにMeCabやCabochaをインストールするものだったり。
MeCabは [fugashi](https://github.com/polm/fugashi?tab=readme-ov-file) の存在を知っていたのでそのあたりから何かないかと試行錯誤。

## jdepp + fugashi
暫く探して見つけたのが [jdepp\-python: Python binding for J\.DepP\(C\+\+ implementation of Japanese Dependency Parsers\)](https://github.com/lighttransport/jdepp-python)
Japanese Dependency Parsersでジョニデというのが妙にツボに入ったところもある。

実験用のコンテナにインストール。
``` Dockerfile
RUN pip install fugashi[unidic] jdepp
RUN wget https://github.com/lighttransport/jdepp-python/releases/download/v0.1.0/knbc-mecab-jumandic-2ndpoly.tar.gz \
    && tar xvf knbc-mecab-jumandic-2ndpoly.tar.gz 
```

とりあえずコードを書く
``` python
from fugashi import Tagger
import jdepp
jdepp_model_path = "model/knbc"
input = '''
あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。
'''

tagger = Tagger("-Owakati")
parser = jdepp.Jdepp()
parser.load_model(jdepp_model_path)

pos_formatted = []
for word in tagger(input):
    f = word.feature
    formatted = "{}\t{},{},{},{},{},{},{}".format(
        word.surface,
        f.pos1,
        f.pos2,
        f.cType,
        f.cForm,
        f.lemma,
        f.kana,
        f.pron,       
    )
    pos_formatted.append(formatted)

pos_formatted.append("EOS\n")

print("\n".join(pos_formatted))
sent = parser.parse_from_postagged("\n".join(pos_formatted))
print(jdepp.to_tree(str(sent)))

```

> あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。

 ![morio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570991/54c236fb-a976-4b5f-9e1d-3d6f38050648.png)
 
```
あの    感動詞,フィラー,*,*,あの,アノ,アノ
イーハトーヴォ  名詞,普通名詞,*,*,None,None,None
の      助詞,格助詞,*,*,の,ノ,ノ
すきとおっ      動詞,一般,五段-ラ行,連用形-促音便,透き通る,スキトオッ,スキトーッ
た      助動詞,*,助動詞-タ,連体形-一般,た,タ,タ
風      名詞,普通名詞,*,*,風,カゼ,カゼ
、      補助記号,読点,*,*,、,,
夏      名詞,普通名詞,*,*,夏,ナツ,ナツ
で      助詞,格助詞,*,*,で,デ,デ
も      助詞,係助詞,*,*,も,モ,モ
底      名詞,普通名詞,*,*,底,ソコ,ソコ
に      助詞,格助詞,*,*,に,ニ,ニ
冷た    形容詞,一般,形容詞,語幹-一般,冷たい,ツメタ,ツメタ
さ      接尾辞,名詞的,*,*,さ,サ,サ
を      助詞,格助詞,*,*,を,ヲ,オ
もつ    動詞,一般,五段-タ行,連体形-一般,持つ,モツ,モツ
青      名詞,普通名詞,*,*,青,アオ,アオ
いそら  名詞,固有名詞,*,*,イソラ,イソラ,イソラ
、      補助記号,読点,*,*,、,,
うつくしい      形容詞,一般,形容詞,連体形-一般,美しい,ウツクシイ,ウツクシー
森      名詞,普通名詞,*,*,森,モリ,モリ
で      助詞,格助詞,*,*,で,デ,デ
飾ら    動詞,一般,五段-ラ行,未然形-一般,飾る,カザラ,カザラ
れ      助動詞,*,助動詞-レル,連用形-一般,れる,レ,レ
た      助動詞,*,助動詞-タ,連体形-一般,た,タ,タ
モリーオ        名詞,普通名詞,*,*,None,None,None
市      名詞,普通名詞,*,*,市,シ,シ
、      補助記号,読点,*,*,、,,
郊外    名詞,普通名詞,*,*,郊外,コウガイ,コーガイ
の      助詞,格助詞,*,*,の,ノ,ノ
ぎらぎら        副詞,*,*,*,ぎらぎら,ギラギラ,ギラギラ
ひかる  動詞,一般,五段-ラ行,連体形-一般,光る,ヒカル,ヒカル
草      名詞,普通名詞,*,*,草,クサ,クサ
の      助詞,格助詞,*,*,の,ノ,ノ
波      名詞,普通名詞,*,*,波,ナミ,ナミ
。      補助記号,句点,*,*,。,,
EOS

# S-ID: 1; J.DepP
  0:　　　　　　　　　　　あの━━┓
  1:　　イーハトーヴォの━━┓　　┃
  2:　　　　　　　すきとおった━━┫
  3:　　　　　　　　　　　風、━━┫
  4:　　　　　　　　　　夏でも━━┫
  5:　　　　　　　　　　　底に━━┫
  6:　　　　　　　　　冷たさを━━┫
  7:　　　　　　もつ青いそら、━━┫
  8:　　うつくしい━━┓　　　　　┃
  9:　　　　　　　　森で━━┓　　┃
 10:　　　　　　　　　飾られた━━┫
 11:　　　　　　　モリーオ市、━━┫
 12:　　　　　　　郊外の━━┓　　┃
 13:　　　　　　ぎらぎら━━┫　　┃
 14:　　　　　　　　　　ひかる━━┫
 15:　　　　　　　　　　　草の━━┫
 16:　　　　　　　　　　　　　　波。EOS
 ```

最低限のことはできそうでホッとした。
青いそら、のあたりが残念ではあるが、まぁ、ざっくりやりたいだけなので。

## jagger + jdepp
jdeppの公式サンプルではjaggerが使われていたので、こちらも試してみる

``` python
import jdepp
import jagger

jdepp_model_path = "model/knbc"
jagger_model_path = "model/kwdlc/patterns"

jagger = jagger.Jagger()
jagger.load_model(jagger_model_path)
parser = jdepp.Jdepp()
parser.load_model(jdepp_model_path)

toks = jagger.tokenize(s)
pos_tagged_input = ""
for tok in toks:
    pos_tagged_input += tok.surface() + '\t' + tok.feature() + '\n'
pos_tagged_input += "EOS\n"
print(pos_tagged_input)
sent = parser.parse_from_postagged(pos_tagged_input)
print(jdepp.to_tree(str(sent)))

```

```
あの    指示詞,連体詞形態指示詞,*,*,あの,あの,*
イーハトーヴ    名詞,普通名詞,*,*,イーハトーヴ,イーハトーヴ,自動獲得:Wikipedia Wikipediaリダイレクト:イーハトーブ 代表表記:イーハトーブ/イーハトーブ
ォ      名詞,普通名詞,*,*
の      助詞,格助詞,*,*,の,の,*
すきとおった    動詞,*,子音動詞ラ行,タ形,すきとおる,すきとおった,代表表記:透き通る/すきとおる
風      名詞,普通名詞,*,*,風,かぜ,*
、      特殊,読点,*,*,、,、,*
夏      名詞,時相名詞,*,*,夏,なつ,*
でも    助詞,副助詞,*,*,でも,でも,*
底      名詞,普通名詞,*,*,底,そこ,*
に      助詞,格助詞,*,*,に,に,*
冷た    形容詞,*,イ形容詞アウオ段,語幹,冷たい,つめた,代表表記:冷たい/つめたい 反義:形容詞:温かい/あたたかい;形容詞:熱い/あつい
さ      接尾辞,名詞性述語接尾辞,*,*,さ,さ,*
を      助詞,格助詞,*,*,を,を,*
もつ    動詞,*,子音動詞タ行,基本形,もつ,もつ,*
青い    形容詞,*,イ形容詞アウオ段,基本形,青い,あおい,*
そら    名詞,普通名詞,*,*,そら,そら,代表表記:空/そら 漢字読み:訓 カテゴリ:場所-自然
、      特殊,読点,*,*,、,、,*
うつくしい      形容詞,*,イ形容詞イ段,基本形,うつくしい,うつくしい,代表表記:美しい/うつくしい 反義:形容詞:醜い/みにくい
森      名詞,普通名詞,*,*,森,もり,*
で      助詞,格助詞,*,*,で,で,*
飾ら    動詞,*,子音動詞ラ行,未然形,飾る,かざら,*
れた    接尾辞,動詞性接尾辞,母音動詞,タ形,れる,れた,*
モリーオ        接頭辞,名詞接頭辞,*,*,*,*,*
市      名詞,普通名詞,*,*,市,し,*
、      特殊,読点,*,*,、,、,*
郊外    名詞,普通名詞,*,*,郊外,こうがい,*
のぎ    名詞,普通名詞,*,*,のぎ,のぎ,代表表記:芒/のぎ カテゴリ:植物-部位
ら      接尾辞,名詞性名詞接尾辞,*,*,ら,ら,*
ぎ      名詞,普通名詞,*,*,ぎ,ぎ,代表表記:偽/ぎ 漢字読み:音 カテゴリ:抽象物
ら      接尾辞,名詞性名詞接尾辞,*,*,ら,ら,*
ひかる  名詞,人名,*,*,ひかる,ひかる,人名:日本:名:1038:0.00017
草      名詞,普通名詞,*,*,草,そう,*
の      助詞,接続助詞,*,*,の,の,*
波      名詞,普通名詞,*,*,波,なみ,*
。      特殊,句点,*,*,。,。,*
EOS

# S-ID: 1; J.DepP
  0:　　　　　　　　　　　あの━━┓　　　　　　　　　　　　
  1:　　イーハトーヴォの━━┓　　┃　　　　　　　　　　　　
  2:　　　　　　　すきとおった━━┫　　　　　　　　　　　　
  3:　　　　　　　　　　　　　　風、━━┓　　　　　　　　　
  4:　　　　　　　　　　　　　　　　夏でも━━┓　　　　　　
  5:　　　　　底に━━┓　　　　　　　　　　　┃　　　　　　
  6:　　　冷たさを━━┫　　　　　　　　　　　┃　　　　　　
  7:　　　　　　　　もつ━━┓　　　　　　　　┃　　　　　　
  8:　　　　　　　　　　　青い━━┓　　　　　┃　　　　　　
  9:　　　　　　　　　　　　　そら、━━┓　　┃　　　　　　
 10:　　　　　　　　　　　うつくしい━━┫　　┃　　　　　　
 11:　　　　　　　　　　　　　　　　　森で━━┫　　　　　　
 12:　　　　　　　　　　　　　　　　　　飾られた━━┓　　　
 13:　　　　　　　　　　　　　　　　　　　モリーオ市、━━┓
 14:　　　　　　　　　　　　　郊外のぎらぎらひかる草の━━┫
 15:　　　　　　　　　　　　　　　　　　　　　　　　　　波。EOS

```
「青いそら」は良くなったが、「ぎらぎらひかる」が死んでいる。
あと「夏でも～」が変なところに行ってる。
（厳密にやりたいわけでもないので、許容範囲だけど）
wikipediaとかを出典に出しているあたり、固有名詞にはだいぶ強そう。


## spacy + sudachi
fugashi関連を調べているときに、fugashiからspacyに連携して、構文解析できるみたいな情報が見つかった。
で、更に調べてみると、spacyの日本語形態素解析のデフォルトは現在 [SudachiPy](https://github.com/WorksApplications/SudachiPy) になっているらしい。

こっちもさくっとやってみる
``` Dockerfile
RUN pip install spacy deplacy && python -m spacy download ja_core_news_sm
```

コード量は非常に減ってスッキリする

```python
import deplacy
import spacy

input = '''
あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。
'''

nlp=spacy.load("ja_core_news_sm")

doc=nlp(input)
deplacy.render(doc,Japanese=True)
```
結果
```
あの           DET   <══╗               det(決定詞)
イーハトーヴォ NOUN  ═╗═╝<╗             nmod(体言による連体修飾語)
の             ADP   <╝   ║             case(格表示)
すき           NOUN  ═════╝<╗           obl(斜格補語)
とおっ         VERB  ═╗═════╝═╗═╗<╗     advcl(連用修飾節)
た             AUX   <╝       ║ ║ ║     aux(動詞補助成分)
風             PART  <════════╝ ║ ║     mark(標識)
、             PUNCT <══════════╝ ║     punct(句読点)
夏             NOUN  ═╗═╗<══════╗ ║     obl(斜格補語)
で             ADP   <╝ ║       ║ ║     case(格表示)
も             ADP   <══╝       ║ ║     case(格表示)
底             NOUN  ═╗<╗       ║ ║     obl(斜格補語)
に             ADP   <╝ ║       ║ ║     case(格表示)
冷た           ADJ   ═══╝═╗═╗<╗ ║ ║     obj(目的語)
さ             PART  <════╝ ║ ║ ║ ║     mark(標識)
を             ADP   <══════╝ ║ ║ ║     case(格表示)
もつ           VERB  ═════╗═══╝═╝═╝<╗   acl(連体修飾節)
青             NOUN  <╗   ║         ║   compound(複合)
いそら         NOUN  ═╝═╗<╝         ║   dep(未定義)
、             PUNCT <══╝           ║   punct(句読点)
うつくしい     PROPN <══════╗       ║   advcl(連用修飾節)
森             PROPN ═╗<══╗ ║       ║   obl(斜格補語)
で             ADP   <╝   ║ ║       ║   case(格表示)
飾ら           VERB  ═╗═╗═╝═╝<╗     ║   acl(連体修飾節)
れ             AUX   <╝ ║     ║     ║   aux(動詞補助成分)
た             AUX   <══╝     ║     ║   aux(動詞補助成分)
モリーオ       PROPN <╗       ║     ║   compound(複合)
市             NOUN  ═╝═══════╝═╗<╗ ║   nmod(体言による連体修飾語)
、             PUNCT <══════════╝ ║ ║   punct(句読点)
郊外           NOUN  ═╗<╗         ║ ║   nmod(体言による連体修飾語)
の             ADP   <╝ ║         ║ ║   case(格表示)
ぎらぎら       NOUN  ═══╝<══╗     ║ ║   nmod(体言による連体修飾語)
ひかる         ADJ   <══╗   ║     ║ ║   acl(連体修飾節)
草             NOUN  ═╗═╝<╗ ║     ║ ║   nmod(体言による連体修飾語)
の             ADP   <╝   ║ ║     ║ ║   case(格表示)
波             NOUN  ═════╝═╝═════╝═╝═╗ ROOT(親)
。             PUNCT <════════════════╝ punct(句読点)
```
「青いそら」は死に「ぎらぎらひかる」は拾ってくれた。

## おまけにGINZA
[deplacy/doc/ja\.md at master · KoichiYasuoka/deplacy](https://github.com/KoichiYasuoka/deplacy/blob/master/doc/ja.md)
deplacyのドキュメントを見ると、かなり多くのspacyプラグインが存在することがわかった。
ただ、Transformerを要求するものが多い。
BERT系のものも1つ試してみたが、やはり重くて使えない。

聞いたことがありNoTransformerモードもあったginza試してみた。

出力のみ

```
あの           DET   <══╗                         det(決定詞)
イーハトーヴォ PROPN ═╗═╝<╗                       nmod(体言による連体修飾語)
の             ADP   <╝   ║                       case(格表示)
すきとおっ     VERB  ═╗═══╝<╗                     acl(連体修飾節)
た             AUX   <╝     ║                     aux(動詞補助成分)
風             NOUN  ═══════╝═╗<══════╗           nmod(体言による連体修飾語)
、             PUNCT <════════╝       ║           punct(句読点)
夏             NOUN  ═╗═╗<════╗       ║           obl(斜格補語)
で             ADP   <╝ ║     ║       ║           case(格表示)
も             ADP   <══╝     ║       ║           case(格表示)
底             NOUN  ═╗<════╗ ║       ║           obl(斜格補語)
に             ADP   <╝     ║ ║       ║           case(格表示)
冷た           ADJ   ═╗═╗<╗ ║ ║       ║           obj(目的語)
さ             PART  <╝ ║ ║ ║ ║       ║           mark(標識)
を             ADP   <══╝ ║ ║ ║       ║           case(格表示)
もつ           VERB  ═════╝═╝═╝<╗     ║           acl(連体修飾節)
青             NOUN  <╗         ║     ║           compound(複合)
いそら         NOUN  ═╝═════════╝═╗<╗ ║           nmod(体言による連体修飾語)
、             PUNCT <════════════╝ ║ ║           punct(句読点)
うつくしい     VERB  <══╗           ║ ║           acl(連体修飾節)
森             NOUN  ═╗═╝<╗         ║ ║           obl(斜格補語)
で             ADP   <╝   ║         ║ ║           case(格表示)
飾ら           VERB  ═╗═╗═╝<╗       ║ ║           acl(連体修飾節)
れ             AUX   <╝ ║   ║       ║ ║           aux(動詞補助成分)
た             AUX   <══╝   ║       ║ ║           aux(動詞補助成分)
モリーオ       PROPN <╗     ║       ║ ║           compound(複合)
市             NOUN  ═╝═════╝═══════╝═╝═╗<╗       nmod(体言による連体修飾語)
、             PUNCT <══════════════════╝ ║       punct(句読点)
郊外           NOUN  ═╗═══════════════════╝<╗     nmod(体言による連体修飾語)
の             ADP   <╝                     ║     case(格表示)
ぎらぎら       ADJ   <════╗                 ║     acl(連体修飾節)
ひかる         PROPN <╗   ║                 ║     compound(複合)
草             NOUN  ═╝═╗═╝═════════════════╝<╗   nmod(体言による連体修飾語)
の             ADP   <══╝                     ║   case(格表示)
波             NOUN  ═════════════════════════╝═╗ ROOT(親)
。             PUNCT <══════════════════════════╝ punct(句読点)

```

……「青いそら」と「ぎらぎらひかる」はバーターなの？

# で、速度の方は？

手元にあった数10ページの文章を文章分割→個別に構文解析させてみた結果。
文章量としては600センテンス。
マシンは結構なつよつよサーバなので、そこはさておき。

| tool      |       time |  
|:-----------------|:------------------:|
| jagger + jdepp              |              0.174747 |
| fugashi + jdepp              |              0.311111 |
| sudachi + spacy              |              4.045916 |

[C\+\+ 高速日本語係り受け解析 J\.DepP\(ジョニーデップ\)の Python binding のメモ](https://zenn.dev/syoyo/articles/d642867aae9aee)
にも記載のあるとおり、jdepp様爆速でございました。
形態素解析部分だけ差し替えが効くのも嬉しいので、使っていきたいですね。

とはいえ、フォーマットする部分でちょっと手がかかるので、
当初の、ちょっとだけさくっと使いたい、であれば sudachi + spacy がインストール手順も少なく、コードもすっきり書けるのでよいと思います。
マルチリンガルで使い方の情報量も多そうだし、文法の正確性も高そう。

あとはどのくらいの文法情報が取り出せるか、というところですが、一旦ここまで。