テクノロジー

torchtextで前処理をして記事分類してみた

1. 自己紹介

初めまして。 2020年4月よりビジネスエンジニアリング部DSチームに加わる予定の名越と申します。 今年(2019年)の4月からD2Cでインターンに参加させていただいております。 現在は学部の4年生で、大学では経営学部に所属しています。営業の長期インターンシップをしているうちにデータの重要性に気付き去年からデータサイエンティストのインターンをしています。 今回初めて深層学習に挑戦したのでわからないことも多く、先輩社員の方に何度も質問しました。ありがとうございます。

2. 実施内容と目的

今回はニュース記事の記事カテゴリの分類をtorchtextとpytorchでCNNを実装して実施します。 データセットはlivedoorニュースコーパスを使用させていただきました。 このコーパスは2012年9月上旬ごろのlivedoor ニュースを収集したものです。 収集されたニュースは以下のカテゴリのどれかに属しています。
カテゴリ記事数
トピックニュース770
Sports Watch900
ITライフハック870
家電チャンネル864
MOVIE ENTER870
独女通信870
エスマックス870
livedoor HOMME511
Peachy842
これに取り組んだ理由として、私は社内の未活用のテキストデータの分析により広告効果を向上させるプロジェクトに関わりたいと考えています。
そのスキル獲得のためにまずはlivedoorニュース記事の記事カテゴリ分類をしてみるのがいいだろうというアドバイスを先輩社員の方にいただいたからです。 そのためノイズが多いという社内データの分析を想定し前処理も少し工夫しました。 CNNの実装はpytorchを使い、データ整形はtorchtextを使って行いました。以下それぞれの採用理由です。
  • CNN
    RNNと比べた時に自然言語処理の分類問題については同等以上の精度が出てかつ仕組みが比較的わかりやすかったため。
  • pytorch
    計算処理が早く、論文実装のほとんどがpytorchを使っているため。
  • torchtext
    自力で実装するのは困難に思えた前処理を簡単に行える物を調べていた際に見つけた。使いこなせると有用そうではあったが、日本語ドキュメントが少なかったため調査も兼ねて採用した。

3. 環境やツールなど

環境
  • python: 3.6.5
  • ubuntu: 16.04.6
ツール
  • torch: 1.0.1
  • torchtext: 0.3.1
  • scikit-learn: 0.20.3
  • mecab: 0.996.1
  • mojimoji: 0.0.9

4.実行の流れ

前処理から学習、評価の流れは以下のように実施します。 ① テキストデータを読み込んでクリーニング処理 ② torchtextでのデータの読み込み ③ 学習済み単語ベクトルの読み込みと単語への番号振り ④ バッチ化 ⑤ CNNを組んでデータを学習させる ⑥ テストデータを予測して精度評価

4.1 テキストデータの読み込み・クリーニング

まずlivedoorニュースのデータを読み込んでpandasのDataFrameの形式にします。 1記事テキストファイルになっているlivedoorニュースコーパスを以下のような1つのDataFrameにしています。 記事の1行目と2行目は記事のURLとタイムスタンプなので削除しました。
カラム名内容
key記事ID
label記事カテゴリ
text記事本文
次にクリーニング処理を実施します。 今回は以下の処理を実装しました。 ① 記号・日付の削除 ② 数字をすべて0に ③アルファベットを全部小文字に ④ 出現頻度の低い単語を削除 このうち④と⑤はtorchtextでできるのでここでは①と②の処理を実装します。
① 記号・日付の削除、②数字をすべて0に
記号を一つ一つ指定して削除しようとすると抜け漏れが生まれやすいですし何より手間がかかります。 そこでmecabで文中で使われている記号を抽出してリスト化し、削除します。 こうする事により抜け漏れが少なく手軽に記号を削除できます。
parseToNodeからfeature.split(‘,’)で形態素解析の結果を取り出して、featureの0番目に品詞の情報が入っているのでそこを判定基準として記号を抜き出しています。 半角記号はnode.feature.split(",")[6]に何も入っていないものが多いのでmojimojiの関数han_to_zenで一度文章をすべて全角にします。 次に関数を実行して文中で使われている記号のリストを作成します。 記号のリストができたので、記号と日付を削除し数字を0に正規化する関数を定義します。 記事削除後
記事削除後
関数を実行すると以下のようにほとんどの記号を消すことができました。

4.1.5 torchtextに投入する前の処理

torchtextでデータを読み込ませるクラスはtrainデータとtestデータでそれぞれcsv,tsv,jsonのいずれかの形式のファイルを読み込む仕様となっています。かつtrainデータとtestデータは別のファイルである必要がありそうなのでその処理をしていきます。 そのためクリーニング後のデータセットのラベルを数値データに変換し、trainとtestに分けて保存します。 正解ラベルは9種類あったので0~8の数字に置き換えました。
次はデータをtrain:test=8:2で分割します。 後はcsv,tsv,jsonのいずれかで保存してこの工程は終了です。

4.2 torchtextでのデータの読み込み    

