タイ語処理OSSに4発プルリクを出した初コントリビューション体験記①; 形態素解析

## はじめに
タイ語が読めるので、インターン先で社内唯一のタイ語NLPデータサイエンサーとして登用されている者です。

プログラミングは9年以上やっているのですが、初めてOSSにコントリビューションできて嬉しくなったので、その手順と、そのOSSの管理手法について記録を残します。

## `pythainlp`とは？

タイ語の自然言語処理を行うにあたって恐らく全人類が使うであろう大御所ライブラリ [pythainlp](https://github.com/PyThaiNLP/pythainlp) があります。

これには、
* 形態素解析
* 文章正規化
* ローマ字化
* スペルチェック
* コーパス提供
* ...


など、タイ語の処理に関する森羅万象の機能が搭載されています。

管理は、[backさん](https://github.com/bact)と[wannaphongさん](https://github.com/wannaphong)の2名が積極的に行っております。

## プルリク1; 形態素解析の改善
:::note
このプルリクのお話です：https://github.com/PyThaiNLP/pythainlp/pull/856
:::

人生の初めてのプルリクは、一行の修正でした。

### 問題点
僕は深層学習の潜在空間を扱うより、原始的なWordVectorを扱う方が好きなので、かなり頻繁に形態素解析（後述）の機能を利用しています。
そこで、あまり嬉しくない挙動を見つけました。

>ถ้าไม่รังเกียจสีหน้า(รถ)
>もし表現を気にしないなら(車)

が

>ถ้า / ไม่รังเกียจ / สีหน้า / (รถ)
>もし / 気にしない / 表現 / (車)

と、`()`が分解されない。

:::note
**タイ語の形態素解析**
タイ語は、英語のように単語ごとで空白で区切らず、単語を全て繋げて記述します。（韓国語と同じ）

例えば、

>เชื่อมต่อทุกคำและเขียน
>全て繋げて記述します

で一つの文章になっていて、
単語ごとに区切ると

>เชื่อมต่อ / ทุก / คำ / และ / เขียน
>繋げる / 全ての / 単語 / そして(and) / 書く

となります。語彙力のない初級者には読解が難しいですね。
このように、「 **単語ごとに区切る** 」ことを **形態素解析** といいます。
学校の国語でやらされた **品詞分解** というとイメージがつくかもしれません。

ちなみに、タイ語に「熟語」という概念があり、
>รถ(車) + ไฟ(電気) -> รถไฟ(電車)

のように、2つの名詞をくっつけて新しい意味の名詞が大量にあります。なので、単純に見つけた単語から単純に切っていけばいいというわけではありません。
ちなみのちなみに、タイ語は後置修飾の言語（`green apple`ではなく`apple green`）なので、「車」の後ろに「電気」がつくわけです。

タイ語の形態素解析を行うアルゴリズムには、まあ色々ありますが、現在標準的なのはMaximal Matchingが使われています。（細かくいうとThai Cluster Maximal Matching）
かなり簡単に単純に言うと「単語リストと照らし合わせて、なるべく最も長い単語を優先してマッチングしていく」、というものです。熟語の問題もクリアしているわけですね。
:::

### 改善手順
まずは最初に、[issue](https://github.com/PyThaiNLP/pythainlp/issues/855)を立てました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/5d07df8e-2807-2d9e-c633-35859eb327d5.png)

形態素解析モジュールに
```python
# match non-Thai tokens
_PAT_NONTHAI = re.compile(
    r"""(?x)
[-a-zA-Z]+|        # Latin characters
\d+([,\.]\d+)*|    # numbers
[ \t]+|            # spaces
\r?\n              # newlines
"""
)
```
このような正規表現が定数として使われていましたが、「タイ語じゃないもの」をどうして「タイ語以外」と指定せずに、具体的に列挙してるんだ？こうしているから漏れが出たのでは？
という疑問です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/79bec623-ee6c-42d8-f58d-614fbdbebef4.png)
みなさんもちょっと違和感を抱いている模様。

なので、
* レポジトリをフォークして、
* 修正して、
* プルリクエストを提出

しました。

最初の提案
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/e14c1518-cb35-843a-9aca-af7cd060afbe.png)

タイ文字の文字コードが`\u0E00-\u0E7F`なので、「それ以外」という意味で`[^\u0E00-\u0E7F]+`ではないかと。

すると×印が。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/084b5f7f-2c6b-6038-de17-e60b72dc258e.png)

テストコードとlint(コード規約チェック）が通らなかったとのこと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/a5b69dcf-830e-e629-7f88-141d7c1f861b.png)

当時はGitHub Actionsに触れたことがなかったので、世の中にこんな便利な機能があるんだと震撼し、次の日から自分で使い始めました。

しばらくして皆さまが情報整備を行いました。歯車を動かした感じがしてドキドキ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/05a890ae-6b25-e0a3-7609-036114b829c0.png)

テストコードを確認していると、そもそもの仕様変更なので矛盾が生じていた模様。管理者がどう受け止めるかをお伺いすると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/29c94c17-c904-4a4f-d2e5-ffcba190cabf.png)
`dev`ブランチの更新をpullしてくれと言われたので、pullすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/59a44f80-b2d9-da23-e045-f06cdfe84c90.png)
対象の関数のデフォルト形態素解析器をすり替えた模様。それアリなんだ。

pullしたものの、まだバツ印が。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/7d3c4db4-22a0-272e-d774-676a117aed69.png)

linterとテストコードがはじかれている模様。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/c4edc644-2119-4d4e-eccb-4ed95eff3dd3.png)


linterの結果報告を見ても、自分の担当箇所のコード規約は大丈夫だという（逆に言うと他の箇所は規約違反のまま残っているんですね）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/3d2dcf85-2385-e59f-7b1f-9836aafb044f.png)

同様に、テストコードも自分の担当箇所以外がひっかかっているようでした。
問い合わせてみると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/6ff70762-3df9-3791-f188-b822e2241277.png)

テスト環境の整備が間に合っていないとのこと。かわいそうに。

そこで、こちらはこちらで良からぬ挙動が。`12:12pm `と空白1つがひっついちゃっているという。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/199fc82a-8c42-4867-5964-82601627caaf.png)
そしてもう直したとのこと。レポジトリ管理者はフォークレポジトリに直接pushできるんですね。

自分がコメントを付け足して、最終的にはこうなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602537/cd251a59-94ca-e234-6dff-4b3d5ae03e59.png)
要するに、`|`で先にパターンを書いておくことにより、「非タイ文字」を **ごっそり** 区切ってしまうのではなく、非タイ文字の中でも、「アルファベット」「数字」などをさらに区切ることができる、早期returnのような使い方をしています。

`_PAT_NONTHAI`の変数名に対してはミスマッチしているような気もしますが、勉強になりますね。

## 最後に
一つの記事にまとめようと思いましたが、縦長になってきたので分割します。（分割記事はあんま伸びないんだよなあ...）

明日ぐらいに、2発目のプルリクについてのお話を書きます。

:::note warn
次回: [タイ語処理OSSに4発プルリクを出した初コントリビューション体験記②; 文章正規化](https://qiita.com/konbraphat51/items/06112ef387cd69d63608)
:::

:::note
いいね頂けると泣きながら喜びます><

この記事を読んでいる方は、これらの記事も読んでいるのかもしれません:
* [とりあえず周りと仕事ができるGitHub入門](https://qiita.com/konbraphat51/items/8d4907bf02cfff518462)
* [【Python】発音で検索できるタイ語辞書アプリを作った](https://qiita.com/konbraphat51/items/ed11678d10cc5aeea9f9)
:::