形態素解析前の日本語文書の前処理 (Python)

日本語の文書を扱っていますと、モデルやパラメータよりも、前処理を改善する方が精度が改善し、かつ、頑健になることがしばしばあります。

本投稿では形態素解析 (分かち書き) する前、つまり文字レベルでの前処理でよく使っているテクニックを紹介します。

お題

少し極端な例ですが、題材として架空のレビュー文を使います。

お友達の紹介で、女子2人で三時のティータイムに利用しました。
2人用のソファに並んでいただきま〜す v(^^)v なかよし(笑)
最後に出された,モンブランのケーキ。
やばっっっ!!これはうまーーーい!!
とってもDeliciousで、サービスもGoodでした😀
これで2,500円はとってもお得です☆
http://hogehoge.nantoka.blog/example/link.html

前処理のポイントがいくつかありますね。いずれも、どちらかに統一したり除外したりするほうが、意味的な分析を行うタスクには有効です。
ただし、感情分析を行いたい場合は、4つ目以下はそのまま使った方が良いかもしれません。その場合でも、辞書とマッチしやすいように加工しておくべきです。

  • 全角と半角の混在 ("2"と"2", "Delicious"と"Good", "モンブラン"と"ケーキ")
  • URLを含んでいる
  • 桁区切りの"," ("3,000")
    • このまま形態素解析にかけると、"3"と"000"に分離してしまいます
  • 文字を重ねた感情表現 ("やばっっっ!!" と "うまーーーい!!")
  • 顔文字などの感情表現テキスト ("v(^^)v" と "(笑)" )
  • 絵文字 ("😀")

全角・半角の統一と重ね表現の除去 (neologdn)

全角・半角の統一 (大括りに言えばUnicode正規化) と重ね表現の除去には、neologdnを使えます。
neologdの正規化処理を、Cythonで実装されたライブラリで、Pythonからもpip install neologdnで楽に導入できます。

github.com

使い方はシンプルで、normalizeメソッドに文字列を渡して呼び出すだけでOKです。

import neologdn

normalized_text = neologdn.normalize(text)
お友達の紹介で、女子2人で三時のティータイムに利用しました。
2人用のソファに並んでいただきますv(^^)vなかよし(笑)
最後に出された,モンブランのケーキ。
やばっっっ!!これはうまーい!!
とってもDeliciousで、サービスもGoodでした😀
これで2,500円はとってもお得です☆
http://hogehoge.nantoka.blog/example/link.html
  • アルファベットやアラビア数字、括弧やエクスクラメーションマークなどの記号は、半角に統一
  • カタカナは、全角に統一
  • "うまーーーい!!" → "うまーい!" など、重ね表現を除去
    • 一方で、"やばっっっ!!" は除去できてません
    • repeat引数に1を渡すと、2文字以上の重ね表現は1文字にできますが、そうすると"Good"は"God"になってしまったりします
  • ”〜”などの記号も除去されてます

URLの除去

正規表現でURLを除去します。

import re

text_without_url = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-]+', '', normalized_text)
お友達の紹介で、女子2人で三時のティータイムに利用しました。
2人用のソファに並んでいただきますv(^^)vなかよし(笑)
最後に出された,モンブランのケーキ。
やばっっっ!!これはうまーい!!
とってもDeliciousで、サービスもGoodでした😀
これで2,500円はとってもお得です☆

絵文字の除去 (emoji)

絵文字の判定にはそのものズバリの emoji というライブラリがあります。

github.com

絵文字のリストはemoji.UNICODE_EMOJIにありますので、それに含まれる文字は除外するようにします。

import emoji

text_without_emoji = ''.join(['' if c in emoji.UNICODE_EMOJI else c for c in text_without_url])
お友達の紹介で、女子2人で三時のティータイムに利用しました。
2人用のソファに並んでいただきますv(^^)vなかよし(笑)
最後に出された,モンブランのケーキ。
やばっっっ!!これはうまーい!!
とってもDeliciousで、サービスもGoodでした
これで2,500円はとってもお得です☆

桁区切りの除去と数字の置換

桁区切り (ついでに小数点) の数字を除去します。
あわせて、数字も全て0に置き換えてしまいます。意味的な分析ででは、数字の具体的な値を使えないことが多く、いたずらにボキャブラリを増やすだけで、後のタスクでは役に立たないことが多いためです。

import re

tmp = re.sub(r'(\d)([,.])(\d+)', r'\1\3', text_without_emoji)
text_replaced_number = re.sub(r'\d+', '0', tmp)
お友達の紹介で、女子0人で三時のティータイムに利用しました。
0人用のソファに並んでいただきますv(^^)vなかよし(笑)
最後に出された,モンブランのケーキ。
やばっっっ!!これはうまーい!!
とってもDeliciousで、サービスもGoodでした
これで0円はとってもお得です☆

記号の置き換え

記号によって割り当てられているブロックが異なりますので、タスクの性質を見極めながら適宜追加していく必要があります。ブロックはこちらの記事で整理されています。

Unicode 内のそれぞれの文字種の範囲 - みちのぶのねぐら 工作室 旧館

記号は半角スペースに置き換えてます。文中では区切り文字として使われていることが多く、完全に除去してしまうと、文や単語の区切りがわからなくなってしまうことがあるためです。

# 半角記号の置換
tmp = re.sub(r'[!-/:-@[-`{-~]', r' ', text_replaced_number)

# 全角記号の置換 (ここでは0x25A0 - 0x266Fのブロックのみを除去)
text_removed_symbol = re.sub(u'[■-♯]', ' ', tmp)

"("や"!", "☆"などが除去されていることを確認できます。

お友達の紹介で、女子0人で三時のティータイムに利用しました。
0人用のソファに並んでいただきますv    vなかよし 笑 
最後に出された モンブランのケーキ。
やばっっっ  これはうまーい  
とってもDeliciousで、サービスもGoodでした
これで0円はとってもお得です

まだ気になる点はいくつかありますが、ひとまず形態素解析にかけて様子を見ながら、ルールの追加・変更を行っていけばよいかと思います。

まとめ

自分がよく使う形態素解析前の前処理についてまとめました。