📖

日本語の活用変形のライブラリ化を試みた

2022/11/20に公開約5,900字

この記事に書くこと

日本語の活用変形ライブラリを実装しました。
https://github.com/sadahry/katsuyo-text

この記事では、以下のことを書きます。

  • ライブラリの概要説明
  • 活用変形のロジック化の困難

モチベーション

対話システム研究の一環で「おうむ返し」を実装しました(詳細は別記事

その実装を進めるなかで活用変形が必要だったのですが、
活用変形は決まったルールなのでロジックに落とし込めるのでは? という仮説が浮かび、
活用変形をロジックに落とし込めるかどうか、ライブラリ化を試みました。

ライブラリの概要説明

このライブラリでは活用形を含む文字列を
KatsuyoText, Katsuyo, FixedKatsuyoText で表現します。

KatsuyoTextには語幹と活用型(=Katsuyo)が含まれ、
その活用変形が FixedKatsuyoText で表現されます。

utsukushii = KatsuyoText(
    gokan='美し',
    katsuyo=KeiyoushiKatsuyo(
        katei='けれ',
	rentai='い',
	shushi='い',
	renyo_ta='かっ',
	renyo='く',
	mizen='かろ'
    )
)

utsukushii.as_fkt_rentai
# => FixedKatsuyoText(gokan='美し', katsuyo='い')

また、活用変形を含まない文字列を INonKatsuyoText というインターフェースで実装し、該当する品詞を大まかに分類します。
(分類粒度は「文法に違いが出るかどうか」を目安にしています)

isinstance(TaigenText("状態"), INonKatsuyoText)
# => True
isinstance(FukushiText("とても"), INonKatsuyoText)
# => True
isinstance(KakujoshiText("が"), INonKatsuyoText)
# => True

そして、文章の作成は KatsuyoTextINonKatsuyoText の足し算で実現します。
FixedKatsuyoTextは補助的クラスであり、直接利用することはあまり想定されていません)

# 形容詞「美しい」 + 接続助詞「ば」
utsukushii + SETSUZOKUJOSHI_BA
# => SetsuzokujoshiText(gokan='美しければ', katsuyo=None)

日本語の活用形は(大まかには)以下のロジックで決定されるため、

  • 「足す単語」に「足される品詞」が適切であるか
  • 「足す単語」のために「足される単語の活用形」をどう変形するか

ライブラリとしては、「足す単語のクラス」に足し算のロジックを含め、足されたあとは「足す単語のクラス」となるよう設計してみました。

上記例では、接続助詞「ば」のクラスに足し算のロジックがあり、
足されたあとは接続助詞「美しければ」としてふるまいます。

かなり説明を省いていますが、これがライブラリの概要説明となります。

活用変形のロジック化の困難

このルールのもと、各品詞についてクラスをそれぞれ実装するだけで
活用変形が扱いやすくなればよかったのですが、当然、そうはいきませんでした。。

困難1.同じ単語で品詞が異なる

例えば否定の意味を持つ「ない」について考えます。

実はこの「ない」は3種類の品詞が存在し、

  • 形容詞「ない」
  • 助動詞「ない」
  • 補助形容詞「ない」

それぞれ紐づく活用形が異なります。

TaigenText("心配") + KEIYOUSHI_NAI
# => KatsuyoText(gokan='心配な', katsuyo=KeiyoushiKatsuyo(katei='けれ', rentai='い', shushi='い', renyo_ta='かっ', renyo='く', mizen='かろ'))
TaigenText("心配") + JODOUSHI_NAI
# => error
TaigenText("心配") + HOJO_NAI
# => KatsuyoText(gokan='心配でな', katsuyo=KeiyoushiKatsuyo(katei='けれ', rentai='い', shushi='い', renyo_ta='かっ', renyo='く', mizen='かろ'))

KatsuyoText("遊", GODAN_BA_GYO) + JODOUSHI_NAI
# => KatsuyoText(gokan='遊ばな', katsuyo=KeiyoushiKatsuyo(katei='けれ', rentai='い', shushi='い', renyo_ta='かっ', renyo='く', mizen='かろ'))
KatsuyoText("遊", GODAN_BA_GYO) + KEIYOUSHI_NAI
# => KatsuyoText(gokan='遊びな', katsuyo=KeiyoushiKatsuyo(katei='けれ', rentai='い', shushi='い', renyo_ta='かっ', renyo='く', mizen='かろ'))
KatsuyoText("遊", GODAN_BA_GYO) + HOJO_NAI
# => KatsuyoText(gokan='遊んでな', katsuyo=KeiyoushiKatsuyo(katei='けれ', rentai='い', shushi='い', renyo_ta='かっ', renyo='く', mizen='かろ'))

足し算で活用変形ができるようになっても、
ただルール化するだけでは考慮すべき要素が多く、複雑な管理が必要となります。

困難2.求めていた言葉遣いにさせる

対話システムとしては語調を整えたいのですが、
例えば上記「ない」において「心配ではない」と出力したい場合、
その実現には、複数の単語を繋ぎ合わせる必要があります。

TaigenText("心配") + KAKUJOSHI_DE + KEIJOSHI_HA + KEIYOUSHI_NAI
# => KatsuyoText(gokan='心配ではな', katsuyo=KeiyoushiKatsuyo(katei='けれ', rentai='い', shushi='い', renyo_ta='かっ', renyo='く', mizen='かろ'))

これらの管理を行いながら実装するのでは、
ただ文字列を管理するよりも複雑な管理が必要となりそうな予感がします。

