研究をしているときにgit
でバージョン管理をすることは少なくないと思います。
- 複数人で開発を行っている場合
- 同じファイル構造をローカルPCとリモートサーバーなど2箇所に保存し、実行する場所(サーバー・GPUなど)とコードを書く場所(PC)を分けて開発する場合
などでお世話になる機会が多く、git
に足を向けて寝られないエンジニアがほとんどなのではないでしょうか。
ある日、筆者はこのようなケースにぶち当たりました。
- かなり前の学習モデルの動作確認をしようとしたが、モデル構造が以前と今では変更されており、どのバージョンまで戻せばいいのか分からない
-
nohup
実行したときにnohup.out
のどの部分にログが残っているのか分からない
このとき、git reset
やgit reflog
,git revert
を駆使して頑張ってバージョンを戻すことも可能ですが、間違えて今のファイルを全部消してしまうリスクもありますし、元のバージョンに戻すのが面倒くさいケースもあります。
極論言えば、タイムスタンプ付きのログ用フォルダを用意して、そこに移動すれば元のファイルを実行できる、というような環境を作りたいと考えました。
たとえばdataset
とmodels
を外部からインポートしている場合、こうすれば当時の実行環境を再現できると思います。
- フォルダ構造
-root
-models
-model_hogehoge.py
-datasets
-dataset_hogehoge.py
-main.py
-logs
- コマンドライン、
main.py
の実行例
.../root$ python main.py #実行コマンド
-
main.py
の書き方の例、このような形式で実行すると後で実行ログを振り返るのが楽ちんに。
main.py
from models import model_hogehoge
from datasets import dataset_hogehoge
import shutil
import os
import sys
from datetime import datetime, UTC, timedelta
if __name__ == "__main__":
utc_time = datetime.now(UTC)
#タイムスタンプ
execution_time_str = (utc_time + timedelta(hours=9)).strftime('%Y%m%d_%H%M')
log_folder = f"./logs/{execution_time_str}"
#タイムスタンプを基にログフォルダを作成
os.makedirs(log_folder, exist_ok=True)
print(f"logs saved to {log_folder}") #この行だけ普通に標準出力
# stdoutとstderrをログファイルに出力
# このとき、実行ファイル名("main.py")はsys.argv[0]に格納されている
out_f = open(f"{log_folder}/{sys.argv[0]}.log","w",encoding="utf-8")
sys.stdout = out_f
sys.stderr = out_f
# 実行ファイルをログフォルダにコピー
new_path = shutil.copy(f"{sys.argv[0]}",log_folder)
parent_directory = '.'
# 別ディレクトリからmain.pyを走らせていた場合
# ただし兄弟ディレクトリとか(`../`)のケースは想定してないので注意
if '/' in sys.argv[0]:
parent_directory = '/'.join(sys.argv[0].split('/')[:-1])
# importが必要なモジュールも入れておく
datasets_path = shutil.copytree(f"{parent_directory}/datasets",f"{log_folder}/datasets")
models_path = shutil.copytree(f"{parent_directory}/models",f"{log_folder}/models")
#### ここに実行したいことを書く ####
out_f.close()
できれば外部モジュールからimportしたものを自動でlog_folderにコピってくれるものも作りたいけど、やり方が分からず断念。
もしやり方が分かれば追記します
※このコードを応用してログを保存する場合、gitと併用するならばgitignoreするのを忘れずに(2024/6/21追記)