pytorchでデータ数を増やすとやけに学習時間が増えるバグ
pytorchで学習する処理を書いた際、データセット内のデータ数の増加により学習時間が増えた。
データ数増加で学習時間が増えるのは当然だろうと思うかもしれないが、今回書いていた処理はデータセットのすべてを学習に使わないもので、指定したbatch数分だけminibatchを作って学習するのを1epochにしていた。なのでデータセット内のデータ数が増えても処理にかかる時間はほとんど変わらない想定だったのでおかしい。
その増加分というのもやけに大きく、cProfileで1epoch分を計測してtottime順にみると以下のようになった。
# 8000件のデータセット ncalls tottime percall cumtime percall filename:lineno(function) 20 1.491 0.075 1.491 0.075 {method 'run_backward' of 'torch._C._EngineBase' objects} 1 0.112 0.112 0.112 0.112 {built-in method _tkinter.create}
25000件のデータセット ncalls tottime percall cumtime percall filename:lineno(function) 20 8.083 0.404 8.083 0.404 {method 'run_backward' of 'torch._C._EngineBase' objects} 1 0.104 0.104 0.104 0.104 {built-in method _tkinter.create}
backwardの計算にほとんどの時間がかかっている。
これを念頭にコードを見てみるとデータをロードしてtensorとする部分でrequires_grad=Trueとしてしまっていることに気づいた。なのでこれをFalseとして実行したところデータ数による処理時間の変動はなくなった。
requires_grad=Trueとすることでbackwardを行った際に計算グラフから勾配が計算されるが、データ全部にそれが適用されてしまったためデータ数に応じてbackwardの処理時間が増えるという事態になっていた。
kedroで01_rawのディレクトリ構造に合わせて各フェーズのデータを出力する
以前kedroのドキュメントを見たときに推奨されるデータ管理用のディレクトリ構造が定義されているのを見つけた。
https://kedro.readthedocs.io/en/stable/12_faq/01_faq.html#what-is-data-engineering-convention
整理しやすそうだと思ったため自前のスクリプトを書く際にも流用していたのだが、なかなかよかったのでkedro自体も実際に使ってみることにした。
全データの中から範囲を変えてグルーピングしていくつかのデータセットにしたいことがあったのだが、素のままでやる場合
- それぞれグループ毎にcatalog.ymlに定義を追加
- 追加した定義を利用するようpipelineの定義を追加
とする必要がありそうだった。設定やコードとして書かれることでわかりやすいとも言えるのだが、これがもっと増えた場合は似たような定義が並んでわかりづらいのと、そもそもデータを変えるためだけに毎回これを追記していくのはちょっと面倒。
なのでもっと簡単にできないか調べてみたところこんな記事を見つけた
Kedroにおける特徴量のバージョニング方法の一案 - Qiita
catalog.ymlでtemplate変数を使うというのはまさにイメージしてた解決策なのでこちらの方針でやってみることにした。
template変数を導入する方法自体は公式のドキュメントにも書いてある。
https://kedro.readthedocs.io/en/0.17.6/04_kedro_project_setup/02_configuration.html?highlight=template#template-configuration
kedro run実行時に引数から設定できるようにしたいが、今回はparameters.ymlをcli上から上書きする際の方法を流用することにした。
hooks.pyを以下のよう変更
def register_config_loader( self, conf_paths: Iterable[str], env: str, extra_params: Dict[str, Any], ) -> ConfigLoader: return TemplatedConfigLoader( conf_paths, globals_pattern="*globals.yml", # read the globals dictionary from project config globals_dict=extra_params, )
extra_paramsにparamsのオプションで渡した値が入ってきて、globals.ymlで定義されている値を上書きしてくれる。
これにより、globals.ymlにてデフォルトの設定を書きつつ以下のような形で引数による変更が可能
kedro run --params "dataset:example_dataset"
catalog.ymlの設定としては
raw_data: type: pandas.CSVDataSet path: data/01_raw/${dataset}/data.csv primary_data: type: pandas.CSVDataSet path: data/03_primary/${dataset}/data.csv ...
のように各フェーズで利用するcatalogのpathとして${dataset}を含むようにしておく。あとは01_rawに使いたいデータを用意した上でdatasetをparamsで渡せばそれに応じたデータを使ってパイプラインの処理を行える。
pandasインストール時にエラー(pip 1.20.1)
以下の実行時にnumpyのインストールでエラーとなった。
pip install pandas==1.3.4
1.3.4は別の環境にて最近インストールした記憶があり、そちらは普通に成功していたはずだったので違いを調べてみたところpipのversionが違った。
失敗したのはpip1.20.1で、成功したのはpip1.21.0だった。
なのでその違いを調べて見るとどうやらパッケージの依存関係を解決するためのresolverが変更された模様。
https://www.python.jp/pages/2020-10-07-new-pip-deps.html
1.20.3以前はそのときinstallしようとしているパッケージの依存関係のみを考慮して条件を満たすversionのものを入れており、それ以前に入れたパッケージの依存関係を壊す可能性があった。一方、1.20.3以降では入っているものとこれから入れるものの全体を考慮してパッケージのversionを決める。
1.21系では新しい方法がデフォルトで使われるため上記のように挙動が変わったと思われる。
こちらにpackage manager全般の話として詳しく書いている記事があった。
https://vaaaaaanquish.hatenablog.com/entry/2021/03/29/221715#2020-resolver
backtrackingというアルゴリズムで依存解決をしているようで、各パッケージとそのversionの組み合わせを深さ優先探索して必要な依存関係に適するものを探す、という形の模様。backtrackingついてはこちらがわかりやすかった。
https://www.simplilearn.com/tutorials/data-structure-tutorial/backtracking-algorithm
実際に採用されたのはこちらのissueにかかれている通りでresolvelibというパッケージになっている。
https://github.com/pypa/pip/issues/7406#issuecomment-608075215