自然言語（前）処理

この記事は，NTTドコモSI部アドベントカレンダー5日目の記事になります。

新入社員の澤山です。業務では自然言語処理に関わる内容に取り組んでいます。
5日目のこの記事では，自然言語処理の基本であるテキストデータの前処理について紹介します。

## なぜ，前処理を行うのか？

私たちは日々，様々な文字や言葉に囲まれています。話し言葉，書き言葉，ネットスラング，外国語，プログラム言語，多種多様の記号といったものです。人間は，これらが混ざったテキストデータをある程度理解できますが，機械学習などでは，それらの分類や予測・理解が容易ではありません。
そのため，いかに分類モデルにこれらを理解しやすくさせるか（さらには，人間が理解・処理しやすくするか）が，前処理が必要な理由であり，前処理を行う理由だと考えています。
それでは，前処理をおこなうことのメリットを順番に見ていきしょう。

- データ利用の容易性の向上
  - データ形式を揃えることは，データを利用する上で間違いなくいい影響を与えてくれます。
    - バグの低減
    - 後段処理の簡素化/高速化
    - チーム間でのデータ利用の簡易化
    - 分類モデルを構築する（コードを書く）際の問題の切り分け(データ側 or 分類モデル側）


* データの本質の発見
  - 前処理をおこなうことで，本当に必要なデータを見つけ出すことができたり，データへの理解を深めることができます。
    - その単語，本当に未知語ですか？（半角・全角などの表記ゆれの違い等）
    - 分類モデルに着目させる部分を勘違いをさせない。
    - 単語の意味表現学習をよりよくする。


* 副産物による恩恵
  - 前処理は，データ形式を揃えるだけでなく，「どんな文字や単語がよく出現・置換されたか」，「何が不要だったか」といった追加情報や，別のタスクでも扱うことのできる副産物を生み出してくれる場合があります。
    - 後段の機械学習モデルの追加素性・類義語，言い換え辞書など。
    - 他のデータへの転用ができる。


* 精度改善への貢献
  - ご存知の通り，日本語は英語などに比べて文字種が多いです（ひらがな+カタカナ+漢字+記号等）。加えて，単語分割（単語の定義）が必要になります。
  - つまり，適切な「解析単位」と「単語の意味のエンコード」が必要です。
  - これによって，モデルチューニングよりも簡単にモデルの精度改善につがなる場合もあります。


このように，前処理は，後段処理に大きく影響を与えることがわかったかと思います。それでは，機械学習などを用いた自然言語処理において，一般的な前処理である，コーパスクリーニング → 単語分割 → ベクトル化（モデル入力前まで）のうち，今回は「コーパスクリーニング」と「単語分割」に着目してみます。

## 解析に影響しそうなところはどこ？

次のテキストのうち，前処理を行わないことで，後段処理に影響しそうな部分はどこでしょうか？

* 「`<p>`そういえば、昼に椎名林檎さんとﾘﾝｺﾞジュースを飲んだけど，夜にもりんごジュースをのんだよおお😍 `</p>`」
  - 不要なhtmlタグの混入
  - 人名の分割単位（「椎名林檎」，「椎名/林檎」）
  - 異なる句読点の混入(「、」と「，」)
  - 表記ゆれ(「ﾘﾝｺﾞ」と「りんご」，「飲んだ」と「のんだ」)
  - 崩れ表記（のんだよおお）
  - 文末の無駄なスペースの挿入（😍と`</p>`の間の半角スペース）
  - （絵文字処理）

このように，様々な要因がノイズとなって，後段処理に影響を与えることになります。人間であれば，前処理なしの文でも必要な情報を取捨選択し，意味を理解することができます。しかし，機械学習では，これら複数の（余分な）要素を同時に考慮しながら分類することが難しいです。

## コーパスクリーニング：　チェックリスト(仮)
このようなテキストに対し，自然言語処理に取り組む人たちは以下の様々な前処理をおこなっています。以下にチェックすべき内容を（思いつく限り）リストアップしました（もしかしたら，この他にもあるかもしれません）。
なお，以下に書かれている内容は，書いてある順番でやれというわけではなく，自分の取り組むタスク/対象とするテキストにあったものを用いてください。
記述する関数やライブラリ（モジュール）はPythonで使われているものです。

  * 大文字/小文字の変換
    - 英語では，タスクによって大文字を小文字に変換したりする。
    - 企業名や商品名などの固有名詞など，大文字のままのほうが良い場合もある。
    - lower(), upper() など。


  * 句読点の統一
    - 日本語の句読点が「、」「，」「,」「。」「．」「.」が混在していると，後段処理で異なった単語IDを割り振ることになってしまう。そのため，分類モデルなどがその違いに過敏に反応/着目してしまうなどの弊害が起こり得る(Attentionベースの分類モデルなど)。
    - ただし，数値の区切り（2,000円）として「,」が使われている場合もあるので，テキストの属性を考慮して適宜統一したり，残したりする。
    - replace() など。


  * 用語（同義語）の統一
    - りんご，リンゴ，林檎など。
    - 料理ドメインなどのテキストであれば統一するほうがよい。
    - 逆に一般ドメインでは，固有名詞などの一部になる可能性があるので，統一しないほうが良い場合もある。
    - [日本語WordNet](http://compling.hss.ntu.edu.sg/wnja/)を使うなど。


  * 無駄な行やスペースの削除
    - 連続する空白/文頭・文末の空白スペースなどを削除する。
    - 文字列分割関数のエラーの大半がこれだったりする。
    - strip(), rstrip(), lstrip(), replace()など。


  * テキスト属性によるクリーニング
    - htmlタグなどの構造タグは，自然文解析には（ほぼ）不要なので先にlxmlなどで文書のみ取り除いてしまいましょう。
    - 部分的にタグが混入しているなら，reなどで処理。


  * SNSテキスト特有の文字(列)
    - SNS特有の文字(列)といえば，絵文字，顔文字，ハッシュタグ，URLなどがあげられると思います。
      - 顔文字，絵文字
        - [めちゃくちゃある。](https://unicode.org/emoji/charts/full-emoji-list.html)
        - emojiライブラリで絵文字を抽出・クリーニングするなど。
        - ご存知の通り，ここ数年でさらに絵文字の数が増えているので，ライブラリや辞書が更新されていないことが原因で取りこぼす可能性があることを覚えておく。
        - 感情分析などのタスクにおいて，絵文字も重要な要素となり得る。
    - ハッシュタグ，URL
      - 正規表現での処理がよくある。
      - ハッシュタグ情報はつかえるので，うまく処理や活用をしたいですね。


  * 未知語の処理
    - 未知語のままがよいのか，サブワードや文字レベルに分解すべきか。
    - 学習データの量などを考えて処理をするかどうかを決める。


  * 文字コード
    - サーバークライアント間で文字列のやり取りをする際に，文字のエンコード形式次第で文字化けが発生する場合がある。
    - OSの違いによる文字化けなどもあるので注意。


  * 基本形/省略系への変更
    - 同一の特徴は，単語分割の際に基本形に変更する。
    - 英語の省略形(I'm から I am)
    - MeCabの形態素解析結果を利用するなど。


  * 他言語の混入
    - 言語判定などを利用し，規定の言語以外を弾くなど。
    - unicodedataで文字種から日本語判定をするなどができる。


  * typo（崩れ表記）を直す
    - SNSテキストではよく考慮する項目であり，SNSテキストの解析を難しくさせる原因。
    - Encoder-Decoderモデルの機械学習モデルを使って崩れ表記を正規化する話もある。
    - [これを活用した食べ物の正規化の例などもわかりやすい。](https://techlife.cookpad.com/entry/2017/10/30/080102)


  * ストップワード処理
    - 他の前処理が終わった後に行うことが一般的。
    - 無駄な文字列を減らすことで，一文，一文書あたりの処理を高速化させることができる。
    - 分類に寄与しない情報を削ぎ落とすことで，データの本質を捉える。
    - 辞書ベースと統計ベースのやり方がある。
    - 辞書としては[SlothLibストップワード](http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt)など。


  * 半角/全角の統一(正規化)
    - 形態素解析後に正規化をすると結果が分かれる。
      - IPA辞書では全角「AB」は分割され，半角「AB」は分割されないなど，分割にも影響。
    - 「りんご」と「ﾘﾝｺﾞ」の例にあるように， せっかく用意された学習済み単語ベクトルなどを引っ張ってこれなくなるので，しっかりやっておきたい。
    - unicodedataとか, neologdnでできる。
    - MeCabの形態素解析結果を利用するなど。


  * etc. : フィラー/言い澱みの処理
    - 音声認識のテキスト化の際など。
    - 辞書ベースとかが浮かびやすいが，MeCabのIPA品詞体系に「フィラー」があるので，取り除くことが一応できる。


  * etc. : マスキング/置き換え処理
    - 人名など，個人特定に繋がったりプライバシーに関わるものはマスキングする。
      - 辞書ベースや[固有表現抽出ベース](https://qiita.com/Hironsan/items/a5acf1d121926666907b)など。
      - 差別的な表現は利用しない。人間と同様に，分類モデルなどにもバイアスがかかってしまいます。
    - 固有名詞などを「人名」や「地名」，「ジャンル」などのシンボルに置き換えて（マスキングして）学習することで，文脈などの学習がもう少しうまくいく場合もあります。
    - 対話システムなどで固有名詞とかをジャンルやキーワードに置き換えて返答生成なんかに使っていそう。


## 単語分割：　解析器/辞書を鵜呑みにしない
次に，単語分割について紹介します。日本語は英語のように単語ごとに区切られていないので，文を単語ごとに区切る必要があります。日本語で一番有名な単語分割/形態素解析器はMeCabかなと思います。ちょっと試してみるぐらいだから，ダウンロードしたものをそのまま使っているという話を見聞きするかと思います。しかし，そのまま使うことが本当に（そのテキストに対して）良い単語分割方法なのでしょうか。今一度，再考することも兼ねて，単語分割について少しだけ紹介してみようと思います。

* どの分割方法が分類モデルにとって適切か
  - 日本語の分割方法は大きく3つあると思っています。
    - 単語単位（MeCabなどの解析器+辞書）
      - よくみる形態素単位の分割です。
    - 文字単位
      - 一文字ごとの分割です。
    - サブワード(部分文字単位)
      - ざっくり言うと，コーパス中で出現頻度の高い単語を1単語とし，出現頻度の低い単語をにさらに小さい単位する分割です。


* 適切な形態素解析器/単語分割器/用語辞書をつかってますか？
  - それぞれできること，できないこと，解析誤りがあることを念頭に置いて使うことが大事です。
    - MeCabにも解析誤りはある「例：りんごジュース」
    - 解析器/辞書を変えるとうまくいく場合があります。
    - MeCab/Juman++/Sudachi など，短単位・長単位など，モデルや辞書によって解析単位が異なる単語がある。
    - ユーザー辞書登録(パラメータ変更)によって，他の単語の分割に影響が出ることも…。


* NEologd(長単位固有名詞)も使いよう
  - [NEologd](https://github.com/neologd/MeCab-ipadic-neologd)とは，新語に対応した，MeCabのシステム辞書のことです。高頻度で更新されています（ありがたや）。
  - 新語に強い一方で，長単位固有名詞は，機械学習のモデルに入力する際(単語-idへの置き換え)に文字情報を入れるなどしなければ，情報が失われてしまいます。
    - 例：「よこすか海軍カレー」は「よこすか/海軍/カレー」と分割して入力すれば，「カレー」であると分かる。一方，分割せずに入力すれば，モデル側からは文脈から食べ物だとわかるかもしれないが，カレーかどうかわからない可能性がある。
    - ちなみに「よこすか海軍カレー」はMeCab(IPA辞書)で解析エラーです。


* サブワード分割のメリット/デメリット
  - ここ数年，機械翻訳分野を発端に，サブワード(部分文字)への分割が注目されるようになりました。メリットととしては，翻訳や抽出モデルの評価時に未知語が（ほぼ）なくなることです。
    - 未知語がなくなる　＝　未知語がなんだったのかわからなくなる
    - 文長（単語数）の増加による予測回数の増加
    - 文脈の取り方を変えないと，文脈範囲が狭くなる(単語 n-gram > サブワード n-gram > 文字 n-gram)
      - 文字単位や単語単位と組み合わせて使ったりすると，狭い文脈と広い文脈を捉えられる。
    - 自分で語彙サイズを決める必要がある。
      - ドメイン特化したテキストを分割する場合は，機械翻訳のような大きな語彙サイズではなく，比較的小さい語彙サイズにすると，コーパス中の出現頻度の高い文字列がひとかたまりになりやすいので，そのドメインに特徴的な用語が抽出しやすくなる場合がある。


* 用語抽出・文書分類など，タスクにあった分割方法をとる
  - 固有表現抽出，文分類，文書分類など，分類する単位に応じて単語分割単位をかえてみる，というのもひとつの手です。
  - 文（書）分類においては，同義語などの用語統一したほうがいい分類ができる可能性があります。
  - サブワードへの分割をおこなうと，文書分類においては要素が増えすぎて逆に予測を難しくさせることも…。


## 前処理に関係するライブラリ・モジュールの一例
最後に，前処理に関係するライブラリの一部を載せておきます。

* コーパスクリーニング(Python)
  - unicodedata（正規化）
  - re（正規表現）
  - pytypo(タイポ判定)
  - emoji(絵文字判定)
  - lxml
  - nltk

* 単語分割
  - [MeCab(単語)](http://taku910.github.io/MeCab/)
  - [KyTea(単語)](http://www.phontron.com/kytea/index-ja.html)
  - [Juman++(単語)](http://nlp.ist.i.kyoto-u.ac.jp/index.php?JUMAN++)
  - [Janome(単語)](http://mocobeta.github.io/janome/)
  - [Sudachi(単語)](https://github.com/WorksApplications/Sudachi)
  - [Nagisa(単語)](https://github.com/taishi-i/nagisa)
  - [subword-nmt(サブワード)](https://github.com/rsennrich/subword-nmt)
  - [Sentencepiece(サブワード)](https://github.com/google/sentencepiece)
  - [BERTのmultilingual model](https://github.com/google-research/bert/blob/master/multilingual.md)


## 終わりに
前処理の重要性と内容についてお話しさせていただきました。今回は，「コーパスクリーニング」と「単語分割」を中心にお話しさせていただきましたが，うまくメリットが伝わったなら嬉しいです。
具体的な処理のソースコード等を公開してくださっている方がたくさんいらっしゃるので，ぜひ色々試してみてください。
正直，誰も思いついていない前処理なんてもうないと思っています。つまり，既に誰かが実装しているはずです。
自分で実装すると，かえって間違ってしまうこともありますので，なるべく用意されている関数やライブラリを使うようにしましょう。
ノイズもりもりのデータも磨けば宝になります。

## 参考
* 前処理
  - [自然言語処理における前処理の種類とその威力](https://qiita.com/Hironsan/items/2466fe0f344115aff177)
  - [自然言語処理の前処理・素性いろいろ](http://yukinoi.hatenablog.com/entry/2018/05/29/120000)
  - [Python3×日本語：自然言語処理の前処理まとめ](https://qiita.com/chamao/items/7edaba62b120a660657e)


* MeCab 解析誤り分析
  - [MeCab の形態素解析誤りを修正する生起コストの求め方](https://abicky.net/2016/11/28/204542/)


* Sentencepiece
  - [Sentencepiece : ニューラル言語処理向けトークナイザ](https://qiita.com/taku910/items/7e52f1e58d0ea6e7859c)


## その他所感
- 入社して半年ちょっとですが，内部で勉強会等もあり，楽しめてます。
- 最近はエルモ，バートの次が何になるのかぁと気になってます。

## 追記
- [今年(2019)もAdvent Calender出ます！](https://qiita.com/advent-calendar/2019/nttdocomo_service_innovation)
- [（記事が公開されました！）Deep Learning において，漢字はどの単位で分割・エンコードされるべきなのだろう？](https://qiita.com/dcm_sawayama/items/58c8a7ce71d2f24d6f2b)