はじめに
※こちらは、8月に投稿した記事にコード解説の項目を加筆して再投稿したものとなります。以前の記事よりマシになっているはずなので、もう一度読んでいただけると幸いです。
今まで主にスマホアプリ開発をしていたのですが、2022年8月からAI関連の案件に携わっており、BERTという自然言語処理モデルについて勉強しています。
最近は、多変量の時系列表データの学習に使用する、TabBERT(Hierarchical Tabular BERT)というBERTの応用モデルに関する論文を読みました(Qiitaにも紹介記事を書きました)。
この論文、なんとありがたいことに、著者が事前学習のコードとデータをGitHubにあげています。
環境構築やモデル学習の経験を積むことで理解をさらに深められると思い、こちらのコードをいろいろ触ってみることにしました。
とりあえず第一歩として、AWSで仮想環境をたてて事前学習を実行するところまでをやってみました。
以下について知りたい方に、特に役立つ記事になると思います。
- GPUインスタンスの立て方(AMIとインスタンス選び、vCPUの制限解除方法)
- Anacondaによる環境構築(condaのエラー解消、仮想環境の選択)
- 事前学習まわりのコードの中身
※仮想環境を選択したのは、ローカルマシンのGPUがNVIDIA製ではなく、CUDAとcuDNNが動かせなかったためです。setup.yml
で環境構築を行おうとしたら、ResolvePackageNotFound
エラーでいきなり失敗しました...
(base) [9:10:58] → conda env create -f setup.yml
Collecting package metadata (repodata.json): | / done
Solving environment: failed
ResolvePackageNotFound:
- pytorch==1.7.1=py3.8_cuda11.0.221_cudnn8.0.5_0
name: tabformer
channels:
- anaconda
- pytorch
- huggingface
- conda-forge
dependencies:
- python>=3.8
- pip>=21.0
- pytorch=1.7.1=py3.8_cuda11.0.221_cudnn8.0.5_0
- torchvision
- pandas
- scikit-learn
- transformers
- numpy
- libgcc
- pip:
- transformers==3.2.0
環境構築
AWSでGPUインスタンスをたてて、その中でさらにAnacondaで仮想環境をつくるまでの流れとなります。
GPUインスタンスの作成
AWSで用意されていたDeep Learning用のAMIと一番安価なGPUインスタンスを使用します。
- AMI(Amazon Machine Image): amazon/Deep Learning AMI GPU PyTorch 1.12.0 (Amazon Linux 2) 20220803
- インスタンスのタイプ: g4dn.2xlarge(xlargeだとメモリが枯渇したため) *説明ではg4ad.xlargeとなっています。すみません。
- EBSボリューム: 300 GB(45 GBだとストレージが枯渇したため
OSError: [Errno 28] No space left on device
)
g4adインスタンスを作成するのに必要なvCPUが足りないとのことです。
右上の「制限緩和のリクエスト」から以下のリクエストを送ったところ、4時間くらいで制限が解除され、無事にインスタンスを起動することができました。
Anacondaで仮想環境構築
SSHでEC2インスタンスに接続します。
※パブリックIPはコンソール画面のEC2 > インスタンス
からパブリックIPv4アドレス
をコピーしてください。
(base) [10:55:26] → ssh -i ~/.ssh/MyKeyPair.pem ec2-user@<パブリックIP>
インスタンスにコードを落とします。
[ec2-user@ip-172-31-21-45 ~]$ git clone https://github.com/IBM/TabFormer.git
setup.yml
のあるディレクトリに移動して、conda env create ~
を実行します。
[ec2-user@ip-172-31-21-45 ~]$ cd TabFormer
[ec2-user@ip-172-31-21-45 TabFormer]$ conda config --set channel_priority flexible
[ec2-user@ip-172-31-21-45 TabFormer]$ conda env create -f setup.yml
※conda config --set channel_priority flexible
をしないと、以下の文言が出て構築に失敗します。
Collecting package metadata (repodata.json): done
Solving environment: /
Found conflicts! Looking for incompatible packages.
conda init
後に、作成した仮想環境(tabformer)をアクティベートします(アクティベート前に再度リモートログインが必要)。
[ec2-user@ip-172-31-21-45 TabFormer]$ conda init bash
[ec2-user@ip-172-31-21-45 TabFormer]$ conda activate tabformer
最後に、特定バージョンのcudatoolkit
とpytorch
がコンフリクトするというAnacondaのバグがあったので、以下を実行します。
[ec2-user@ip-172-31-21-45 TabFormer]$ pip install torch==1.7.1+cu110 torchvision==0.8.2+cu110 torchaudio===0.7.2 -f https://download.pytorch.org/whl/torch_stable.html
※以上をインストールしないと、python実行時に以下のエラーが出ます。
OSError: /home/ka37/anaconda3/envs/fail/lib/python3.8/site-packages/torch/lib/../../../../libcublas.so.11: symbol free_gemm_select version libcublasLt.so.11 not defined in file libcublasLt.so.11 with link time reference
追記
まだ以下のようなエラーが出る場合があります。
ImportError: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by /opt/conda/envs/tabformer-opt/lib/python3.8/site-packages/pandas/_libs/window/aggregations.cpython-38-x86_64-linux-gnu.so)
libstdc++.so.6
にGLIBCXX_3.4.29
のバージョンが含まれていないとのことです。
そもそもlibstdc++.so.6
のパスが間違っていそうなので、こちらをconda環境が含まれているパスに修正します。
以下のコマンドでパスを調べます。
find / -name "libstdc++.so*"
すると、conda環境を含むパスにlibstdc++
を確認できます。
...
/opt/conda/envs/tabformer-opt/lib/libstdc++.so.6
/opt/conda/envs/tabformer-opt/lib/libstdc++.so.6.0.21
/opt/conda/envs/tabformer-opt/lib/libstdc++.so.6.0.29
libstdc++.so.6
のままだとGLIBCXX_3.4.29
は存在しないので、libstdc++.so.6.0.29
のパスに変更します。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/conda/envs/tabformer/lib
事前学習
論文では2種類のデータが用意されているのですが、今回はクレジットカードのトランザクションデータを使用し、学習を行います。
まずは、ローカルからデータをコピーします。
ディレクトリごとコピーするために、-r
オプションをつける必要があります。
*READMEに記載されていた./data/card/
ではなく、./data/prsa/
にデータをコピーします(多分誤記)。
(base) [14:31:15] → scp -r -i ~/.ssh/MyKeyPair.pem ~/Programs/TabFormer/data/credit_card ec2-user@18.180.227.218:/home/ec2-user/TabFormer/data/
学習を実行します。
[ec2-user@ip-172-31-21-45 TabFormer]$ python main.py --do_train --mlm --field_ce --lm_type bert --field_hs 64 --data_type card --output_dir ./output_card/
6時間でストレージ(300 GB)がいっぱいになってしまいましたが、とりあえず動きました。
以下が学習が止まった時点でのcheckpoint(35000, 35500)の中身です。
作成された学習済モデルpytorch_model.bin
やconfig.json
optimizer.pt
をFine-Tuningの際に使用します。
以下がconfig.json
とpytorch_model.bin
の読み込み例です。
from transformers import BertConfig, BertModel
config = BertConfig.from_pretrained(
"./output_card/checkpoint-35000/config.json"
)
model = BertModel.from_pretrained("./output_card/checkpoint-35000/pytorch_model.bin", config=config)
print(config)
print(model)
BertConfig {
"architectures": [
"TabFormerHierarchicalLM"
],
"attention_probs_dropout_prob": 0.1,
"field_hidden_size": 64,
"flatten": false,
"gradient_checkpointing": false,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-12,
"max_position_embeddings": 512,
"model_type": "bert",
"ncols": 12,
"nhead": 8,
"num_attention_heads": 12,
"num_hidden_layers": 12,
"num_layers": 1,
"pad_token_id": 0,
"total_flos": 41577468710400000,
"type_vocab_size": 2,
"vocab_size": 143492
}
BertModel(
(embeddings): BertEmbeddings(
(word_embeddings): Embedding(143492, 768, padding_idx=0)
(position_embeddings): Embedding(512, 768)
(token_type_embeddings): Embedding(2, 768)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(encoder): BertEncoder(
(layer): ModuleList(
(0): BertLayer(
(attention): BertAttention(
(self): BertSelfAttention(
(query): Linear(in_features=768, out_features=768, bias=True)
(key): Linear(in_features=768, out_features=768, bias=True)
(value): Linear(in_features=768, out_features=768, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(output): BertSelfOutput(
(dense): Linear(in_features=768, out_features=768, bias=True)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
...
コード解説
コード全文の説明は難しいので、要点のみ解説します。
以下がmain.py
です。
Dataset
TransactionDataset
クラスはPytorchのDataset
クラスを継承しており、データの前処理やトークンとidの対応を辞書に登録するようなメソッドが登録されています。
-
encode_data()
:生データを前処理データに変換 -
init_vocab()
:トークンとidの対応を登録(辞書作成) -
user_level_data()
:ユーザーごとのデータとラベルをすべての連結してリストで出力-
unique_users
: Userカラムのリスト(24001行までなら[0 1]) -
user_data
: Userごとのデータフレーム -
trans_data
: 1Userのラベル以外のデータを全部まとめたリスト[[…], […], […]…] special tokenはまだ入っていない -
trans_labels
: 1Userのラベルを全部まとめたリスト
-
-
format_trans()
:user_level_dataで出力された連結データを入力して、11フィールドずつのレコードに分割。その後にトークンをidに変換、[SEP]トークンを付加して出力 -
prepare_samples()
:format_transで出力されたデータとラベルを10データ(window)ずつ連結して出力-
user_idx
: trans_dataのインデックス -
user_row
: 1Userの生データリスト -
user_row_ids
: 1Userのid変換後のリスト [SEP]付与済 -
ids
: strideで5こずつずらしながらseq_lenでuser_row_idsを10連結ずつに区切ったリスト[…] -
self.data
: 10連結リストidsをまとめたリスト[[…], […]…] 全User含む
-
-
__getitem__()
:prepare_samplesで作成されたデータからindexに対応したものを取得。flatten(平坦化)フラグがFalseであれば10行(window)ずつreshape[10, 12]。最後のカラムはformat_transで付与した[SEP]
自然言語(例えば英語)の場合であれば、単語をBertTokenizer
に入力すれば対応idを出力してくれますが、TabBERT用のTokenizerは存在しないため、辞書を作成するような処理が必要となります。
トークンから辞書を作成するメソッドset_id
、辞書からidを取得するメソッドget_id
はVocabulary
クラスから呼び出して使用しています。
Model
TabFormerBertLM
からモデルを読み込みます。
DataCollator
DataCollatorForLanguageModeling
を継承したTransDataCollatorForLanguageModeling
を作成し、トークンのマスク化やinput_idsとlabels(MASKの正解)の出力を行います。
-
mask_tokens()
:MLMのためにトークンをマスク化 -
__call__()
:Datasetの__getitem__で取得したデータをバッチサイズ分まとめてinput_ids([8, 10, 12])とlabelsとしてTensorで出力。バッチサイズは固定。バッチ内のUserは混ざっている。
Trainer
Dataset、Model、DataCollatorをそれぞれ引数にとり、事前学習をやってくれます。
DataLoaderがラップされており、DataCollatorの__call__
の処理はこの中で実行してくれます。
おわりに
Fine-Tuning以降のコードはなさそうだったので、現在調査しながら実装しています。
実装できたら新しく記事を書きます。
参考資料