困難3.活用型によって紐づく単語が異なる

接続助詞「て」について考えてみます。

この助詞は活用形を持ちませんが、動詞の活用形によって「で」に変わります。
(e.g., 昨日は友達の家で遊ん

KatsuyoText("遊", GODAN_BA_GYO) + SETSUZOKUJOSHI_DE
# => SetsuzokujoshiText(gokan='遊んで', katsuyo=None)

KatsuyoText("遊", GODAN_BA_GYO) + SETSUZOKUJOSHI_TE
# => error

ただ活用変形を実装するだけでは、動詞が「て」「で」どちらに振り分けられるかを
意識しながら実装する必要があり、かなり複雑な管理が必要です。

対応1.ロジックをひとまとめにしたHelperの実装

困難1.~3.より、活用変形が実現できたとしても、
ただ実装するだけでは管理がかなり煩雑になることがお分かりいただけたと思います。

これらの解決策として IKatsuyoTextHelper の実装を考えました。

具体的には try_mergebridge の2種類の処理を追加しました。
これらを活用変形とは無関係に実装できるようにすることで、活用変形では吸収しきれない差異を吸収できる余地を加えました。

try_merge

こちらは接続助詞「て」「で」の振り分けのように、
「出力したい単語が複数候補現れるケース」を想定したメソッドです。

基本的には「文法に則った処理」を書く想定で、
文法に当てはまらないケースでは None を返す想定です。

https://github.com/sadahry/katsuyo-text/blob/7099ddc5ad248c0aa0e703ea7ea6788951d23418/katsuyo_text/katsuyo_text_helper.py#L789-L800

こちらにより、柔軟な単語選択が可能になります。

bridge

こちらは否定「ではない」の語調を整えるケースのように、
出力したい文字列を調整するためのオプションを提供します。

https://github.com/sadahry/katsuyo-text/blob/7099ddc5ad248c0aa0e703ea7ea6788951d23418/katsuyo_text/katsuyo_text_helper.py#L207-L223

あくまで「文法表現に当てはまらない」メソッドをコンストラクタに渡す仕組みとしており、
任意のロジックを付与できるようにしています。

(未実装) 品詞選択

語調を整えるうえで「ない」を「ぬ」に変えたいケースがあると思います。
そういった「意味が同じで単語が異なるケース」にもHelperは対応可能だと考えられます。

困難4.活用形が確定しないケースがある

ここまでは活用形が決まっているケースでした。
実は活用形が決まらないケースもあるため、その例を書きます。

体言

体言は一般的に連体形(e.g.,「笑う社長」)で接続されますが、
「笑われ社長」のように省略表現として連体形以外で接続されるケースがあります。

格助詞「に」

格助詞「に」は、連体形の場合(e.g., 「遊ぶ限る」)は状態や到達点を表しますが、
連用形の場合(e.g., 「遊び行く」)は目的を表し、それは「足す単語」では識別困難です。

ref. https://ja.wikipedia.org/wiki/助詞#格助詞

文末接続

「そこのけ車が通る」といった文章が繋がっているケースに
「足す単語」では活用形の識別困難となる場合があります。

対応2.文脈に含まれた活用形を保持する

活用形が確定しないケースは「既存の文章を変換する」場合に障壁となります。
(文生成であれば、格助詞「に」であればHelperの使い分けで対処できる。文末接続は別文章として対応可能。..etc)

よって、既存の文章から変換を行う ISentenceConverter をライブラリとして実装し、
その文章変換のなかで活用形を保持して変換する仕組みを導入しました。

けっこう泥臭くやっているので具体的な説明は省略しますが、
ざっと以下の流れで実装しています。

  • 変換したい単語を KatsuyoText/INonKatsuyoText として抽出
  • 変換したい単語の前単語を KatsuyoText/INonKatsuyoText 化して足す
  • 変換したい単語以降の単語もライブラリ内で活用形を判断できれば KatsuyoText/INonKatsuyoText 化して足す
  • ライブラリ内で活用形を判断できない品詞/単語が出たら、変換前の活用形で文字列化

詳細を知りたい場合はこちらの実装を確認してみてください。

雑感

端的に書くと難しくはないように思えますが、活用形は全部調べて書く必要があったり、活用形が明らかではない単語があったり(終助詞「か」..etc)、細かな決断を各単語へひとつひとつ適用する必要があるため、ロジックは複雑です。

また稀に、形態素解析の仕様と折り合いをつけなければいけないケースがあり、テストケースはそれぞれの活用形に対してひとつひとつ書いていく必要があり大変でした。
(完全には辞書対応していない方言、微妙に正規形のルールが異なる単語「たって」「だって」..etc)

あとは、文法のみで意味を抽出することは困難であるため、意味抽出の用途にこのライブラリは使えないなと思っています。このライブラリが、どういった用途だと活用が広がるのか見えづらい。
(自分としては文中の「です」「ます」を抜くために活用形が必要だったので実装したまで)

理想形は「単語の足し算引き算だけで文章が成立する」でしたが、↓みたく、単語の連なりから文章を補完する深層学習のほうが適切かもしれない、と思ったりもしています。。
(論文はまだ読めてないです)
https://twitter.com/paperswithcode/status/1592546933679476736

最後に

もし活用法、改善点あれば気軽にissueいただけると幸いです。
https://github.com/sadahry/katsuyo-text/issues

それでは。

Discussion

ログインするとコメントできます