日本語CTRLを1から学習する - 7

はじめに

前回までCTRL1の学習に必要なものを一通り準備してきた。
今回は実際にGCP上で学習を始めるための検証を行う。

データのやり取りについて

これまで学習用のデータの前処理はGoogle Colaboratory上で行ってきた。
(データの保存はGoogle Drive)
学習を行うためにはGCP上にこのデータを転送しなければならない。
以下に学習を行うための流れをまとめる。

  1. GCP上でCloud Storageのバケットを作る。
  2. Google DriveからCloud Storageにデータを転送する。
  3. Compute Engineを立ち上げる。
  4. Compute Engineのローカルにデータを転送する。
  5. 学習する

バケットを作る

プロジェクトを作成した後、Cloud Storage上でバケットを作成する。
今回、バケット作成時の設定はデフォルトから変えていない。

Cloud Storageへのデータ転送

以下のサイトの手法を使った。
https://medium.com/@philipplies/transferring-data-from-google-drive-to-google-cloud-storage-using-google-colab-96e088a8c041

from google.colab import auth
auth.authenticate_user() # googleアカウントで認証する
project_id = 'project_id_name' # 自分が作成したプロジェクトID
bucket_name = 'bucket_name' # 自分が作成したバケット名
!gcloud config set project {project_id}
!gsutil -m cp -r "drive/My Drive/sample.zip" gs://{bucket_name}/ # My Drive/以下のsample.zipを転送

Compute Engineを立ち上げる

Compute Engineは時間あたりの使用料金が高いので慎重に立ち上げる。
以前も触れたが機械学習環境用のブートディスクを使うと環境設定の手間が減る。

CPUについて

今回の検証ではGPUで主に計算するため、CPUの性能はそれほど必要ないと思う。
ただGoogle Colaboratory上で検証した時と同程度のメモリは確保しておきたいので
vCPU 4 個 + メモリ 15 GB ($138.70/月)を利用する。

GPUについて

インスタンスの立ち上げ時に値段を確認した2020/4/17時点での
利用可能なGPUの1枚あたりの値段(us-central1)

GPU 値段/月
Tesla K80 $328.5
Tesla P4 $438.0
Tesla T4 $255.5
Tesla V100 $1810.4

V100は圧倒的に単価が高い。
最も新しいTesla T4がコストパフォーマンス的に良さそう。

参考: GCP上のGPU一覧
https://cloud.google.com/compute/docs/gpus?hl=ja

また、プリエンプティブインスタンスというものもあるらしい。
参考: https://qiita.com/EastResident/items/442616d6ad1fe8108ea3
今回の場合は時間制限などから利用しないが、 もう少し小規模な計算の場合は利用してみたい。
(今回の学習でもうまくチャックポイントを出力してリスタートを繰り返せば使えるかもしれない)

今回GPUつきのインスタンスを立ち上げようとしたら失敗した。
どうやらGPUを利用したインスタンスには"割り当て"が必要らしい。 初期のGPU割り当て数は0なのでリクエストを送る必要がある。
https://cloud.google.com/compute/quotas#gpus
今回は0→1への変更なのですぐ(2分程度)で申請が通ったが、
膨大なリソースを利用するときは時間がかかるらしい。

Cloud StorageからCompute Engineへデータの転送

Cloud StorageからCompute Engineへのデータ転送はgsutilコマンドが楽。
Compute Engineにログインし、ターミナル上で

gsutil cp gs://bucket_name/data.zip ./

とするだけでインスタンス上にデータがコピーされる。

学習する

機械学習環境用のブートディスクを利用すると pytorchなどのライブラリが予め
インストールされており、 最初に起動したときにcuda関連もインストールされる。
ただし、今回利用するtransformersライブラリはインストールされていなかったので
pipを使ってインストールした。 (sentencepieceはインストールされていた。)

適切なパラメータ設定検討

今回マシンリソースが限られているためCTRLのパラメータ設定を適切に行う必要がある。
マシンリソースに関連するパラメータは前々回も考えていたが

  • 学習エポック数(Iteration数)
  • Transformer層数
  • 潜在次元数
  • 入力系列長
  • バッチサイズ

などが考えられる。

学習エポック数

元のCTRL論文では学習Iterationの回数は800K(80万回)とあった。
BERTの論文(図5)2でも600K~1MのIterationで収束しているように見える。
ゆえに学習率をそれらに合わせ1Mステップ程度学習すれば十分であると思われる。
1 Iter/s の場合は1 M ステップに訳11.5日かかるのでこれくらいを目安に
モデル・バッチサイズを決める。

