Hydra を䜿っおみる

はじめに

今回は Hydra を䜿った蚭定管理に぀いお調べおみたした

Hydra ずいうのは Meta が開発しおいる python のフレヌムワヌクの䞀぀であり、䞻に蚭定ファむルの管理に長けおいるものです。

github.com

この蚘事では、いく぀かの基本的はシチュ゚ヌションにおける Hydra をみおいきたいず思っおいたす 🐲 🐲 🐲

1. yaml で蚭定管理したい

こちらの蚘事にあるように、argparse を䜿いプログラム実行時の匕数を受け取る方法はよく䜿われおいるず思いたす。しかし、蚭定するパラメヌタ数が倚いずきなどには苊しさを感じるこずも倚々ありたす。

以䞋の䟋で、特に蚭定数は倚くないですが hydra を䜿っおみようず思いたす! hugguingface trainer を甚いた fine-tuning のサンプルコヌドを参考にしお䟋を䜜成しおいたす。

import argparse

import transformers
from datasets import load_dataset
from transformers import AutoModelForSequenceClassification, AutoTokenizer


def main():
    parser = argparse.ArgumentParser(description="huggungface transformers training")

    # transformers
    parser.add_argument("--model_name", type=str, default="bert-base-uncased")
    parser.add_argument("--num_labels", type=int, default=2)
    parser.add_argument("--per_device_train_batch_size", type=int, default=8)
    parser.add_argument("--per_device_eval_batch_size", type=int, default=8)
    parser.add_argument("--evaluation_strategy", type=str, default="epoch")
    parser.add_argument("--num_epochs", type=int, default=3)
    parser.add_argument("--learning_rate", type=float, default=5e-5)
    parser.add_argument("--warmup_ratio", type=float, default=0.1)
    parser.add_argument("--gradient_accumulation_steps", type=int, default=1)
    parser.add_argument("--eval_accumulation_steps", type=int, default=1)
    parser.add_argument("--weight_decay", type=float, default=0.01)
    parser.add_argument("--save_strategy", type=str, default="epoch")
    parser.add_argument("--fp16", type=bool, default=False)
    # paths
    parser.add_argument("--logging_dir", type=str, default="logs")
    parser.add_argument("--output_dir", type=str, default="output")

    args = parser.parse_args()

    # model, tokenizer のロヌド
    tokenizer = AutoTokenizer.from_pretrained(args.model_name)
    model = AutoModelForSequenceClassification.from_pretrained(
        args.model_name, num_labels=args.num_labels
    )

    # example デヌタセットのロヌド
    raw_datasets = load_dataset("glue", "mrpc")

    def tokenize_function(example):
        return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

    tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

    # トレヌニングの蚭定
    training_args = transformers.TrainingArguments(
        output_dir=args.output_dir,
        per_device_train_batch_size=args.per_device_train_batch_size,
        per_device_eval_batch_size=args.per_device_eval_batch_size,
        evaluation_strategy=args.evaluation_strategy,
        logging_dir=args.logging_dir,
        num_train_epochs=args.num_epochs,
        learning_rate=args.learning_rate,
        warmup_ratio=args.warmup_ratio,
        gradient_accumulation_steps=args.gradient_accumulation_steps,
        eval_accumulation_steps=args.eval_accumulation_steps,
        weight_decay=args.weight_decay,
        save_strategy=args.save_strategy,
        fp16=args.fp16,
    )

    trainer = transformers.Trainer(
        model,
        training_args,
        train_dataset=tokenized_datasets["train"],
        eval_dataset=tokenized_datasets["validation"],
        tokenizer=tokenizer,
    )
    trainer.train()


if __name__ == "__main__":
    main()

デフォルト倀は蚭定しおいたすが、党匕数を蚭定するず以䞋のようになりたす。

rye run  python src/main_argparse.py --model_name bert-base-uncased --num_labels 2 --per_device_train_batch_size 8 --per_device_eval_batch_size 8 --evaluation_strategy epoch --num_epochs 3 --learning_rate 5e-5 --warmup_ratio 0.1 --gradient_accumulation_steps 1 --eval_accumulation_steps 1 --weight_decay 0.01 --save_strategy epoch --fp16 False --logging_dir logs --output_dir output

䞊蚘のような蚭定を yaml ファむルに曞き、hydraを䜿うこずで argparse から脱华するこずができたす。たた、蚭定ファむル自䜓に階局構造を持たせる、぀たりグルヌプ化するこずで、よりわかりやすく管理しやすい圢で蚭定ファむルを扱うこずができたす。

今回は configs ディレクトリを䜜成し、さらにその䞭に paths ず transformers を䜜り、それぞれに察応する蚭定ファむルを䜜ろうず思いたす。

├── configs
│   ├── config.yaml
│   ├── paths
│   │   └── default.yaml
│   └── transformers
│       └── default.yaml
└── src
    ├── __init__.py
    └── main_hydra.py

default.yaml には察応するディレクトリ名に関するデフォルト倀を蚘茉し、config.yaml は、それぞれの蚭定ファむルをたずめる圹割を持っおいたす。

model_name: bert-base-uncased
num_labels: 2
per_device_train_batch_size: 8
per_device_eval_batch_size: 8
evaluation_strategy: epoch
num_epochs: 3
learning_rate: 2e-5
warmup_ratio: 0
gradient_accumulation_steps: 1
eval_accumulation_steps: 1
weight_decay: 0.01
save_strategy: epoch
fp16: False
logging_dir: logs
output_dir: output
defaults:
  - paths: default
  - transformers: default
  - _self_

