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はここまでです。
- 自然言語処理特有の前処理の定義
- データの読み込みやtrain/test分割といった基本的な処理
- バッチ化と数値化(単語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メソッドを見ていただきたいのですが、
- バッチごとにデータ読み込み
- オプティマイザ初期化
- 推論
- 誤差の計算、精度の計算
- 誤差のバックプロパゲーション
- オプティマイザ更新
というニューラルネット学習の一連の流れが理解できます。単純に勉強にもなりますし、これはもう決まりきったパターンなので関数化して使い回せばそれほど負担にもなりません。
何事も一長一短でして、もしこれらのライブラリで実装可能なモデルの場合は、ライブラリを使ったほうが短くスマートには書けると思います。
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
0 件のコメント:
コメントを投稿