2018.06.05

【Techの道も一歩から】第10回「言語処理でのちょっとしたデータ確認やクレンジング」

こんにちは。DSOC R&Dグループの高橋寛治です。

最近は、前処理大全を読んでAwesomeなコードに感動しています(素晴らしい本です!)。
この本でも言及されていますが、文字の前処理は本当に奥が深く、多岐にわたるライブラリーやコマンドを用いて実現されます。
しかし、実際には形態素解析やストップワードの削除といった前処理以前に、データクレンジングやちょっとしたデータ確認を行うことが多く、意外とハマってしまって時間を取られてしまいがちです。

そこで、今回は私がよく利用しているデータ確認方法やクレンジングについて紹介します。

文字列に対するデータ確認とクレンジング

テキストを処理する自然言語処理システムを大ざっぱに言うと、文字列を入力し、機械学習器やルールに適した形式に変換された上で処理されて、文やラベルといった所望する処理結果が出力されるものです。

この文字列が意外とくせ者で、文字コードの問題や半角・全角といったちょっとした段差につまずいてしまいがちです。 データをあらかじめ確認して、簡単な変換やクレンジングを行うことで、この問題は多くの場合で回避できます。そして、データを整えた後に、システムに入力するための前処理である形態素解析や統計情報の取得を行います。

データの確認やデータクレンジングはLinuxコマンドを、前処理や詳細の統計情報の取得はPythonで行うと、効率良く作業できることが多いです。 コマンドとコードの組み合わせとなり、習得までの道のりは長いですが、一通り勉強する方法としては、言語処理100本ノックがおすすめです。

データの確認やクレンジングについて、Linuxコマンドを用いた例を挙げながら紹介します。

基本的には文字コードはUTF-8を利用

日本語を対象として文字列を加工する際に、一番つまづくポイントは文字コードでしょう。

おさらいすると、文字コードとは、文字をコンピュータ上で表現するために、文字に対して割り当てられるバイト表現の符号化方式のことです。
日本語で利用する主な文字コードには、ASCII、EUC-JP、SHIFT-JIS、UTF-8があります。 UTF-8に移行しつつありますが、WindowsはSHIFT-JIS(CP932)、古いLinuxで作成されたファイルはEUC-JPで作成されたファイルであることが多いです。
文字コードの確認方法ですが、 file コマンドか nkf コマンドをよく利用します。
あまりに短いファイルの場合は、判定に失敗するかもしれません(内部処理的にはパターンマッチであるため)。

$ file -b README.md
UTF-8 Unicode text, with CRLF line terminators
$ nkf --guess README.md
UTF-8 (CRLF)

上記コマンドで得られた結果は、文字コードUTF-8を利用していて、改行コードがCRLFであることを示しています。

おすすめは、文字コードにはUTF-8、改行コードはプロジェクト単位でそろえることです。
UTF-8はASCIIコードと相性が良く、標準的に利用される文字コードであるため、UTF-8にそろえるのがいいでしょう(ダメ文字をはじめとした問題を回避できます)。
改行コードは難しい問題ですが、Unix系主体だとLF、Windows主体だとCRLFでいいように思います。

文字コードの変換ですが、iconv コマンドもしくは nkf コマンドを利用します。
さまざまな種類の文字コードのファイルを取り扱う場合には、オリジナルの文字コードをファイルの拡張子に追記しておくと、見たときに分かりやすいです。

$ cat hoge.txt.sjis | iconv -f SHIFT_JIS -t UTF-8
# UTF-8に変換されたhoge.txt.sjisが標準出力される
$ cat hoge.txt.sjis | nkf -w
# UTF-8に変換されたhoge.txt.sjisが標準出力される

cp932からUTF-8に変換する場面が多いですが、一部変換できない文字列があることを頭の片隅に置いておいた方がいいでしょう。
機種依存文字や記号回りは、変換できない・失敗することがありますので、変換後にファイルを少し目視で確認するのがいいと思います。 ただ、言語処理で対象とする文字列にはならない場合が多いため、無視するというのも1つの手段です。
Pythonだとバイト列をデコードする bytes.decodeがありますが、errorsオプション引数で、変換できない文字列の対処法を変更することができます。

万能なUTF-8ですが、BOM(Byte Order Mark)の有無が問題になることがしばしばあります。
0xEF 0xBB 0xBF<U+FEFF> といった左記のバイト列が冒頭にあれば、BOM付きです。
WindowsのExcelで保存されたcsvファイル、tsvファイルは、BOM付きになっています。

簡単なBOMの除去方法は、uconv コマンドか nkf コマンドを利用する方法です。

$ cat hoge.txt.utf_8_with_bom | uconv --remove-signature
# BOMが削除された文字列が標準出力される
$ cat hoge.txt.utf_8_with_bom | nkf -w
# BOMが削除された文字列が標準出力される

less で閲覧しても <U+FEFF> が表示されなくなります。

Unicode正規化を適用する

