ja_sentence_segmenterで文章を句点区切りする（Python）

## はじめに
自然言語処理などで文章を句点（「。」や「．」）区切りにしたい場合に、とても有用な```ja_sentence_segmenter```による句点区切りを備忘録として示します。
ja_sentence_segmenterの詳しい解説や使い方ついては、以下を参照してください。本備忘録もこちらの記事を参考にして作成したものになります。作者のみなさまにたいへん感謝いたします。

https://qiita.com/wwwcojp/items/3535985007aa4269009c

https://github.com/wwwcojp/ja_sentence_segmenter

https://zenn.dev/hellorusk/articles/066259a00e9f69


## 句点区切りで目指したいこと
1. 一般的な文章では、だいたい「。」で終わるので、「。」で1文1文を区切りたい
2. まれに「。」でなく「．」を使った文章もあるので、その場合は「．」で1文1文を区切りたい
3. しかし、数字や項番号（「42.195km」や「1.はじめに」など）で使われる「.」では区切らずそのままにしたい

## 調べてみた
### １） 日本語の文章をいい感じに文区切りするライブラリを見つけました

https://qiita.com/wwwcojp/items/3535985007aa4269009c

こちらの記事では、「」や（）内に句点や感嘆符がある ＆ 文の途中で改行されているものも区切らずそのままにして、「。」区切りをすることができます。以下が示されているコードになります。
```py
# Code from https://qiita.com/wwwcojp/items/3535985007aa4269009c
import functools

from ja_sentence_segmenter.common.pipeline import make_pipeline
from ja_sentence_segmenter.concatenate.simple_concatenator import concatenate_matching
from ja_sentence_segmenter.normalize.neologd_normalizer import normalize
from ja_sentence_segmenter.split.simple_splitter import split_newline, split_punctuation

split_punc2 = functools.partial(split_punctuation, punctuations=r"。!?")
concat_tail_te = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(て)$", remove_former_matched=False)
segmenter = make_pipeline(normalize, split_newline, concat_tail_te, split_punc2)

text1 = """
私は「あなたの思いに答えられない。他を当たってほしい。」と言われました！呆然として
その場にたたずむしかありませんでしたそれでも私は信じたい！
"""
print(list(segmenter(text1)))
```
:::note info
この方法で、「。」「!」「?」で文章を区切ることができます
:::

### ２） 句読点のピリオドと小数点のピリオドを区別する応用を見つけました

https://zenn.dev/hellorusk/articles/066259a00e9f69

また、こちらの記事ではおなじ```ja_sentence_segmenter```を使って文を区切るときに、「句読点のピリオドと小数点のピリオドを区別する」ことができます。以下が示されているコードになります。
```py
# Code from https://zenn.dev/hellorusk/articles/066259a00e9f69
import functools
 
from ja_sentence_segmenter.common.pipeline import make_pipeline
from ja_sentence_segmenter.concatenate.simple_concatenator import  concatenate_matching
from ja_sentence_segmenter.normalize.neologd_normalizer import  normalize
from ja_sentence_segmenter.split.simple_splitter import  split_newline, split_punctuation
 
split_punc2 = functools.partial(split_punctuation, punctuations=r".。 !?")
concat_tail_no = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(の)$", remove_former_matched=False)
concat_decimal = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(\d.)$", latter_matching_rule=r"^(\d)(?P<result>.+)$", remove_former_matched=False, remove_latter_matched=False)
segmenter = make_pipeline(normalize, split_newline, concat_tail_no, split_punc2, concat_decimal)
```
:::note info
この方法で、句読点のピリオドと小数点のピリオドを区別しながら、「。」「!」「?」で文章を区切ることができます。
:::

## 目指したいことを実現してみた
上記の２つではいずれも、```segmenter```にわたすものが調整されています。それぞれを合体させて以下のようなコードを使って、句点区切りすることを考えました
```py
split_punc2 = functools.partial(split_punctuation, punctuations=r".。!?")
concat_tail_no = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(の)$", remove_former_matched=False)
concat_tail_te = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(て)$", remove_former_matched=False)
concat_decimal = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(\d.)$", latter_matching_rule=r"^(\d)(?P<result>.+)$", remove_former_matched=False, remove_latter_matched=False)
segmenter = make_pipeline(normalize, split_newline, concat_tail_no, concat_tail_te, split_punc2, concat_decimal)
```

## 検証
### 句点区切りの動作
「。」と「、」が含まれるダミーの文章と、それを「．」と「，」に置換した文章で動作を見てみます。3文に区切ることができれば問題なさそうです。
```bash
# ダミーの文章（「人間失格」 青空文庫）
恥の多い生涯を送って来ました。自分には、人間の生活というものが、見当つかないのです。自分は東北の田舎に生れましたので、汽車をはじめて見たのは、よほど大きくなってからでした。
# 「．」と「，」に置換した文章
恥の多い生涯を送って来ました．自分には，人間の生活というものが，見当つかないのです．自分は東北の田舎に生れましたので，汽車をはじめて見たのは，よほど大きくなってからでした．
```
```bash
# 実行結果1（ダミー文章）
1文目：恥の多い生涯を送って来ました。
2文目：自分には、人間の生活というものが、見当つかないのです。
3文目：自分は東北の田舎に生れましたので、汽車をはじめて見たのは、よほど大きくなってからでした。

# 実行結果2（置換した文章）
1文目：恥の多い生涯を送って来ました.
2文目：自分には,人間の生活というものが,見当つかないのです.
3文目：自分は東北の田舎に生れましたので,汽車をはじめて見たのは,よほど大きくなってからでした.
```