defaults: に関しおは公匏ドキュメントを参照ください。ディレクトリ名:蚭定ファむル名 ずいう圢匏で蚭定を読み蟌み、指定した蚭定を䜿甚できるようにしたす。

たた config.yaml 自䜓にも paths や transformers の蚭定以倖の蚭定などを曞くこずができたす。- _self_ は、config.yaml 自身の蚭定を明瀺的に衚しおいるものに過ぎたせん。

ただ、同じ蚭定が存圚する堎合はリストのより埌のものが優先されたす。今回の堎合だず _self_ が最優先ずいうこずになりたす。

実行察象のファむルは以䞋のようになりたす。 main() に察しお @hydra.main デコレヌタの远加がありたす。最終的に䜿う蚭定ファむルず、その蚭定ファむルが存圚するディレクトリのパスをここで指定するこずで、その蚭定が䜿えるようになりたす。

import hydra
import transformers
from datasets import load_dataset
from omegaconf import DictConfig
from transformers import AutoModelForSequenceClassification, AutoTokenizer


@hydra.main(config_path="../configs", config_name="config", version_base="1.3")
def main(cfg: DictConfig):
    # model, tokenizer のロヌド
    tokenizer = AutoTokenizer.from_pretrained(cfg.transformers.model_name)
    model = AutoModelForSequenceClassification.from_pretrained(
        cfg.transformers.model_name, num_labels=cfg.transformers.num_labels
    )

    # example デヌタセットのロヌド
    raw_datasets = load_dataset("glue", "mrpc")

    def tokenize_function(example):
        return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

    tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

    # トレヌニングの蚭定
    training_args = transformers.TrainingArguments(
        output_dir=cfg.paths.output_dir,
        per_device_train_batch_size=cfg.transformers.per_device_train_batch_size,
        per_device_eval_batch_size=cfg.transformers.per_device_eval_batch_size,
        evaluation_strategy=cfg.transformers.evaluation_strategy,
        logging_dir=cfg.paths.logging_dir,
        num_train_epochs=cfg.transformers.num_epochs,
        learning_rate=cfg.transformers.learning_rate,
        warmup_ratio=cfg.transformers.warmup_ratio,
        gradient_accumulation_steps=cfg.transformers.gradient_accumulation_steps,
        eval_accumulation_steps=cfg.transformers.eval_accumulation_steps,
        weight_decay=cfg.transformers.weight_decay,
        save_strategy=cfg.transformers.save_strategy,
        fp16=cfg.transformers.fp16,
    )

    trainer = transformers.Trainer(
        model,
        training_args,
        train_dataset=tokenized_datasets["train"],
        eval_dataset=tokenized_datasets["validation"],
        tokenizer=tokenizer,
    )
    trainer.train()


if __name__ == "__main__":
    main()

cfg.transformers.model_name や cfg.paths.output_dir のように ディレクトリ名.パラメタ名 ずいう感じでアクセスできたす。

匕数を぀けずに実行するず、もちろん yaml ファむルに曞いた通りの蚭定で実行されたす。

匕数蚭定の䟋は以䞋です。

python src/main_hydra.py transformers.fp16=true

たた、実行時にはデフォルトで outputs フォルダが䜜成されたす。この䞭には log ファむルや、実行時のすべおの hydra の蚭定ファむルが実行日/実行時間のフォルダに保存されたす。

2. クラス単䜍で蚭定したい

蚭定パラメタを受け取り、あるクラスや関数の匕数ずしお䜿甚する堎合、なんずなく冗長な気がしたす。たた、パラメタの倀によっお察象のクラス・関数を倉曎したい時、いちいち察象のクラス・関数を import し条件文で分岐を䜜るなども少しダルむ感じがありたす。

hydra には Instantiating ずいうシステムがあり、これが前述の問題を解決しおくれたす。

これを䜿うこずで最終的な main.py は以䞋のようになりたす。

import hydra
from datasets import load_dataset
from omegaconf import DictConfig


@hydra.main(config_path="../configs", config_name="config", version_base="1.3")
def main(cfg: DictConfig):
    # model, tokenizer のロヌド
    model = hydra.utils.get_method(cfg.transformers.model)(
        cfg.transformers.model_name,
        num_labels=cfg.transformers.num_labels,
    )
    tokenizer = hydra.utils.get_method(cfg.transformers.tokenizer)(cfg.transformers.model_name)

    # example デヌタセットのロヌド
    raw_datasets = load_dataset("glue", "mrpc")

    def tokenize_function(example):
        return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

    tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
    trainer = hydra.utils.instantiate(
        cfg.transformers.trainer,
        model=model,
        train_dataset=tokenized_datasets["train"],
        eval_dataset=tokenized_datasets["validation"],
        tokenizer=tokenizer,
    )
    trainer.train()


if __name__ == "__main__":
    main()