Transformer層数・潜在次元数

これらのパラメータはNeural Netの表現能力に関係している。

BERTのgithubページに層数・潜在次元数ごとにモデルが公開されている。
https://github.com/google-research/bert

また以下の論文がBERTのサイズを下げた場合の影響について検証している。
https://arxiv.org/abs/1908.08962

この論文ではpretrainingしてからfine-tuningする場合などは
幅(潜在次元数)よりも深さ(層数)のほうが大事であるとしている。
初期値をランダムとした場合にはパラメータごとの差は小さい。
また、BERTのタスクと今回の文章生成はタスクとして異なるため、
この結果をそのまま当てはめていいか分からないがとりあえずは層数優先とする。

入力系列長

このパラメータの値は最終的なモデルがどれくらいの長さの文章を
作成したいかによって決める。

参考 : https://data.wingarc.com/easy-reading-16542
上記の記事によると読みやすい文章は500文字程度らしい。
手元の小規模なデータセット(100文章程度)でsentencepieceの
1トークンあたりの文字数を調べたところ以下の表のようになった。

データセット 平均 文字数/トーク
青空文庫 1.81155
Wikipedia 1.9279
JESC 1.73421

平均すると1トークンあたり2文字弱なので、系列長を256とすることで
だいたい500文字程度の文章を学習する。

バッチサイズ

バッチサイズは1 Iterあたりのデータ数である。
バッチサイズが小さすぎるとIterごとのデータの偏りが大きくなり
学習が不安定となると言われている。
BERT論文ではpre-trainで256, fine-tuningで16~32としていた。
元のCTRL論文ではCloud TPU256コア使いバッチサイズ1024を
実現していたがそれほど計算リソースは使えない。

バッチサイズを上げるテクニックについては以下の記事が参考になった。
https://medium.com/huggingface/training-larger-batches-practical-tips-on-1-gpu-multi-gpu-distributed-setups-ec88c3e51255

accumulating gradientsはIterごとにoptimizerを動かすのではなく
数ステップ間のgradientを貯めてからoptimzeirを動かしてパラメータ更新を行う。
簡単に実装できるのはいいが、Layer Normalizationの挙動が少し変わるので
完全に等価な結果にはならないような気がする。
できればaccumulating gradientsの有無による結果を比較したい。

GCP上での検証

GCP上で実際に少し動かして学習可能かの検証を行ったところ
各パラメータは以下のように決まった。

パラメータ
エポック数 1
Iter 1351572
Transformer層数 8
潜在次元数 512
入力系列長 256
バッチサイズ 20

ネットワークのサイズに関してはBERT-mediumと同じものにすることとした。
このパラメータでバッチサイズ20程度にするとnvidia-smiでの
GPUのメモリ使用率が10 GB程度となったのでこの値とした。

エポック数に関してはエポックあたりのIter数が1Mを超えてしまったので1エポックのみの学習となった。
これについての詳細を以下に記載する。

データセット 青空文庫 Wikipedia JESC
ファイルサイズ(付加情報込) 955MB 2.77GB 215MB
文章セグメント数 34476 1833827 2703143
エポック数 32 8 1

文章セグメント数は1つの文章を文字数1000で分割したものである。
学習時はこのセグメントをsentcepieceでトークン化して、
トークン数が256となるようにpaddingやランダム切り出ししている。

各データセット間のファイルサイズを比較するとJESCが少ないが、 文章セグメント数は最も多い。
すなわち1文章当たりの文字数が圧倒的に少ない。
さらに今回は長文の文章生成を行いたいので、 バッチ内のデータ割合は
青空文庫:Wikipedia:JESC = 4:5:1
としている。

これによりJESCデータが1ループするまで135万Iterationかかる
一方で青空文庫は32ループ、Wikipeidaは8ループしている。
メインターゲットである青空文庫Wikipediaの文章は何巡かしているので
とりあえず今回はこのような設定とする。

この設定のもとで1Iter当たり約1sなので推定計算時間は 1351572 s = 15.6 日である。
(途中経過次第で打ち切ることもありうる)

まとめと今後

今回はGCPのプロジェクトを立ち上げて実際に学習を開始した。
計算リソースに関してはかなり妥協しているがとりあえずどうなるか見てみたい。
今後は学習の途中経過を観察しながらモデルの性能評価を行う。

参考文献