Unicodeには等価性という概念があり、一つの文字を表現するために、いくつかの符号を結合した結合文字、もしくは単一の符号で表現される合成済み文字があります。
Unicode正規化は、文字列のバイト列表現方法を統一する方式で、㍍:メートル、ハンカク:ハンカクのように統一します。
言語処理では基本的に適用していいと思いますが、旧字体を明確に区別したい場合や、㍍のような機種依存文字、半角全角を素性として利用する場合は、利用しないほうがいいでしょう。
また、一度正規化すると復元することができない、すなわち非可逆であるという点にも注意します。

uconv コマンドで正規化を適用することができます。

$ echo '㍍' | uconv -x nfkc
メートル

Pythonでは、 unicodedata モジュールの normalize メソッドで適用できます。
あえてワンライナーで表現すると、以下のように記述できます。

$ echo ‘㍍’ | python -c 'import sys,unicodedata; [print(unicodedata.normalize("NFKC", _),end="") for _ in sys.stdin]'
メートル

不可視文字や見た目が同じ文字に注意する

ゼロ幅スペース(U+200B)は不可視文字、ノーブレークスペース(U+00A0)はスペースと見た目が同じですが、バイト列が異なります。
それぞれ目的のあるバイト列ですが、日本語を対象にした処理の場合は不要なことが多いです。
そして、こういった文字列は、Webから取得したHTMLに含まれていることが多く、後段の処理でエラーや想定外の挙動を引き起こします。

前節のUnicode正規化で、ゼロ幅スペースもノーブレークスペースも、半角スペース(U+0020)「 」に変換されます。
正規化しない場合は、ファイル内に含まれていないか確認したほうがいいかもしれません。
Google IMEの場合、Unicodeを日本語で入力すると変換候補に対応する文字が出てきます。
例えば、「U+0020」と入力すると半角スペース「 」が変換候補となります。

ファイルを閲覧する

moreとless

more はテキストを1画面ずつ表示するコマンドです。

$ more hoge.txt
# hoge.txtファイルを表示

対して lessopposite of more と説明されていて(man lessで確認)、 more コマンドと違い、いくつかの特徴があります。
例えば、ファイルを全て読み込んでから表示するのではなく、読み込んだものから表示されます。
他にも検索機能や行番号表示などあり、多機能で非常に出番の多いコマンドです。
less のマニュアルを一読されることをおすすめします。

$ less hoge.txt
# hoge.txtファイルを表示

catとtac

cat はファイル群を読み込んで連結したものを標準出力します。

$ cat hoge.txt fuga.txta
# hoge.txtファイルを表示し、続いてfuga.txtファイルを表示

対して tac はファイルの最終行から表示されます。
すなわち cat の逆で tac となります(笑)。

$ tac hoge.txt
# hoge.txtファイルをファイル末尾から表示

圧縮ファイルをそのまま閲覧するzcat、zless、bzcat、bzless

gz 形式のファイルを catless と同等の操作性で扱えるコマンドに zcatzless があります。
bz2 形式のファイルは bzcatbzless となります。
ちなみに zmorebzmore もありますが、 more コマンドの出番は少ないです。

$ zcat hoge.txt.gz
# hobe.txt.gzファイルを表示
$ zless hoge.txt.gz
# hobe.txt.gzファイルを表示
$ bzcat fuga.txt.bz2
# fuga.txt.bz2ファイルを表示
$ bzless fuga.txt.bz2
# fuga.txt.bz2ファイルを表示

ファイルの冒頭や末尾を少し確認するheadとtail

less コマンドでファイルを閲覧し、ファイルの冒頭や末尾を確認するのもいいですが、便利なコマンドがあります。
head コマンドはファイルの先頭を、tail コマンドはファイルの末尾を表示します。
行数を指定するオプションは、両方同じです。

$ head -15 hoge.txt
# 15行先頭から表示
$ tail -15 fuga.txt
# 15行末尾から表示

wcで行数を確認

ファイルの行数やスペースで区切られた単語数などをファイル単位で表示します。
前処理前後でファイルの行数や単語数が変化していないかを確認するときや、処理量の見積もりでよく利用します。

wc hoge.txt
# nの数 単語数 バイト数 ファイル名 を表示

行数だけ計上する際は、 -l オプションを利用します。

快適な前処理のために

言語処理システムを作るための素性抽出をはじめとした前処理は、データを確認してクレンジングした上で、快適に取り組むことができます。
地味な作業ですが、後々の工程に響いてくることでしょう。 次回は、私がよく利用する前処理について紹介したいと思います。

執筆者プロフィール

過去記事

▼第9回 「API GatewayとAWS Lambda PythonでAPI開発」 Vol. 4:デプロイ
▼第8回 「API GatewayとAWS Lambda PythonでAPI開発」Vol. 3:エラー処理
▼第7回 「API GatewayとAWS Lambda PythonでAPI開発」Vol. 2:ローカルでの開発環境構築
▼第6回 「API GatewayとAWS Lambda PythonでAPI開発」Vol. 1:API GatewayとAWS Lambdaを知る
▼第5回 快適なシェル環境の再構築を自動化する
▼第4回 第16回情報科学技術フォーラム(FIT2017)で登壇
▼第3回 第11回テキストアナリティクス・シンポジウム
▼第2回 R&D論文読み会勉強会
▼第1回 言語処理100本ノック勉強会

text: DSOC R&Dグループ 高橋寛治