### 小数点のピリオドの動作
「小数点」と「桁区切り」が含まれるダミーの文章と、それを「．」と「，」に置換した文章で動作を見てみます。2文に区切ることができれば問題なさそうです。
```bash
# ダミーの文章（適当に私が作文しました）
マラソンは42.195kmをひとりで走ります。記録会などマラソンのレースの参加費は、無料のところもあれば3,000円するときもあります。
# 「．」と「，」に置換した文章
マラソンは42.195kmをひとりで走ります．記録会などマラソンのレースの参加費は，無料のところもあれば3,000円するときもあります．
```
```bash
# 実行結果3（ダミー文章）
1文目：マラソンは42.195kmをひとりで走ります。
2文目：記録会などマラソンのレースの参加費は、無料のところもあれば3,000円するときもあります。

# 実行結果4（置換した文章）
1文目：マラソンは42.195kmをひとりで走ります.
2文目：記録会などマラソンのレースの参加費は,無料のところもあれば3,000円するときもあります.
```
:::note info
それぞれの検証の結果、実現したいことにとって問題なさそうなことが確認できました。いい感じに区切ることができていそうです。
:::

## コード
### コマンドラインのPythonで実行する場合
:::note warn
以下のコードを実行する前に ja_sentence_segmenter を pip でインストールしてください
:::
```bash
pip install ja_sentence_segmenter
```

以下のコードを ```py```ファイルに保存して実行してください
```py
# ja_sentence_segmenter など必要なライブラリのインポート
import functools
from ja_sentence_segmenter.common.pipeline import make_pipeline
from ja_sentence_segmenter.concatenate.simple_concatenator import concatenate_matching
from ja_sentence_segmenter.normalize.neologd_normalizer import normalize
from ja_sentence_segmenter.split.simple_splitter import split_newline, split_punctuation

# segmenter の定義
split_punc2 = functools.partial(split_punctuation, punctuations=r".。!?")
concat_tail_no = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(の)$", remove_former_matched=False)
concat_tail_te = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(て)$", remove_former_matched=False)
concat_decimal = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(\d.)$", latter_matching_rule=r"^(\d)(?P<result>.+)$", remove_former_matched=False, remove_latter_matched=False)
segmenter = make_pipeline(normalize, split_newline, concat_tail_no, concat_tail_te, split_punc2, concat_decimal)

# 句点区切りしたい文章
content = "マラソンは42.195kmをひとりで走ります．記録会などマラソンのレースの参加費は，無料のところもあれば3,000円するときもあります．"

# 句点区切りを実行
parse = list(segmenter(content))

# 句点区切りした文の確認
count = 1
for sentence in parse:
  print(str(count) + "文目：" + sentence)
  count+=1
```

### Google Colaboratoryで実行する場合
Google Colaboratoryを利用する場合、以下をそれぞれコピペして実行してみてください。

1\. ```ja_sentence_segmenter``` を```pip``` インストールしておきます
```bash 
!pip install ja_sentence_segmenter
```

2\. ```pip``` インストールが終わったら、以下を実行してライブラリをインポートします
```py
# ja_sentence_segmenter など必要なライブラリのインポート
import functools
from ja_sentence_segmenter.common.pipeline import make_pipeline
from ja_sentence_segmenter.concatenate.simple_concatenator import concatenate_matching
from ja_sentence_segmenter.normalize.neologd_normalizer import normalize
from ja_sentence_segmenter.split.simple_splitter import split_newline, split_punctuation
```

3\. ```ja_sentence_segmenter```により句点区切りするために ```segmenter``` を定義します
```py
# segmenter の定義
split_punc2 = functools.partial(split_punctuation, punctuations=r".。!?")
concat_tail_no = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(の)$", remove_former_matched=False)
concat_tail_te = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(て)$", remove_former_matched=False)
concat_decimal = functools.partial(concatenate_matching, former_matching_rule=r"^(?P<result>.+)(\d.)$", latter_matching_rule=r"^(\d)(?P<result>.+)$", remove_former_matched=False, remove_latter_matched=False)
segmenter = make_pipeline(normalize, split_newline, concat_tail_no, concat_tail_te, split_punc2, concat_decimal)
```

4\. ```content``` に句点区切りしたい文章を入力してから実行します。実行すると区切られた文章を確認できます。
```py
# 句点区切りしたい文章
content = "マラソンは42.195kmをひとりで走ります．記録会などマラソンのレースの参加費は，無料のところもあれば3,000円するときもあります．"

# 句点区切りを実行
parse = list(segmenter(content))

# 句点区切りした文の確認
count = 1
for sentence in parse:
  print(str(count) + "文目：" + sentence)
  count+=1
```

## おわりに
```ja_sentence_segmenter```はいい感じに日本語を区切ることができるとても便利なライブラリです。それぞれの記事の方に大変感謝いたします。