pytorchとtorchtextではじめる自然言語処理

2020/07/07

自然言語処理

t f B! P L

pytorchとtorchtextではじめる自然言語処理
 
torchtextとpytorchでさくっと自然言語処理を始められる雛形コードの解説をします。
 
NNの学習コードはモデル自体は変えてもコードの大部分は似たような構造になってるので、自分で理解した雛形コードを持っていると色々はかどります。
 
私の場合、pytorchによる自然言語処理では、前処理部分はtorchtextを使う恩恵が大きく、それ以外の部分はふつうに素のpytorchで書いています。

ノートブックはこちらです。
torchtext-quickstart.ipynb · master · youmounlp / nlp-notebooks-misc · GitLab
 
torchtextで3ステップ、pytorchで5ステップに分けて考えると、全体をするっと理解できます。
 
サンプルタスクはメール返信が脈アリか?脈ナシか?という2値分類になっています。
 
ブログでは各セクションにコメントをいれていきます。

torchtext-1 Field

初っ端ですがtorchtextでここが一番重要です。
 
フィールドごとにデータの前処理を定義します。
フィールドとは、例えばtsvからデータを読み込む場合は、列に対応します。
列ごとにテキストが格納されている列、ラベルが格納されている列、のように型があると思います。
 
テキスト型のフィールドに対しては、tokenizeとpaddingの設定が重要です。
 
tokenizeは自分で関数を定義することができます。サンプルコードでは簡単のため、文字単位に分割しています。
 
この段階ではpaddingされませんが、padding_tokenの指定(pad_token)、先頭からpaddingするか後ろからpaddingするか(pad_first)などの設定は、ここで済ませておきます。
 
Filedに関してはドキュメントを読んで確認しておくとよいです。
パラメータにわかりやすい名前がついているので理解しやすいと思います。
https://torchtext.readthedocs.io/en/latest/data.html#field

torchtext-2 Dataset

ここでは実際にファイルからデータを読み込み、列を前節のFieldに対応させます。
また、train_test_splitや、単語の数値への変換辞書の作成(build_vocab)もここでやります。

torchtext-3 Iterator

ここではバッチ化がメインです。
 
テキストデータのような可変長データでは、BucketIteratorを使うことで、よしなに長さが同じくらいのデータ同士でバッチ化してくれます。
paddingも実際の処理が走るのはここですが、Fieldに定義済みなのでここで特に何か指定する必要はありません。
 
torchtextはここまでです。

  1. 自然言語処理特有の前処理の定義
  2. データの読み込みやtrain/test分割といった基本的な処理
  3. バッチ化と数値化(単語IDへの変換)と自動padding

というステップで理解しましょう。
 
他にも学習済み単語ベクトルのロードなどが目玉機能ですが、今回は使用していません。

pytorch-1 model定義

ここが一番ニューラルネットしている感がある。
initメソッドでレイヤーを定義して、forwardメソッドで順方向の計算グラフを定義します。
 
ここはタスクによりけりですし、一番ニューラルネットしている感がある(二回目)ので、楽しんで書きましょう。

pytorch-2 ヘルパー関数

学習ループ内で呼ぶtrain/evalutateの関数です。
この処理を学習ループ内に直接書いている例も多いですが、私は関数化したほうが見通しがよくなるので、別途関数に書いています。
 
後述の学習ループでも述べますが、ここは実はKerasなどではfitメソッドだけで済んでしまう部分です。
 
それと比べるとpytorchめんど、、、となってしまうのですが、ちょっと変わった処理を書きたいときはpytorchのほうが便利です。
ここが結構pytorchの強みかな、と思っています。
 
pytorchは計算グラフの中(forwardメソッドの中)や、損失関数、学習ループに素のPythonを書きやすいです(たぶんそういう意図で設計されている)。
 
一見するとPythonとpytorchのAPIが密結合になってしまい、見通しが悪いのですし、素のPythonでプリミティブな書き方をするより、NNライブラリのAPIだけで実装するほうが(そうできる場合は)実際にスマートでしょう。
pytorchにもそういう意図のAPIライブラリがいくつかあります(catalyst、Pytorch Lightning、skrochなど)。
 
しかし、これらのライブラリでは、ライブラリのメソッドがサポートしている範囲外のことをやろうとすると、途端にめんどくさくなります。できたとしてもハック的なやり方になってしまったり、調べた挙げ句無理っぽい、という場合もあります。
 
ハック的な書き方をするくらいなら、最初から素のpytorchに素のPythonで書いてしまったほうが、自由度が高いですし、学習コストも低くて済む、というのが私の考えです。
 
trainメソッドを見ていただきたいのですが、

  1. バッチごとにデータ読み込み
  2. オプティマイザ初期化
  3. 推論
  4. 誤差の計算、精度の計算
  5. 誤差のバックプロパゲーション
  6. オプティマイザ更新

というニューラルネット学習の一連の流れが理解できます。単純に勉強にもなりますし、これはもう決まりきったパターンなので関数化して使い回せばそれほど負担にもなりません。
 
何事も一長一短でして、もしこれらのライブラリで実装可能なモデルの場合は、ライブラリを使ったほうが短くスマートには書けると思います。

pytorch-3 学習ループ

ここでは、モデル、損失関数、オプティマイザなどを定義して、epochごとのループで、先程のtrain/evaluateのメソッドを呼ぶだけです。
あとは、よしなに誤差や精度を表示するコードなどを書きます。

pytorch-4 テスト

ファイルから読み込んで学習させるまでは、色々なサンプルコードに書かれているのですが、メモリ上にある1データを前処理して推論結果を得る部分は意外と省略されています。
 
むしろアプリケーションに乗せる場合はこのコードが大事になります。

pytorch-5 モデルの保存と読み込み

これもテストに並んで大事です。
torchtextとpytorchでは、前処理用のfieldとモデルの重みを保存します。
 
別のコードから呼び出す場合は、モデルの構造とトークナイザーメソッドは改めて定義しておく必要があります。
 
サンプルコードではやっていませんが、学習、推論で共通して使うコードは別ファイルにまとめておき、そこから読み込むようにするとスマートです。

まとめ

pytorchもこのようにステップにわけることで理解しやすくなります。
 
1のモデル定義は、タスクによって書き換える必要がある一方、2,3,5あたりは、ほとんどそのまま使い回せます。
これらは、タスクにあまり依存せず、誰が書いても同じになるからです。
 
このようにテンプレ化しておくと捗ると思います。

参考資料

https://pytorch.org/tutorials/beginner/text_sentiment_ngrams_tutorial.html


by @youmounlp

ラベル

QooQ