静かなる名辞

pythonとプログラミングのこと



【python】nltkで英語のStemmingとLemmatization

 Stemming(ステミング)は単語の語幹を取り出したいとき、Lemmatization(レンマ化、敢えてカタカナ表記するとレンマタイゼーション)はカテゴリごとにグルーピングしたりしたいときに使う。

 公式ドキュメントはここ。
nltk.stem package — NLTK 3.2.5 documentation

 目次

Stemming

概要

 nltkでStemmingに使えるクラスはたくさんある(ように見える)。nltk.stemに実装されているものだけでも、

  • ARLSTem Arabic Stemmer*1

 アラビア語

  • ISRI Arabic Stemmer*2

 アラビア語

  • Lancaster Stemmer*3

 英語用。古い(1990年)

  • Porter Stemmer*4

 英語用。古い(1980年) 

 正規表現で実装されている。どんな正規表現にするかは使うときに指定する

  • RSLP Stemmer

 ポルトガル語

  • Snowball Stemmers

 なんか色々な言語に対応している

 これだけある。注釈はぜんぶドキュメントからコピペした出典です。

 とりあえず英語で何も考えずに使えるのは、Lancaster、Porter、Snowball Stemmersの3択。LancasterとPorterは基本的にルールベース、Snowball StemmersはPorter StemmerのPorterさんの作ったフレームワークで、多言語対応していることが特徴なのだけど肝心の英語版のアルゴリズムはPorterらしいので(ドキュメントにそう書いてある)、選択肢から外すと、けっきょくLancaster、Porterの2択になる。

 なお、Stemmingの研究がどこまで進んでいるかはこのドキュメントを見て勉強した。
http://kenbenoit.net/assets/courses/tcd2014qta/readings/Jivani_ijcta2011020632.pdf
 統計的言語処理に走って、けっきょくこれは難しいタスクというか、一単語ずつ見てもどうしようもないよね、という壁にぶち当たった・・・って感じか。今はどんな感じになってるんだろうか(というかstemming自体、今はどこまで重要視されているのだろうか)。

 nltk.stemで使えるアルゴリズムはそういった難しい問題が立ちはだかる以前のものなので、とりあえず気楽に使えると言えば使えそう。n-gramとかHMMとかを応用したStemmerも提案はされてるっぽいのだが、nltkで使えなさそうなので没。

Porterを使う

 とりあえずPorterから先に使ってみる。

>>> from nltk.stem.porter import PorterStemmer as PS
>>> ps = PS()
>>> ps.stem("dogs")
'dog'
>>> ps.stem("cats")
'cat'
>>> ps.stem("ceiling")
'ceil'

 これだけ。最後のceilingは「eg. ceil- is not the stem of ceiling」という例なので、解析がいまいち上手く行っていないということになる(この例はnltk.stemの公式ドキュメントの最後に出ている)。

Lancasterを使ってみる

>>> from nltk.stem.lancaster import LancasterStemmer as LS
>>> ls = LS()
>>> ls.stem("dogs")
'dog'
>>> ls.stem("cats")
'cat'
>>> ls.stem("ceiling")
'ceil'

 まったく同様に使えるようだ。

Lemmatizing

 nltk.stemのページの一番最後にしれっと載ってる。wordNetを使って単語をカテゴリに落としてくれる。一体どんな粒度でやってくれるのかよくわからないのだが、公式の例を見るとstemmingと同様に機能している気がする。

 なお、初回呼び出し時は親切なことに、

LookupError: 
**********************************************************************
  Resource wordnet not found.
  Please use the NLTK Downloader to obtain the resource:

  >>> import nltk
  >>> nltk.download('wordnet')

 こんなエラーを吐いてくれる。downloadすると使えるようになる。

>>> from nltk.stem.wordnet import WordNetLemmatizer as WNL
>>> wnl = WNL()
>>> wnl.lemmatize("dogs")
'dog'
>>> wnl.lemmatize("cats")
'cat'
>>> wnl.lemmatize("ceiling")
'ceiling'

 Lemmatizer特有のメリットとしては、

>>> wnl.lemmatize("good", pos="a")
'good'
>>> wnl.lemmatize("better", pos="a")
'good'

 こういうことができる。この例はpos="a"(adjective)を指定しないと名詞として取り扱われて上手く行かないみたいだけど。このposキーワードについては、そもそもPOSタグ判定が成功しないと上手く使えない上、POS taggerの吐くPOSタグと指定できるPOSタグに互換性がなさそうなので、使い方はかなり難しそう。無理を重ねている感じ。

 なお、公式曰く、「Returns the input word unchanged if it cannot be found in WordNet.」とのこと。つまり「元々語幹の形で入力されたのでchangeする必要がなかった」のか「見つからないからとりあえずそのまま返した」だけなのか区別がつかない。ふざけんなと言いたい。せめて検索して見つかったかどうかくらいはT/Fでも1/0でも良いから返してくれ。

 ま、そういうことになっているので、実際どう使うべきなのかは率直に言って悩む。

結論

 研究者・開発者の皆さんには申し訳ないけど、どれもどんぐりの背比べ感が否めない・・・。

*1:K. Abainia, S. Ouamour and H. Sayoud, A Novel Robust Arabic Light Stemmer , Journal of Experimental & Theoretical Artificial Intelligence (JETAI‘17), Vol. 29, No. 3, 2017, pp. 557-573.

*2:Taghva, K., Elkoury, R., and Coombs, J. 2005. Arabic Stemming without a root dictionary. Information Science Research Institute. University of Nevada, Las Vegas, USA.

*3:Paice, Chris D. “Another Stemmer.” ACM SIGIR Forum 24.3 (1990): 56-61.

*4:Porter, M. “An algorithm for suffix stripping.” Program 14.3 (1980): 130-137.