torchtextで前処理は以下の様に実装します。
  • textとlabelそれぞれのデータと前処理を管理するFieldを定義する ↓
  • TabularDatasetクラスにFieldを渡しデータの読み込みと前処理
torchtext.datasets.Fieldクラスはデータの前処理とその結果を管理するクラスです。 それではまずFieldの定義をしていきます。 Fieldの引数は以下の意味を持っています。
  • sequential: 対応するデータがtextのように可変長のデータかどうか
  • lower: 文字をすべて小文字に変換するかどうか
  • tokenize: tokenizeや前処理を記述した関数
今回は設定しませんでしたが、引数stop_wordsでストップワードを設定することもできます。 tokenizeに使用する関数は文字列を受け取り、分かち書き結果の単語単位の配列を返す関数です。 tokenizeには任意の関数を指定可能ですが、今回はmecabでtokenizeを行います。
次にデータセットの定義をします。 data.TabularDataset.splitsはデータを読み込んで、datasetオブジェクトを返します。
上記のコードは ここで各データセットのカラムにフィールドを対応させています。
対応のさせ方は、辞書型でキーにカラム名、値にタプル型でデータに付ける名前と対応させるFieldクラスを指定するというやり方です。 ここでキーに指定しなかったカラムは取得しません。

4.3学習済み単語ベクトルの読み込みと単語への番号振り

次は単語に番号を振って辞書を作成していきます。 この処理にはFieldクラスのbuild_vocabメソッドを使います。 このメソッドでは番号振り以外にも出現頻度の低い単語を番号を振らずに削ったり、単語へ番号を振るのと同時に学習済みの単語ベクトルを指定し、読み込むことができます。 torchtext.vocab.Fasttextで日本語の学習済み単語ベクトルがデフォルトで読み込めるようになっていますが、現在(2019/6/20)学習済みモデルのダウンロードリンクが403エラーになってしまうので、
今回は自分でダウンロードしたfasttextの学習済みモデルを読み込むように設定します。 ①こちらのモデルをダウンロード(zipファイルになっています) ②zipファイルを解凍(中にはmodel.vecが入っています) ③model.vecを.vector_cache/のディレクトリの下に移す。 次に以下の様にVectorクラスを定義してbuild_vocabの変数に指定します。
引数の意味は以下になります
  • vector:読み込ませたい学習済み単語ベクトル
  • min_freq:番号を振る最小出現回数、指定した数値未満の出現回数の単語には番号を振らない。
単語に振った番号は以下のコードで確認できます。 \と\はメタキーワードです。 \は未知語を表現しており、バッチ化の際に未知語が置き換わります。出現回数がmin_freq未満の単語も\に置き換わります。 \はトークンを表し、バッチ化の際にバッチ内の文章に含まれる単語数を固定長に合わせる場合に用いられます。

4.4 バッチ化

trainデータをバッチ化するためのイテレータを定義します。
ロスのグラフ
torchtext.data.BucketIteratorはDatasetオブジェクトから各単語を番号に変換し、ミニバッチごとにまとめた行列を返すイテレータを作成できます。 またバッチを作る際にバッチ内の一番長い文章に合わせてパディングも行ってくれます。 torchtext.data.BucketIteratorでは引数sort_keyにシーケンス長を指定することで文章をシーケンス長順に並び変えその順番でバッチ化することができます。 これによってパディングの量を最小限に済ますことができます。 以下のグラフは長さ順でパディングした時とそうでないときのlossを測ったもので、緑が前者で水色が後者です。 損失関数はクロスエントロピーを使用しました。
ロスのグラフ
ここから、パディングの量を最小限にすることにより、しない時と比べてlossが安定し、過学習が起こりにくくなっていることがわかります。 testデータを定義するためのイテレータも定義しておきます。 次にテストデータのイテレータを作成します。

4.5 CNNを組む

今回組んだモデルは以下の図のような構造になっています。
精度を高くするために、5種類のフィルターで特徴量をそれぞれ作り組み合わせるようなモデルにしています。 以下が今回のハイパーパラメータです。
パラメータ名説明
kernel_num畳み込み時の出力チャネル数256
kernel_size畳み込み時のフィルターサイズ、今回は複数設定[1,2,3,4,5]
fixed_length1文章あたりの最大単語数1500
dropout過学習を防ぐためのもの0.2

4.6 テストデータを予測して精度評価

scikitlearnのclassification_reportを使って結果を見ます。 以下の結果が出ました。 どのパラメータにおいても少なくとも0.9弱のスコアが出て平均スコアは0.96という結果がでたのでここで終わりとします。

5.まとめ

今回はtorchtextにデータを投げる下準備をしてからtorchtextで前処理しpytorchでCNNモデルを組んで記事分類をしてみました。 torchtextは使って何かしてる日本語の記事が現状あまりないのですが、前処理も基本的には手軽にできますしバッチ化の際に手軽にパディングを最小限に済ませられるメソッドもあり、 自然言語処理の前処理を行うのに大分便利そうなのでもっと使いこなせるようになっていきたいです。

6. 参考にした記事など


関連タグ