Rubyではじめる形態素解析


形態素解析とは

文章を品詞などを参考に、最小単位に分解することです。
実際の形態素解析結果を見るとイメージがしやすいかもしれません。

これはQiitaの記事です
これ は Qiita の 記事 です

今回はMecabを使用して、形態素解析を行います。

実際にやってみる

  • 筆者の環境はmac OS Catalinaです。

Mecabインストール

Homebrewを使用して、Mecab本体をインストールします。

$ brew install mecab

$ brew list | grep mecab
mecab

次に、mecab-ipadicをインストールします。
形態素解析では、辞書を元に文章を分解します。

$ brew install mecab-ipadic

$ brew list | grep mecab
mecab
mecab-ipadic

これで準備は完了したので、実際に形態素解析を行ってみます。
わかち書き(語の間に空白)で出力してみます。

$ echo 'これはQiitaの記事です' | mecab -O wakati -d /usr/local/lib/mecab/dic/ipadic
これ は Qiita の 記事 です

「これ」 「は」 「Qiita」 「の」 「記事」 「です」に分解されていることが分かります。
また、-dオプションで先ほどインストールしたipadicをシステム辞書として参照しています。

辞書はシステム辞書とユーザ辞書が存在し、並列で使用することも多いと思います。
独自の辞書(単語)をユーザ辞書(or システム辞書)に登録すると、その辞書を元に形態素解析することが可能です。

nattoインストール

rubyのバージョンは以下を使用しました。

$ ruby -v
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin19]

mecabとnattoのGemをインストールします。

$ gem install mecab natto

$ gem list | grep -E '^(mecab|natto)'
mecab (0.996)
natto (1.2.0)

irbで動作を確認してみます。

$ irb
(ins)>> require 'natto'
=> true
(ins)>> nm = Natto::MeCab.new
=> #<Natto::MeCab:0x00007fb8b1850c20 @model=#<FFI::Pointer address=0x00007fb8b3a09f40>, @tagger=#<FFI::Pointer address=0x00007fb8b38abce0>, @lattice=#<FF...
(ins)>> nm.parse('これはQiitaの記事です')
=> "これ\t名詞,代名詞,一般,*,*,*,これ,コレ,コレ\nは\t助詞,係助詞,*,*,*,*,は,ハ,ワ\nQiita\t名詞,固有名詞,組織,*,*,*,*\nの\t助詞,連体化,*,*,*,*,の,ノ,ノ\n記事\t名詞,一般,*,*,*,*,記事,キジ,キジ\nです\t助動詞,*,*,*,特殊・デス,基本形,です,デス,デス\nEOS\n"

動作しているようです。

feature, surfaceで出力を切り替えます。

(ins)?> nm.parse('これはQiitaの記事です') do |n|
(ins)?>   puts n.feature
(ins)>> end
名詞,代名詞,一般,*,*,*,これ,コレ,コレ
助詞,係助詞,*,*,*,*,は,ハ,ワ
名詞,固有名詞,組織,*,*,*,*
助詞,連体化,*,*,*,*,の,ノ,ノ
名詞,一般,*,*,*,*,記事,キジ,キジ
助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
BOS/EOS,*,*,*,*,*,*,*,*

(ins)?> nm.parse('これはQiitaの記事です') do |n|
(ins)?>   puts n.surface
(ins)>> end
これ
は
Qiita
の
記事
です

名詞の単語で絞ってみます。

(ins)?> nm.parse('これはQiitaの記事です') do |n|
(ins)?>   feature = n.feature.split(',')
(ins)?>   puts n.surface if feature[0] == '名詞'
(ins)>> end
これ
Qiita
記事

色々と単語を試してみます。
うまく形態素解析されない文章があることに気づきます。
以下は、「スーパー」「ーー」「!」に分解されるとおもいきや、「スーパーーー」が一単語になってしまっています。

(ins)?> nm.parse('スーパーーー!') do |n|
(ins)?>   puts "#{n.surface}: #{n.feature}"
(ins)>> end
スーパーーー: 名詞,一般,*,*,*,*,*
!: 記号,一般,*,*,*,*,!,!,!
: BOS/EOS,*,*,*,*,*,*,*,*

形態素解析の前処理

自然言語処理の前処理で如何に文章を整形するかで結果が変わってきます。
正規化を行うGemをインストールします。
実はつい先日、このGemをリリースしました。良ければ使ってみてください。

$ gem install normalize_text

$ gem list | grep normalize_text
normalize_text (0.1.0)

先ほどの文字列を正規化して、形態素解析をかけてみます。

$ irb
(ins)>> require 'normalize_text'
=> true
(ins)>> 'スーパーーー!'.normalize
=> "スーパー!"

(ins)?> nm.parse('スーパーーー!'.normalize) do |n|
(ins)?>   puts "#{n.surface}: #{n.feature}"
(ins)>> end
スーパー: 名詞,一般,*,*,*,*,スーパー,スーパー,スーパー
!: 名詞,サ変接続,*,*,*,*,*
: BOS/EOS,*,*,*,*,*,*,*,*

想定したとおり、「スーパー」と「!」に分解されています。

まとめ

Rubyを使って、形態素解析を行う手順をまとめてみました。
実例を通して、形態素解析においての前の処理の大切さが伝わると嬉しいです。

また本文に出てきますが、normalize_textをリリースしてみたので、良ければ使ってみてください。
今後は顔文字の除去なども対応できればいいなと思っています。

今回紹介しませんでしたが、mecab-ipadic-neologdを使用した辞書の拡張、ユーザ辞書の生成を活用して、形態素解析できる幅を広げることが可能です。