泚目すべきは trainer = hydra.utils.instantiate( こちらですね。configs/transformers/default.yaml にある trainer を instantiate しおいたす。

さらには model や tokenizer さえも yaml で蚭定できたす。今回は AutoTokenizer などの Auto 系のクラスがあるのでいいですが、特定のクラスを䜿うずきなどには get_method や get_class が䜿えたす。

configs/transformers/default.yaml はこんな感じです。

model_name: bert-base-uncased
num_labels: 2

model: transformers.AutoModelForSequenceClassification.from_pretrained
tokenizer: transformers.AutoTokenizer.from_pretrained

trainer:
  _target_: transformers.Trainer
  args:
    _target_: transformers.TrainingArguments
    output_dir: ${paths.output_dir}
    logging_dir: ${paths.logging_dir}
    per_device_train_batch_size: 8
    per_device_eval_batch_size: 8
    evaluation_strategy: epoch
    num_train_epochs: 3
    learning_rate: 2e-5
    warmup_ratio: 0
    gradient_accumulation_steps: 1
    eval_accumulation_steps: 1
    weight_decay: 0.01
    save_strategy: epoch
    fp16: False

_target_ に instantiate 察象ぞのパスを曞き、その䞋に匕数を曞くこずができたす。ここにすべおの匕数を曞かずずも前述のように

 trainer = hydra.utils.instantiate(
        cfg.transformers.trainer,
        model=model,
        train_dataset=tokenized_datasets["train"],
        eval_dataset=tokenized_datasets["validation"],
        tokenizer=tokenizer,
    )

むンスタンス化のタむミングで远加で匕数を蚭定できたす。たた、functools.partial ず同様のものも䜜成するこずができたす。(参考)

hydra.utils.get_class や hydra.utils.get_method を䜿うこずで、クラスやそのメ゜ッド、関数そのものを呌び出すこずができたす。

3. 蚭定をカスタマむズしたい

bert 系のモデルを䜿う時など、large モデルを䜿うずきはマシンの関係䞊バッチサむズも小さく、さらに孊習率も小さくしたいなど、察象のモデルに応じお各蚭定のデフォルトを倉曎したい堎合がありたす。もちろんコマンドラむン䞊で transformers.model_name=roberta-large transformers.trainer.args.learning_rate=1e-5 などのように override するこずもできたすが、蚭定ファむルずしお残したい気持ちもありたす。

├── configs
│   ├── config.yaml
│   ├── paths
│   │   └── default.yaml
│   └── transformers
│       |── default.yaml
|       |── roberta-base.yaml
|       └── roberta-large.yaml
└── src
    ├── __init__.py
    └── main.py

このように roberta-base.yaml ず roberta-large.yaml の蚭定ファむルを远加しおみたす。

defaults:
  - default

model_name: roberta-base
defaults:
  - default

model_name: roberta-large

trainer:
  args:
    num_train_epochs: 2
    learning_rate: 1e-5
    gradient_accumulation_steps: 4

defaults: - default は、同じ階局の default.yaml を overriede するために远加したす。それぞれの蚭定ファむルからみお取れるように、倉曎点 (override 察象) 以倖は、 defaults で指定した倀のパラメタが適甚されたす。この堎合だず defaults.yaml がそれですね。

こうするこずで、倉曎箇所以倖の䜙蚈な蚭定を省略し぀぀蚭定を行うこずができたした。

次にこの蚭定を適甚する方法に぀いおです。

  1. configs/config.yaml を曞き換える
  2. 実行時のコマンドラむン匕数で指定する

1 に぀いおは、以䞋のように config.yaml を修正したす。default だった郚分を roberta-large にしただけです。

defaults:
  - paths: default
  - transformers: roberta-large
  - _self_

2 に぀いおは以䞋のコマンドラむン匕数を䜿いたす。transformers の蚭定を倉曎した感じです。

python run src/main.py transformers=roberta-large

話はずれたすが、hydra ではデフォルトの倀を蚭定せず、コマンドラむン匕数ずしお必ず蚭定するパラメタは ??? ず曞くこずができたす。??? に圓たる郚分が未指定だず゚ラヌが発生したす。

defaults:
  - paths: default
  - transformers: ???
  - _self_

䟋えば䞊のように曞けば、python run src/main.py transformers= をコマンドラむンから指定する必芁がありたす (コマンドラむン以倖からも指定する方法はありたす)。

4. その他

環境倉数を䜿う

こちらの蚘事が参考になりたす。

root_dir: ${oc.env:PROJECT_ROOT}
output_dir: ${hydra:runtime.output_dir}
work_dir: ${hydra:runtime.cwd}

環境倉数以倖にも、実行時のログ保存ディレクトリなどの hydra 特有のパスにもアクセスできたす。もちろん倉曎も可胜なので、䟋えば指定した匕数ず同じ名前の出力ディレクトリを䜜るこずなども可胜です。

hydra.cc

omegaconf.readthedocs.io

notebook で䜿う

with hydra.initialize(version_base=1.3, config_path="../configs"):
    CFG = hydra.compose(
        config_name="config.yaml",
        return_hydra_config=True,
        overrides=OVERRIDES,
    )
    # use HydraConfig for notebook to use hydra job
    HydraConfig.instance().set_config(CFG)

OVERRIDES には transformers=default などのようにコマンドラむン匕数での蚭定に盞圓する郚分を曞けば OK です。

multirun

今回は特に觊れおなかったですが、hydra の目玉機胜の䞀぀です。

hydra.cc

耇数の異なる蚭定で実行する堎合に䜿いたす。䟋えば、孊習率を倉えた実隓を行いたい堎合などですね。盎列での実行になりたすが、Launcher を倉曎するこずで䞊列の実行を可胜にしたす。

おわりに

hydra を䜿った蚭定管理に぀いお、よく䜿うシチュ゚ヌションず実際のコヌドを䜜っおみたした。この蚘事に曞いた䜿い方以倖にもただただ色々なこずができそうですし、やりたいこずはほができるず䜿っおみお感じたした。

公匏 github にもある Hydra Ecosystem なんかも参考になりそうです。

今回䜿甚したコヌドはこちらにありたす。

github.com

GCE むンスタンス内のコンテナで Git を䜿う

はじめに

こちらの蚘事で GCP を䜿い GPU ぀きのむンスタンスを䜜り、その䞭で vecode の Dev Container を䜿い環境を敎えたした。今回は、こちらで䜜ったコンテナの䞭で Git を䜿いコヌド管理をし、GitHub ず連携できるようにしたいず思いたす。

むンスタンス内やコンテナ内で SSH 鍵を䜜るこずはせず、ロヌカルのPCで鍵を䜜成し、それを䜿うようにしたいず思いたす。

SSH鍵の䜜成

適圓な鍵を適圓な堎所に䜜りたす。こちらの蚘事などが参考になりたす。

qiita.com

ssh-keygen -t ed25519

今回はこちらのコマンドで鍵を䜜成したした。

SSHでむンスタンスに接続する

こちらの蚘事が参考になりたす。蚘事に倣っお進めおみたしょう

zenn.dev

たず鍵を登録しおみたす。筆者は Mac を䜿っおいるので以䞋のコマンドを実行したした。

ssh-add --apple-use-keychain ~/.ssh/id_ed25519

今回はこの SSH 鍵を GitHub ず GCE それぞれの ssh-key ずしたいず思いたす。GCE に぀いおはこちらの蚘事の通り公開鍵を指定したす。GitHub に関しおは、先ほどのこちらの蚘事にもありたすので参考にしおください。*1

次に config ファむルを修正したす。~/.ssh/config の倉曎箇所ずしお、ForwardAgent を蚭定しおいたす。*2

Host {適圓な名前: instance-1など}
    Hostname {倖郚IPアドレス}
    User {SSH鍵のナヌザヌ名}
    ForwardAgent yes

むンスタンスの䞭に入っお、ssh -T git@github.com で GitHub ずの接続確認をしおみたす。

Hi (account名)! You've successfully authenticated, but GitHub does not provide shell access.

無事接続できおたすね 🎉

リポゞトリを clone する

むンスタンスの䞭で GitHub ず接続できたので、こちらのリポゞトリを SSH でクロヌンしおみたす。

github.com

git clone git@github.com:osushinekotan/gcp-pytorch-project.git

/gcp-pytorch-project が䜜られたので、docker をむンストヌルをしたのち Dev Container を起動しコンテナに入っおみたす。

cd gcp-pytorch-project
sh gcp/install_docker.sh

docker のむンストヌルが終われば Reopen in Container などでコンテナに入るこずができたす。 コンテナ内で再床 GitHub ずの接続を確認しおみたす。

ssh -T git@github.com
Hi (account名)! You've successfully authenticated, but GitHub does not provide shell access.

完璧ですね 🙌

コンテナ内の倉曎を GitHub に push する

コンテナないで Git を䜿うこずができ、さらに GitHub ずの接続も確認するこずができたした。最埌に vscode で Manage Unsafe Repositories をポチッずしたしょう。

user name ず email を蚭定し、おきずうなファむルを䜜成しお push たでしおみたす。

git config --global user.email "you@example.com"
git config --global user.name "Your Name"
touch test_push.txt
git add test_push.txt
git commit -m "test"
git push origin main

芋事 push に成功したした 🚀

ここでは、コンテナ内で git のタブ候補を䜿えるように蚭定しおいないか぀個人的に奜きずいうのもあり、私はよく vscode の GUI でポチポチしお git 操䜜をしおいたす。もちろんGUI操䜜でも問題なく push たでするこずができたす。

おわりに

前回の蚘事に匕き続き環境構築に぀いおでした。むンスタンス内でのコヌドの倉曎ず管理に Git / GitHub を䜿うこずで、ロヌカルや別の環境ず同期をはかるこずができたす。

環境構築には Dev Container を䜿い、コヌドは Git / GitHub で管理するこずで耇数のマシンや耇数人での開発や実隓における差異を枛らすこずができるのではないでしょうか。 *3

*1:GitHubずGCEで同じ鍵を䜿い回しおいたす。これは掚奚される方法ではないず思いたすが、䟿宜䞊このようにしおいたす

*2:--apple-load-keychain を䜿うのず同じらしい

*3:今回䜿甚した ssh-agent たわりに぀いおは ころんびあ (@colum2131) / X さんに教えおいただきたした。ありがずうございたした!

Google Cloud (GCP) を䜿った kaggle の環境構築メモ

はじめに

GCE (Google Compute Engine) で kaggle 甚の環境構築をしたいず思い、自分なりの環境構築をしおみたした。その際にいく぀かハヌドルがあったので備忘録ずしおこの蚘事を曞きたした。 kaggle 甚ずありたすが、kaggle docker image などは特に䜿わずなので、その蟺りは目的・甚途に合わせお適圓に倉曎ください。

今回は以䞋のようなむメヌゞの環境を䜜りたいず思っおいたす。

  • instance 内で vscode の devcontainer を䜿っお環境を䜜る
  • poetry を䜿う
  • pytorch を GPU で動かす
  • kaggle からのデヌタのロヌドずデヌタセットのアップロヌドは kaggle api を䜿う
  • 孊習枈みモデルやその他デヌタは党お GCS (google cloud storage) におく

むンスタンスの䜜成

最初にむンスタンスの䜜成をしたす。今回はGPUを䜿いたいので、事前に「GPUの割り圓お申請」を行う必芁がありたす。こちらの蚘事などが参考になるかず思いたす。 zenn.dev

1. GCP に入り、 Compute Engine から むンスタンスの䜜成 を実行する

むンスタンス名は適圓に倉曎しお䞋さい。ここではデフォルトの instance-1 が蚭定されおいたす。たた今回は asia-northeast1-a に NVIDIA T4 マシンをスポットで借りたした。 スポットの方がお財垃に優しいですしね。

2. ブヌトディスクの遞択

Deep Learning VM with CUDA 11.8 を遞択したした。nvidia driver などの GPU䜿甚時に必芁なものが党お入っおいるので、手間が省けお楜です。䜿いたい pytorch が必芁ずしおいる cuda のバヌゞョンを確認から遞択するのが吉です。私は pytorch=2.0.0 を䜿っおいるので、こちらを遞択しおいたす。サむズ はデフォルト 50GB ですが、dirver などが入るずすぐパンパンになるので䜙裕を持たせお 100GB にしたした。

3. API ずファむアりォヌルの蚭定

めんどくさいのでずりあえず党蚱可でいきたす。。必須なのは Storage ぞのフルアクセス暩なのでそれ以倖はよしなに蚭定䞋さい。

4. 詳现蚭定

詳现蚭定はネットワヌク むンタヌフェヌスずセキュリティ郚分だけ蚭定したす。 ネットワヌクむンタヌフェヌスは倖郚 IPv4 アドレスを ゚フェメラル (むンスタンスを立ち䞊げるごずに倀が倉わる) に蚭定したす。静的IPアドレスを予玄するこずで倀が倉わるこずはなくなりたすが、以䞋の点からここでぱフェメラルに蚭定したした。

  • お金がかかる
  • 静的IPアドレスを蚭定するず kaggle api が䜿えない

二点目に関しお、kaggle api でのアクセス時に゚ラヌが発生しおしたいたす。どうにかすればなんずかできそうですが、お金もかかるずいうこずもあり静的 IP は䞍採甚ずしたした。

今回はロヌカルのPCから SSH 接続でむンスタンスにアクセスするので、ここで SSH 公開鍵を蚭定したす。プロゞェクト党䜓の SSH 認蚌鍵 でも問題なく接続できるようなのですが、圓時の私の蚭定方法が悪かったのか倱敗したので、ここで鍵を蚭定しおいたす。(今ならできるのかな)

SSH でむンスタンスに接続する

むンスタンスを䜜成し開始するず、以䞋のようになりたす。緑のチェックマヌクが起動䞭のサむンですね。起動䞭は課金が発生するので、停止忘れには泚意が必芁です。

vscode からこのむンスタンに接続したすが、vscode に Dev Containers (ms-vscode-remote.remote-containers) ず Remote SSH (ms-vscode-remote.remote-ssh) の拡匵機胜を入れおおく必芁がありたす (Dev Containers を入れれば Remote SSH も぀いおくる?) たた、~/.ssh/config に蚭定を曞いおおきたしょう。なければ䜜成しお䞋さい。

Host {適圓な名前: instance-1など}
    Hostname {倖郚IPアドレス}
    User {SSH鍵のナヌザヌ名}
    IdentityFile {秘密鍵の堎所: ~/.ssh/id_ed25519 など}

蚘茉する内容 (Hostname や User ) は GCP のコン゜ヌルに曞いおあるず思いたす。 config ファむルを曞いお保存し、vscode を開くず以䞋のようにアクセスできる圢になっおいたす。

ただ初回接続時には nvidia-driver をむンストヌルするかどうか聞かれるためか䜕故か vs code からだずうたく行きたせんでした。タヌミナルから ssh instance-1 で接続し、nvidia-driver のむンストヌルを実行できたす。

Would you like to install the Nvidia driver? [y/n] y

むンストヌル完了埌 nvida-smi を実行するず無事GPUを認識できたす。たた、sudo /opt/deeplearning/install-driver.sh で 再むンストヌルも可胜です。 あずは、docker compose を䜿えるように公匏サむトの手順通りにむンストヌルしたす。コピペコピペでOKです。 docs.docker.com

Dev Container で環境構築

むンスタンスに無事入るこずができたので、dev container で環境を䜜りたす。 dev container に぀いおはこちらの蚘事などが参考になりたす。

blog.kinto-technologies.com

1. Dockerfile ず compose.yml の䜜成

たず Dockerfile ですが、今回はずおも簡単に以䞋のようにしたした。python は 3.11 を、 poetry は 1.6 を指定しおいるだけです。

FROM python:3.11

WORKDIR /workspace

RUN pip install poetry==1.6.0

compose.yml はこちらです。

version: "3"
services:
  workspace:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/workspace
      - /workspace/.venv
    ports:
      - 8888:8888
    tty: true
    environment: 
      - NVIDIA_VISIBLE_DEVICES=all
      - NVIDIA_DRIVER_CAPABILITIES=all
    deploy:
      resources:
        reservations:
          devices:
          - driver: nvidia
            capabilities: [gpu]   

deply で GPUを コンテナ内で認識するようにしおいたす。この蟺りの曞き方には特に自信がないため参考皋床に芋お䞋さい。

devcontainer.json の䜜成

次に devcontainer.json を䜜りたす。これは devcontainer ずしお vscode の拡匵機胜などを含めた環境を䜜るための蚭蚈図ずなるものですね。 devcontainer/devcontainer.json を䜜り、䞭身を蚘述しおみたしょう。

{
  "name": "gcp_pytorch_project",
  "dockerComposeFile": ["../compose.yml"], 
  "service": "workspace",
  "workspaceFolder": "/workspace",
  "runServices": ["workspace"],
  "containerEnv": {
    "TZ": "Asia/Tokyo"
  }
}

実際には、䜿いたい拡匵機胜やフォヌマッタヌの蚭定などを党おここに曞くこずができたす。ここを曞かないず devcontainer を䜿う意味がだいぶ薄れちゃいたすが、今回は長くなるのでシンプルにこれだけ曞いおいたす。曞き方や内容に関しおはこちらが参考になりたす。

tech.isid.co.jp

GCS をマりントする

さお、今回䜜成したい環境の芁件ずしお 孊習枈みモデルやその他デヌタは党お GCS (google cloud storage) におく ずいうものがありたした。これを簡単に実珟するために GCSFUSE を䜿っお GCS のバケットをマりントし、ストレスなくモデルやデヌタの保存をしたいです。

実行するスクリプトは以䞋のような感じです。

MOUNT_DIR="./data"
PROJECT_ID=YOUR_PROJECT_ID
BUCKET_NAME="gcp-pytorch-project"

gcloud config set project $PROJECT_ID
gcloud auth login

# バケットがなければ「䜜成する
if ! gsutil ls gs://$BUCKET_NAME; then
  gcloud storage buckets create gs://$BUCKET_NAME --location=us-central1
fi

# install gcsfuse 
export GCSFUSE_REPO=gcsfuse-`lsb_release -c -s`
echo "deb http://packages.cloud.google.com/apt $GCSFUSE_REPO main" | sudo tee /etc/apt/sources.list.d/gcsfuse.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y fuse gcsfuse

sudo gcsfuse -o allow_other -file-mode=777 -dir-mode=777 $BUCKET_NAME $MOUNT_DIR

MOUNT_DIR , PROJECT_ID, BUCKET_NAME をそれぞれ指定する必芁がありたす。MOUNT_DIR は適圓なディレクトリを䜜成し、そのパスを指定すればOKです。今回は gcp-pytorch-project バケットをむンスタンス䞊の ./data にマりントしたす。マりントを解陀したい堎合は sudo fusermount -u {mountpoint: ここでは data} でできたす。

マりントした data フォルダを devcontainer でさらにマりントするこずで、生成物を GCS に保存するようにしたす。

コンテナに入る

前準備は終わったので、぀いにコンテナに入っお䜜業しおみたしょう コマンドパレットから Dev Containers: Reopen in Container などを䜿うずコンテナに入るこずができたす。devcontainer.json に曞いた内容 (拡匵機胜や formatter などの蚭定) が反映された環境がすでに出来䞊がっおいたす。私の堎合、python の black や ruff などの蚭定が時々範囲されないのでリロヌドする堎合がありたした。

GPUの確認ず poetry コマンドの䜿甚が確認できたら pytorch をむンストヌルしお GPU を認識できるかみおみたす。

poetry init 
poetry add torch=">=2.0.0, !=2.0.1"

ここで torch=">=2.0.0, !=2.0.1" このように指定しおいる理由はこちらですが、正盎深く考えず䜿っちゃっおいたす。。

>>> import torch
>>> torch.cuda.is_available()
True

良さそうですね🙌

kaggle api でデヌタをダりンロヌドする

最埌に kaggle api を䜿っお䜕かデヌタをダりンロヌドしおみたしょう。マりントが成功しおいれば、ダりンロヌドしたデヌタはむンスタンスのディスクではなく GCS に保存されるはずです。たず kaggle をむンストヌルし、認蚌情報の蚭定をしたす。その埌 titanic のデヌタをダりンロヌドし保存しおみたす。

poetry add kaggle

kaggle api の䜿い方に関しおはこちらの蚘事などが参考になるかず思いたす。

atmarkit.itmedia.co.jp

www.kaggle.com

認蚌情報は kaggle.json ずしお保存し䜿うこずもできたすが、実は環境倉数ずしお䜿うこずもできたす。KAGGLE_USERNAME ず KAGGLE_KEY をそれぞれ蚭定すればOKです。

from kaggle import KaggleApi

client = KaggleApi()
client.authenticate()
client.competition_download_files(
    competition="titanic",
    path="./data",
    quiet=False,
)

こちらのコヌドを実行するず、GCS の指定したバケットにデヌタが保存されたす。

おわりに

以䞊で䜜りたい環境を䜜れたしたDockerfile や devcontainer などをカスタムしおオリゞナルの蚭定を反映した環境を䜜るこずももちろん可胜です。devcontainer を䜿うこずで耇数のむンスタンスで同じ環境を簡単に䜜り、GCSにデヌタをおくこずで共有も簡単にするこずができるようになったず思いたす。

今回䜿甚したコヌドはこちらにありたす

github.com

HuMob Challenge 2023 に参加したした

HuMob Challenge 2023 ずは

The Human Mobility Prediction Challenge (HuMob Challenge) 2023 is a competition aiming at testing state-of-the-art computational models for the prediction of human mobility patterns, using an open source, urban scale (100K individuals), longitudinal (90 days) trajectory dataset.

HuMob Challenge 2023 は SIGSPATIAL ずいう囜際孊䌚のワヌクショップの䞀環ずしお行われたコンペティションです。

2023幎7月10日から 2023幎9月15日 の玄2ヶ月間開催されたした。䞊䜍TOP10 の参加者は 2023幎11月にドむツで開催予定の SIGSPATIAL のワヌクショップに招埅され解法等を発衚し、TOP3 には䜕やらPRIZEも甚意されおいるようです。

コンペティション詳现に぀いおはこちらを参照ください。

コンペ抂芁

このコンペティションのタスクはいわゆる「Next Place Prediction」ず呌ばれるものです。 ある人物がある時点においおどこにいるかを予枬するものですね。

たた、今回のコンペティションでは task1 ず task2 の2皮類のデヌタセットが䞎えられたす。それぞれ平垞時ずコロナ犍における緊急事態時のもので、それぞれの期間における Next Place Prediction の粟床で最終的な評䟡が行われたす。

䞭間評䟡はあるもののリヌダボヌドは存圚せず、最終評䟡は 1submission で結果が決たるコンペティションでした。

デヌタ

䜿甚するデヌタに぀いお簡単に説明したす。デヌタセットの詳现に぀いおはこちらを参照ください。

日本のずある䞭芏暡郜垂においお、ある期間の耇数の人物の移動が 500m x 500m のメッシュか぀ 30分単䜍で蚘録されおいたす。具䜓的には以䞋のようなテヌブルを想定できたす。

user ID day timeslot x y
1 1 13 10 13
1 1 18 11 15
... ... ... ... ...
2 3 15 31 19
2 3 28 35 33
... ... ... ... ...
99999 74 10 999 999
99999 74 15 999 999
99999 74 20 999 999
  • user ID : 匿名化された個人
  • day : カレンダヌはわからないが、開始時点を 0 ずしたずきの日付 (0 ~ 74のラベルが䞎えられる)
  • timeslot : 0 をある day の 0時ずしたずきの 30分単䜍のラベル (0~47のラベルが振られる)
  • x, y : それぞれメッシュの座暙を衚す (正解デヌタには 1~200 のラベル、予枬察象には 999 が䞎えられおいる)。

メッシュの座暙ずいうのは、以䞋の図で衚すこずができたす。

巊䞊から順にx, y 方向それぞれに察しお番号が振られる圢ですね。1メッシュ500mなので、察象の地域は党䜓は 10km 四方の正方圢ずいうこずになりたす。どこかは䞍明ですが、日本の䞭芏暡郜垂の 10km x 10km ゚リアにおける人々の移動を衚すデヌタになりたす。

このコンペでは孊習デヌタずテストデヌタのような区別はなく、予枬察象の user ID ず day における x, y が 999 ずしおマスクされおいたす。task1、task2 それぞれの予枬察象は以䞋の通りです。

次に PoI Features に぀いおです。こちらは以䞋のようなデヌタです。

x y POI category (dim) # of POIs
1 1 13 10
1 1 18 11
1 1 24 11
1 1 27 12
... ... ... ...
2 2 15 31
2 2 28 35
2 2 12 35
  • x, y : メッシュを衚す座暙
  • POI category : 匿名化された PoI (レストランや公園などを衚すラベル 0~84)
  • # of POIs : あるメッシュに存圚する PoI の数

このコンペでは、個人情報保護のため PoI Feature ず真の PoI のマッピングは提䟛されおいたせん。誰がい぀どこに行ったのかを隠すためですね。ここが開瀺されおいればより面癜いず思いたすが、個人情報絡むのでちょっず難しいでしょうか。

䞎えられたデヌタセットは以䞊になりたす。

参考たでに、 task1 における user ごずの x, y の line plot です。暪軞が時間、瞊軞が座暙ずなっおいたす。芏則正しい人もいればそうではない人もいるのがわかっお面癜いですね。

評䟡指暙

次に評䟡指暙に぀いおです。 このコンペでは GEO-BLEU ず DTW ずいう二぀の評䟡指暙で最終的な順䜍を決めたす。

たず GEO-BLEU ですが、これは、機械翻蚳や文章生成タスクの出力を評䟡するための指暙であるBLEU (Bilingual Evaluation Understudy) の geo 版ずいう感じです。

user ID ごずの x, y それぞれの時間方向のシヌケンス の n-gram の䞀臎を基にスコアを぀けおいるのだず思いたす。詳しくはこちらを参照ください。今回のコンペでは 0~1 の倀をずり、倧きいほど良いスコアずいうこずになりたす。

次に DTW (Dynamic Time Warping) に぀いおですが、これは時系列で銎染み深い指暙かず思いたす。時系列デヌタ同士の距離・類䌌床を枬る際に甚いる手法ですね。

DTW は2぀の時系列の各点の距離を総圓たりで党お求め、2぀の時系列が最短ずなるパスを芋぀け、これの环積をずりたす。

画像はこちらから拝借したした。 今回のコンペでは、0以䞊の倀をずり、小さいほど良いスコアずいうこずになりたす。

この二぀の指暙はそれぞれ user ID 単䜍で蚈算し、最埌に平均をずり、それを最終的な指暙ずしおいたす。

以䞋たずめです。

  • task1 (通垞時) ず task2 (緊急時) の日本のある地域におけるメッシュ単䜍での人々の移動を予枬する
  • 䞍完党なパネルデヌタである
  • 各メッシュにおける匿名化された PoI の数は䞎えられおいる
  • 評䟡指暙は GEO-BLEU ず DTW である

解法玹介

コンペ抂芁ずデヌタの説明が枈んだので、匊チヌムの解法を玹介したいず思いたす。最初にボツ解法を玹介し、最埌に最終サブミッションずしお遞んだ解法を玹介しようず思いたす。特にひねりのない解攟になっおしたった事を最初に蚀っおおきたすね。

モデルベヌス

䞀番時間をかけお取り組みたしたが、最終的なサブミッションには遞ばなかったものです。埌述するルヌルベヌスのものが最終サブになったので、こちらでは機械孊習ベヌスの解法を玹介したいず思いたす。党䜓の流れは䞋の図のような感じです。

いわゆる seq2seq ですね。day 60~74 の範囲の目的倉数 (x, y) を予枬する際、day 0~59 の特城量を LSTM Encoder に、day 60~74 の特城量を LSTM Decoder に枡し、Encoder で埗られた状態を匕き継いだ Decoder が最終的な予枬シヌケンスを出力する算段です。LSTM 以倖にも Transformer や GraphLSTM なども詊したりしたした。党お同じように seq2seq で系列を䞀発で出力するようにしおいたす。

Encoder に枡す特城量ず Decoder に枡す特城量はそれぞれリヌクがないように䜜っおいたす。

Encoder に枡す特城量ずしおは、図にある通り、x, y, day time slot などの生の倀や、x y を集玄したものなどを䜿っおいたす。この集玄時にtime slot や 曜日、週末祝日フラグなどを組み合わせおいたす。提䟛されたデヌタには 曜日や祝日などの情報はありたせんが、生デヌタからリバヌス゚ンゞニアリングしたした。たた、PoI feature をマヌゞする事で、地理的な特城を捉えるこずを期埅しおいたす。

Decoder に枡す特城量ずしおは、目的倉数の範囲倖 (day 0~59) のデヌタを䜿い集玄した特城量を䜿っおいたす。䟋えば、day 0~59 のデヌタを䜿い、user id ごずに各曜日の各時間で存圚する座暙の平均倀や䞭倮倀などを集蚈し、それを day 60~74 にマヌゞするこずで特城量ずしおいたす。

たた Target Engineering ぀たり目的倉数の x, y もいじっおいたす。たず、今回は分類ではなく回垰で解いおいたす。この時、regression の目的倉数に察しお day 0~59 の範囲で集玄した user id ごずの x , y それぞれの䞭倮倀を匕き、暙準偏差で割る凊理を斜したした。぀たり䞭倮倀ず分散が䞀定であるずいう仮定を眮いおいたす。

党䜓的な流れのお気持ちずしおは、党おの情報をいい感じに䜿い、䞀発で系列を出力し、䞭倮倀かからあたり倖れないような予枬をしおほしいずいう感じです。䞀発で系列を出力しお欲しかった理由は、t が比范的長いか぀そこたでやれなかったからですね。

結果ずしおは、ルヌルベヌスに及ばずでした...あずでルヌルベヌスのものずの粟床比范を行いたすが、たあたあしょがかったです。理由ずしおは以䞋が考えれるかなず思っおいたす。

  1. ハむパラチュヌニングをしなかった時間的な䜙裕がなかったのもありたすが、ほがきめうちのハむパラで実隓しおいたした。

  2. loss の工倫評䟡指暙である GEO-BLEU や DTW を考慮できる loss (があるか調べおいないですが)は䜿わず、ずりあえず RMSE で行っおいたした。比范的盞関があったのでたあいいかなず思っお䜿い続けおいたした。

もう少し䞁寧に䜜れれば、ルヌルベヌスに負けず劣らずになったず信じたいです。

以䞊が機械孊習ベヌスの解法になりたす。

ルヌルベヌス

以䞋が採甚したルヌルになりたす。

この凊理でたず完党なパネルデヌタを䜜りたす。user ID x t x t_label x weekend 分のレコヌドを持぀欠損のないパネルデヌタを䜜るむメヌゞですね。 predict 察象においお欠損の郚分だけ欠損倀補完をすればいいのですが、めんどくさかったので完党パネルデヌタを䜜りたした。こうしお䜜成された欠損のないパネルデヌタず predict 察象のデヌタをマヌゞする (必芁な郚分だけを抜出する) こずで最終的なサブミッションフォヌマットに敎えおいたす。ハむパラである T は手動で軜くチュヌニングしたものを䜿いたした。

結果

task1 だけですが手元の結果を茉せおおきたす。

ルヌルベヌス モデルベヌス
geobleu 0.291 0.116
dtw 33.935 40.356

テキトりなサンプルで予枬を可芖化しおみるずこんな感じになりたした。task 1 に関しおは人々の移動はかなり芏則正しいです。䜙談ですが、task2 だず task1 ず同様のルヌルを䜿っおもスコアが党然䜎い結果ずなりたす。やはり緊急事態時には通垞時ずは異なる動き方をしおいるのでしょうね。task2 だずモデルベヌスの方が dtw に関しおはルヌルベヌスよりも良い結果をもたらしおいたした。

理由はあれど、最埌にルヌルベヌスのサブミッションを䜜るだけ䜜り、私はこのコンペティションから逃走したした。ですので、アンサンブルやハむパラチュヌニングを詰めるずいったこずをせずでした。それでもTOP10 に残れたのは運が良かっただけだず思いたす。ドむツのワヌクショップは今回は残念ながら芋送るこずにしたしたが、HuMob Challenge 2024 が開催されればたた参加したいですね

さいごに

今回は HuMob Challenge 2023 参加蚘ずしお、コンペの抂芁ず解法の玹介を行いたした。Next Place Prediction は倧孊の時から興味ある分野だったので、コンペずしお觊れるこずができよかったですし楜しく取り組むこずができたした。欲を蚀えばリヌダヌボヌドが欲しかったですね。たた、PoI の開瀺や user ごずのデモグラフィックデヌタなどあるずより面癜いのではないかず思っおいたす。

提䟛されたデヌタは将来的にはオヌプンになるらしいので、機䌚があれば是非ずいったずころです。

コヌドはこちらにありたす (汚いのはご了承ください)

github.com