From 47c92550e36a93be020ebbd9bd88d0795014e623 Mon Sep 17 00:00:00 2001 From: facebook-github-bot Date: Thu, 5 Dec 2019 12:56:49 -0800 Subject: [PATCH] Initial commit fbshipit-source-id: 5a7576eed3413a21a0ba0f620b59d45f695a253f --- .circleci/config.yml | 157 ++++ .github/ISSUE_TEMPLATE/bug-report.md | 53 ++ .github/ISSUE_TEMPLATE/documentation.md | 9 + .github/ISSUE_TEMPLATE/feature-request.md | 24 + .../ISSUE_TEMPLATE/questions-help-support.md | 8 + .gitignore | 52 ++ .isort.cfg | 2 + CHANGELOG.md | 3 + CODE_OF_CONDUCT.md | 76 ++ CONTRIBUTING.md | 28 + LICENSE | 21 + MANIFEST.in | 4 + README.md | 86 ++ bin/classy-project | 55 ++ classy_train.py | 167 ++++ classy_vision/__init__.py | 7 + classy_vision/configs/hmdb51/r3d34.json | 121 +++ .../densenet121_imagenet_classy_config.json | 65 ++ .../imagenet/postactivated_r2d101.json | 99 +++ .../configs/imagenet/postactivated_r2d50.json | 99 +++ .../configs/imagenet/preactivated_r2d101.json | 99 +++ .../configs/imagenet/preactivated_r2d50.json | 99 +++ .../resnet101_imagenet_classy_config.json | 74 ++ .../resnet50_imagenet_classy_config.json | 74 ++ .../resnet50_label_smoothing_criterion.json | 77 ++ .../resnext101_32x4d_fine_tuning_config.json | 78 ++ .../resnext101_imagenet_classy_config.json | 75 ++ .../resnext50_imagenet_classy_config.json | 75 ++ .../kinetics400/postactivated_i3d50.json | 111 +++ .../postactivated_i3d50_fine_tuning.json | 113 +++ .../kinetics400/preactivated_i3d50.json | 111 +++ .../preactivated_i3d50_fine_tuning.json | 113 +++ .../configs/r3d34_synthetic_video.json | 80 ++ ...esnet50_synthetic_image_classy_config.json | 74 ++ classy_vision/configs/ucf101/r3d34.json | 121 +++ classy_vision/dataset/README.md | 3 + classy_vision/dataset/__init__.py | 91 +++ classy_vision/dataset/classy_cifar.py | 80 ++ classy_vision/dataset/classy_dataset.py | 189 +++++ classy_vision/dataset/classy_hmdb51.py | 185 +++++ classy_vision/dataset/classy_imagenet.py | 60 ++ classy_vision/dataset/classy_kinetics400.py | 190 +++++ .../dataset/classy_synthetic_image.py | 103 +++ .../dataset/classy_synthetic_video.py | 133 ++++ classy_vision/dataset/classy_ucf101.py | 184 +++++ classy_vision/dataset/classy_video_dataset.py | 261 ++++++ classy_vision/dataset/core/__init__.py | 17 + classy_vision/dataset/core/list_dataset.py | 44 + .../dataset/core/random_image_datasets.py | 88 ++ .../dataset/core/random_video_datasets.py | 70 ++ classy_vision/dataset/generic/hive.py | 123 +++ classy_vision/dataset/image_path_dataset.py | 130 +++ classy_vision/dataset/transforms/__init__.py | 116 +++ .../dataset/transforms/classy_transform.py | 32 + .../dataset/transforms/lighting_transform.py | 67 ++ classy_vision/dataset/transforms/util.py | 379 +++++++++ .../dataset/transforms/util_video.py | 378 +++++++++ classy_vision/distributed/__init__.py | 5 + classy_vision/distributed/launch_ray.py | 221 ++++++ classy_vision/generic/__init__.py | 5 + classy_vision/generic/args.py | 47 ++ classy_vision/generic/debug.py | 20 + classy_vision/generic/distributed_util.py | 147 ++++ classy_vision/generic/opts.py | 205 +++++ classy_vision/generic/pdb.py | 48 ++ classy_vision/generic/perf_stats.py | 237 ++++++ classy_vision/generic/profiler.py | 337 ++++++++ classy_vision/generic/registry_utils.py | 50 ++ classy_vision/generic/util.py | 749 ++++++++++++++++++ classy_vision/generic/visualize.py | 223 ++++++ classy_vision/heads/__init__.py | 91 +++ classy_vision/heads/classy_head.py | 59 ++ classy_vision/heads/fully_connected_head.py | 63 ++ .../heads/fully_convolutional_linear_head.py | 132 +++ classy_vision/heads/identity_head.py | 33 + classy_vision/hooks/__init__.py | 42 + classy_vision/hooks/checkpoint_hook.py | 115 +++ classy_vision/hooks/classy_hook.py | 174 ++++ .../exponential_moving_average_model_hook.py | 127 +++ .../hooks/loss_lr_meter_logging_hook.py | 112 +++ classy_vision/hooks/model_complexity_hook.py | 56 ++ classy_vision/hooks/model_tensorboard_hook.py | 79 ++ classy_vision/hooks/profiler_hook.py | 46 ++ classy_vision/hooks/progress_bar_hook.py | 70 ++ classy_vision/hooks/tensorboard_plot_hook.py | 138 ++++ classy_vision/hooks/time_metrics_hook.py | 98 +++ classy_vision/hooks/visdom_hook.py | 121 +++ classy_vision/hub/__init__.py | 10 + classy_vision/hub/classy_hub_interface.py | 177 +++++ classy_vision/hydra/args.yaml | 21 + .../hydra/config/resnet50_synthetic.yaml | 63 ++ classy_vision/hydra/dataset/imagenet.yaml | 12 + .../hydra/dataset/synthetic_image.yaml | 36 + classy_vision/hydra/loss/cross_entropy.yaml | 3 + .../loss/label_smoothing_cross_entropy.yaml | 4 + classy_vision/hydra/meters/accuracy.yaml | 4 + classy_vision/hydra/model/resnet_50.yaml | 12 + classy_vision/hydra/optimizer/sgd.yaml | 5 + classy_vision/hydra/param_scheduler/step.yaml | 5 + .../hydra/task/classification_task.yaml | 3 + classy_vision/losses/__init__.py | 115 +++ classy_vision/losses/barron_loss.py | 78 ++ classy_vision/losses/classy_loss.py | 44 + classy_vision/losses/label_smoothing_loss.py | 106 +++ classy_vision/losses/multi_output_sum_loss.py | 50 ++ .../losses/soft_target_cross_entropy_loss.py | 83 ++ classy_vision/losses/sum_arbitrary_loss.py | 67 ++ classy_vision/meters/__init__.py | 85 ++ classy_vision/meters/accuracy_meter.py | 179 +++++ classy_vision/meters/classy_meter.py | 116 +++ classy_vision/meters/precision_meter.py | 210 +++++ classy_vision/meters/recall_meter.py | 210 +++++ classy_vision/meters/video_accuracy_meter.py | 138 ++++ classy_vision/models/__init__.py | 108 +++ classy_vision/models/classy_block.py | 34 + classy_vision/models/classy_model.py | 304 +++++++ classy_vision/models/classy_model_wrapper.py | 59 ++ classy_vision/models/densenet.py | 263 ++++++ classy_vision/models/mlp.py | 99 +++ classy_vision/models/resnet.py | 33 + classy_vision/models/resnext.py | 409 ++++++++++ classy_vision/models/resnext3d.py | 421 ++++++++++ classy_vision/models/resnext3d_block.py | 465 +++++++++++ classy_vision/models/resnext3d_stage.py | 193 +++++ classy_vision/models/resnext3d_stem.py | 206 +++++ classy_vision/optim/__init__.py | 76 ++ classy_vision/optim/classy_optimizer.py | 257 ++++++ .../optim/param_scheduler/__init__.py | 94 +++ .../classy_vision_param_scheduler.py | 70 ++ .../param_scheduler/composite_scheduler.py | 143 ++++ .../param_scheduler/constant_scheduler.py | 40 + .../optim/param_scheduler/cosine_scheduler.py | 55 ++ .../optim/param_scheduler/linear_scheduler.py | 50 ++ .../param_scheduler/multi_step_scheduler.py | 108 +++ .../polynomial_decay_scheduler.py | 52 ++ .../optim/param_scheduler/step_scheduler.py | 57 ++ .../step_with_fixed_gamma_scheduler.py | 80 ++ classy_vision/optim/rmsprop.py | 106 +++ classy_vision/optim/sgd.py | 95 +++ classy_vision/tasks/__init__.py | 80 ++ classy_vision/tasks/classification_task.py | 714 +++++++++++++++++ classy_vision/tasks/classy_task.py | 156 ++++ classy_vision/tasks/fine_tuning_task.py | 99 +++ .../synthetic/configs/template_config.json | 66 ++ .../templates/synthetic/datasets/__init__.py | 15 + .../synthetic/datasets/my_dataset.py | 62 ++ .../templates/synthetic/losses/__init__.py | 15 + .../templates/synthetic/losses/my_loss.py | 20 + .../templates/synthetic/models/__init__.py | 15 + .../templates/synthetic/models/my_model.py | 29 + classy_vision/trainer/__init__.py | 12 + classy_vision/trainer/classy_trainer.py | 96 +++ classy_vision/trainer/distributed_trainer.py | 94 +++ classy_vision/trainer/elastic_trainer.py | 266 +++++++ classy_vision/trainer/local_trainer.py | 47 ++ examples/ray/cluster_config.yml | 165 ++++ examples/ray/requirements.txt | 2 + hubconf.py | 32 + requirements.txt | 2 + scripts/build_docs.sh | 96 +++ scripts/parse_sphinx.py | 77 ++ scripts/parse_tutorials.py | 114 +++ setup.py | 66 ++ sphinx/Makefile | 20 + sphinx/conf.py | 102 +++ sphinx/dataset.rst | 5 + sphinx/heads.rst | 5 + sphinx/hooks.rst | 5 + sphinx/index.rst | 34 + sphinx/losses.rst | 5 + sphinx/make.bat | 35 + sphinx/meters.rst | 5 + sphinx/models.rst | 5 + sphinx/optim.rst | 5 + sphinx/param_scheduler.rst | 5 + sphinx/tasks.rst | 5 + sphinx/trainer.rst | 5 + sphinx/transforms.rst | 16 + test/__init__.py | 5 + test/api_test.py | 177 +++++ test/classy_block_test.py | 74 ++ test/classy_vision_head_test.py | 67 ++ test/dataset_classy_dataset_test.py | 258 ++++++ test/dataset_classy_imagenet_test.py | 74 ++ test/dataset_classy_video_dataset_test.py | 154 ++++ test/dataset_image_path_dataset_test.py | 120 +++ ...aset_transforms_lighting_transform_test.py | 35 + test/dataset_transforms_test.py | 82 ++ test/dataset_transforms_util_test.py | 161 ++++ test/dataset_transforms_util_video_test.py | 137 ++++ test/generic/__init__.py | 5 + test/generic/config_utils.py | 369 +++++++++ test/generic/merge_dataset.py | 55 ++ test/generic/meter_test_utils.py | 288 +++++++ test/generic/optim_test_util.py | 223 ++++++ test/generic/utils.py | 265 +++++++ test/generic_util_json_blob_test.json | 34 + test/generic_util_test.py | 416 ++++++++++ test/hooks_checkpoint_hook_test.py | 127 +++ test/hooks_classy_hook_test.py | 40 + ...onential_moving_average_model_hook_test.py | 112 +++ test/hooks_loss_lr_meter_logging_hook_test.py | 112 +++ test/hooks_profiler_hook_test.py | 51 ++ test/hooks_time_metrics_hook_test.py | 127 +++ test/hub_classy_hub_interface_test.py | 91 +++ test/losses_barron_loss_test.py | 66 ++ test/losses_generic_utils_test.py | 24 + ...label_smoothing_cross_entropy_loss_test.py | 212 +++++ test/losses_multi_output_sum_loss_test.py | 42 + ...ses_soft_target_cross_entropy_loss_test.py | 84 ++ test/losses_sum_arbitrary_loss_test.py | 101 +++ test/losses_test.py | 61 ++ .../hooks_model_complexity_hook_test.py | 63 ++ .../hooks_model_tensorboard_hook_test.py | 53 ++ test/manual/hooks_progress_bar_hook_test.py | 89 +++ .../hooks_tensorboard_plot_hook_test.py | 158 ++++ test/manual/hooks_visdom_hook_test.py | 121 +++ .../manual/models_classy_vision_model_test.py | 113 +++ test/meters_accuracy_meter_test.py | 137 ++++ test/meters_precision_meter_test.py | 191 +++++ test/meters_recall_meter_test.py | 192 +++++ test/meters_video_accuracy_meter_test.py | 207 +++++ test/models_classy_model_test.py | 77 ++ test/models_classy_model_wrapper_test.py | 83 ++ test/models_densenet_test.py | 53 ++ test/models_mlp_test.py | 26 + test/models_resnext3d_test.py | 236 ++++++ test/models_resnext_test.py | 85 ++ test/optim_param_scheduler_composite_test.py | 254 ++++++ test/optim_param_scheduler_constant_test.py | 49 ++ test/optim_param_scheduler_cosine_test.py | 95 +++ test/optim_param_scheduler_linear_test.py | 67 ++ test/optim_param_scheduler_multi_step_test.py | 126 +++ test/optim_param_scheduler_polynomial_test.py | 58 ++ test/optim_param_scheduler_step_test.py | 75 ++ ...am_scheduler_step_with_fixed_gamma_test.py | 83 ++ test/optim_param_scheduler_test.py | 179 +++++ test/optim_rmsprop_test.py | 27 + test/optim_sgd_test.py | 25 + test/suites.py | 48 ++ test/tasks_classification_task_test.py | 112 +++ test/tasks_fine_tuning_task_test.py | 112 +++ test/trainer_distributed_trainer_test.py | 64 ++ test/trainer_local_trainer_test.py | 42 + tutorials/classy_dataset.ipynb | 615 ++++++++++++++ tutorials/classy_loss.ipynb | 174 ++++ tutorials/classy_model.ipynb | 195 +++++ tutorials/fine_tuning.ipynb | 693 ++++++++++++++++ tutorials/getting_started.ipynb | 559 +++++++++++++ tutorials/pet_aws.ipynb | 295 +++++++ tutorials/ray_aws.ipynb | 255 ++++++ tutorials/torchscript.ipynb | 142 ++++ tutorials/video_classification.ipynb | 313 ++++++++ tutorials/wsl_model_predict.ipynb | 167 ++++ website/README.md | 193 +++++ website/core/Footer.js | 111 +++ website/core/Tutorial.js | 83 ++ website/core/TutorialSidebar.js | 92 +++ website/package.json | 14 + website/pages/en/help.js | 54 ++ website/pages/en/index.js | 215 +++++ website/pages/en/users.js | 48 ++ website/pages/tutorials/index.js | 58 ++ website/sidebars.json | 10 + website/siteConfig.js | 114 +++ website/static/css/alabaster.css | 716 +++++++++++++++++ website/static/css/basic.css | 736 +++++++++++++++++ website/static/css/code_block_buttons.css | 39 + website/static/css/custom.css | 310 ++++++++ website/static/img/cv-icon.png | Bin 0 -> 51451 bytes website/static/img/cv-logo.png | Bin 0 -> 69847 bytes website/static/img/cv-logo.svg | 1 + website/static/img/expanding_arrows.svg | 1 + website/static/img/favicon.ico | Bin 0 -> 766 bytes website/static/img/favicon.png | Bin 0 -> 1742 bytes website/static/img/landing-background.jpg | Bin 0 -> 217910 bytes website/static/img/modular.svg | 1 + website/static/img/multi-modal.png | Bin 0 -> 21240 bytes website/static/img/oss_logo.png | Bin 0 -> 4370 bytes website/static/img/pytorch_logo.svg | 13 + website/static/js/code_block_buttons.js | 47 ++ website/tutorials.json | 32 + 282 files changed, 31438 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/documentation.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/questions-help-support.md create mode 100644 .gitignore create mode 100644 .isort.cfg create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100755 bin/classy-project create mode 100755 classy_train.py create mode 100644 classy_vision/__init__.py create mode 100644 classy_vision/configs/hmdb51/r3d34.json create mode 100644 classy_vision/configs/imagenet/densenet121_imagenet_classy_config.json create mode 100644 classy_vision/configs/imagenet/postactivated_r2d101.json create mode 100644 classy_vision/configs/imagenet/postactivated_r2d50.json create mode 100644 classy_vision/configs/imagenet/preactivated_r2d101.json create mode 100644 classy_vision/configs/imagenet/preactivated_r2d50.json create mode 100644 classy_vision/configs/imagenet/resnet101_imagenet_classy_config.json create mode 100644 classy_vision/configs/imagenet/resnet50_imagenet_classy_config.json create mode 100644 classy_vision/configs/imagenet/resnet50_label_smoothing_criterion.json create mode 100644 classy_vision/configs/imagenet/resnext101_32x4d_fine_tuning_config.json create mode 100644 classy_vision/configs/imagenet/resnext101_imagenet_classy_config.json create mode 100644 classy_vision/configs/imagenet/resnext50_imagenet_classy_config.json create mode 100644 classy_vision/configs/kinetics400/postactivated_i3d50.json create mode 100644 classy_vision/configs/kinetics400/postactivated_i3d50_fine_tuning.json create mode 100644 classy_vision/configs/kinetics400/preactivated_i3d50.json create mode 100644 classy_vision/configs/kinetics400/preactivated_i3d50_fine_tuning.json create mode 100644 classy_vision/configs/r3d34_synthetic_video.json create mode 100644 classy_vision/configs/resnet50_synthetic_image_classy_config.json create mode 100644 classy_vision/configs/ucf101/r3d34.json create mode 100644 classy_vision/dataset/README.md create mode 100644 classy_vision/dataset/__init__.py create mode 100644 classy_vision/dataset/classy_cifar.py create mode 100644 classy_vision/dataset/classy_dataset.py create mode 100644 classy_vision/dataset/classy_hmdb51.py create mode 100644 classy_vision/dataset/classy_imagenet.py create mode 100644 classy_vision/dataset/classy_kinetics400.py create mode 100644 classy_vision/dataset/classy_synthetic_image.py create mode 100644 classy_vision/dataset/classy_synthetic_video.py create mode 100644 classy_vision/dataset/classy_ucf101.py create mode 100644 classy_vision/dataset/classy_video_dataset.py create mode 100644 classy_vision/dataset/core/__init__.py create mode 100644 classy_vision/dataset/core/list_dataset.py create mode 100644 classy_vision/dataset/core/random_image_datasets.py create mode 100644 classy_vision/dataset/core/random_video_datasets.py create mode 100644 classy_vision/dataset/generic/hive.py create mode 100644 classy_vision/dataset/image_path_dataset.py create mode 100644 classy_vision/dataset/transforms/__init__.py create mode 100644 classy_vision/dataset/transforms/classy_transform.py create mode 100644 classy_vision/dataset/transforms/lighting_transform.py create mode 100644 classy_vision/dataset/transforms/util.py create mode 100644 classy_vision/dataset/transforms/util_video.py create mode 100644 classy_vision/distributed/__init__.py create mode 100755 classy_vision/distributed/launch_ray.py create mode 100644 classy_vision/generic/__init__.py create mode 100644 classy_vision/generic/args.py create mode 100644 classy_vision/generic/debug.py create mode 100644 classy_vision/generic/distributed_util.py create mode 100644 classy_vision/generic/opts.py create mode 100644 classy_vision/generic/pdb.py create mode 100644 classy_vision/generic/perf_stats.py create mode 100644 classy_vision/generic/profiler.py create mode 100644 classy_vision/generic/registry_utils.py create mode 100644 classy_vision/generic/util.py create mode 100644 classy_vision/generic/visualize.py create mode 100644 classy_vision/heads/__init__.py create mode 100644 classy_vision/heads/classy_head.py create mode 100644 classy_vision/heads/fully_connected_head.py create mode 100644 classy_vision/heads/fully_convolutional_linear_head.py create mode 100644 classy_vision/heads/identity_head.py create mode 100644 classy_vision/hooks/__init__.py create mode 100644 classy_vision/hooks/checkpoint_hook.py create mode 100644 classy_vision/hooks/classy_hook.py create mode 100644 classy_vision/hooks/exponential_moving_average_model_hook.py create mode 100644 classy_vision/hooks/loss_lr_meter_logging_hook.py create mode 100644 classy_vision/hooks/model_complexity_hook.py create mode 100644 classy_vision/hooks/model_tensorboard_hook.py create mode 100644 classy_vision/hooks/profiler_hook.py create mode 100644 classy_vision/hooks/progress_bar_hook.py create mode 100644 classy_vision/hooks/tensorboard_plot_hook.py create mode 100644 classy_vision/hooks/time_metrics_hook.py create mode 100644 classy_vision/hooks/visdom_hook.py create mode 100644 classy_vision/hub/__init__.py create mode 100644 classy_vision/hub/classy_hub_interface.py create mode 100644 classy_vision/hydra/args.yaml create mode 100644 classy_vision/hydra/config/resnet50_synthetic.yaml create mode 100644 classy_vision/hydra/dataset/imagenet.yaml create mode 100644 classy_vision/hydra/dataset/synthetic_image.yaml create mode 100644 classy_vision/hydra/loss/cross_entropy.yaml create mode 100644 classy_vision/hydra/loss/label_smoothing_cross_entropy.yaml create mode 100644 classy_vision/hydra/meters/accuracy.yaml create mode 100644 classy_vision/hydra/model/resnet_50.yaml create mode 100644 classy_vision/hydra/optimizer/sgd.yaml create mode 100644 classy_vision/hydra/param_scheduler/step.yaml create mode 100644 classy_vision/hydra/task/classification_task.yaml create mode 100644 classy_vision/losses/__init__.py create mode 100644 classy_vision/losses/barron_loss.py create mode 100644 classy_vision/losses/classy_loss.py create mode 100644 classy_vision/losses/label_smoothing_loss.py create mode 100644 classy_vision/losses/multi_output_sum_loss.py create mode 100644 classy_vision/losses/soft_target_cross_entropy_loss.py create mode 100644 classy_vision/losses/sum_arbitrary_loss.py create mode 100644 classy_vision/meters/__init__.py create mode 100644 classy_vision/meters/accuracy_meter.py create mode 100644 classy_vision/meters/classy_meter.py create mode 100644 classy_vision/meters/precision_meter.py create mode 100644 classy_vision/meters/recall_meter.py create mode 100644 classy_vision/meters/video_accuracy_meter.py create mode 100644 classy_vision/models/__init__.py create mode 100644 classy_vision/models/classy_block.py create mode 100644 classy_vision/models/classy_model.py create mode 100644 classy_vision/models/classy_model_wrapper.py create mode 100644 classy_vision/models/densenet.py create mode 100644 classy_vision/models/mlp.py create mode 100644 classy_vision/models/resnet.py create mode 100644 classy_vision/models/resnext.py create mode 100644 classy_vision/models/resnext3d.py create mode 100644 classy_vision/models/resnext3d_block.py create mode 100644 classy_vision/models/resnext3d_stage.py create mode 100644 classy_vision/models/resnext3d_stem.py create mode 100644 classy_vision/optim/__init__.py create mode 100644 classy_vision/optim/classy_optimizer.py create mode 100644 classy_vision/optim/param_scheduler/__init__.py create mode 100644 classy_vision/optim/param_scheduler/classy_vision_param_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/composite_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/constant_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/cosine_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/linear_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/multi_step_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/polynomial_decay_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/step_scheduler.py create mode 100644 classy_vision/optim/param_scheduler/step_with_fixed_gamma_scheduler.py create mode 100644 classy_vision/optim/rmsprop.py create mode 100644 classy_vision/optim/sgd.py create mode 100644 classy_vision/tasks/__init__.py create mode 100644 classy_vision/tasks/classification_task.py create mode 100644 classy_vision/tasks/classy_task.py create mode 100644 classy_vision/tasks/fine_tuning_task.py create mode 100644 classy_vision/templates/synthetic/configs/template_config.json create mode 100644 classy_vision/templates/synthetic/datasets/__init__.py create mode 100644 classy_vision/templates/synthetic/datasets/my_dataset.py create mode 100644 classy_vision/templates/synthetic/losses/__init__.py create mode 100644 classy_vision/templates/synthetic/losses/my_loss.py create mode 100644 classy_vision/templates/synthetic/models/__init__.py create mode 100644 classy_vision/templates/synthetic/models/my_model.py create mode 100644 classy_vision/trainer/__init__.py create mode 100644 classy_vision/trainer/classy_trainer.py create mode 100644 classy_vision/trainer/distributed_trainer.py create mode 100644 classy_vision/trainer/elastic_trainer.py create mode 100644 classy_vision/trainer/local_trainer.py create mode 100644 examples/ray/cluster_config.yml create mode 100644 examples/ray/requirements.txt create mode 100644 hubconf.py create mode 100644 requirements.txt create mode 100755 scripts/build_docs.sh create mode 100644 scripts/parse_sphinx.py create mode 100644 scripts/parse_tutorials.py create mode 100644 setup.py create mode 100644 sphinx/Makefile create mode 100644 sphinx/conf.py create mode 100644 sphinx/dataset.rst create mode 100644 sphinx/heads.rst create mode 100644 sphinx/hooks.rst create mode 100644 sphinx/index.rst create mode 100644 sphinx/losses.rst create mode 100644 sphinx/make.bat create mode 100644 sphinx/meters.rst create mode 100644 sphinx/models.rst create mode 100644 sphinx/optim.rst create mode 100644 sphinx/param_scheduler.rst create mode 100644 sphinx/tasks.rst create mode 100644 sphinx/trainer.rst create mode 100644 sphinx/transforms.rst create mode 100644 test/__init__.py create mode 100644 test/api_test.py create mode 100644 test/classy_block_test.py create mode 100644 test/classy_vision_head_test.py create mode 100644 test/dataset_classy_dataset_test.py create mode 100644 test/dataset_classy_imagenet_test.py create mode 100644 test/dataset_classy_video_dataset_test.py create mode 100644 test/dataset_image_path_dataset_test.py create mode 100644 test/dataset_transforms_lighting_transform_test.py create mode 100644 test/dataset_transforms_test.py create mode 100644 test/dataset_transforms_util_test.py create mode 100644 test/dataset_transforms_util_video_test.py create mode 100644 test/generic/__init__.py create mode 100644 test/generic/config_utils.py create mode 100644 test/generic/merge_dataset.py create mode 100644 test/generic/meter_test_utils.py create mode 100644 test/generic/optim_test_util.py create mode 100644 test/generic/utils.py create mode 100644 test/generic_util_json_blob_test.json create mode 100644 test/generic_util_test.py create mode 100644 test/hooks_checkpoint_hook_test.py create mode 100644 test/hooks_classy_hook_test.py create mode 100644 test/hooks_exponential_moving_average_model_hook_test.py create mode 100644 test/hooks_loss_lr_meter_logging_hook_test.py create mode 100644 test/hooks_profiler_hook_test.py create mode 100644 test/hooks_time_metrics_hook_test.py create mode 100644 test/hub_classy_hub_interface_test.py create mode 100644 test/losses_barron_loss_test.py create mode 100644 test/losses_generic_utils_test.py create mode 100644 test/losses_label_smoothing_cross_entropy_loss_test.py create mode 100644 test/losses_multi_output_sum_loss_test.py create mode 100644 test/losses_soft_target_cross_entropy_loss_test.py create mode 100644 test/losses_sum_arbitrary_loss_test.py create mode 100644 test/losses_test.py create mode 100644 test/manual/hooks_model_complexity_hook_test.py create mode 100644 test/manual/hooks_model_tensorboard_hook_test.py create mode 100644 test/manual/hooks_progress_bar_hook_test.py create mode 100644 test/manual/hooks_tensorboard_plot_hook_test.py create mode 100644 test/manual/hooks_visdom_hook_test.py create mode 100644 test/manual/models_classy_vision_model_test.py create mode 100644 test/meters_accuracy_meter_test.py create mode 100644 test/meters_precision_meter_test.py create mode 100644 test/meters_recall_meter_test.py create mode 100644 test/meters_video_accuracy_meter_test.py create mode 100644 test/models_classy_model_test.py create mode 100644 test/models_classy_model_wrapper_test.py create mode 100644 test/models_densenet_test.py create mode 100644 test/models_mlp_test.py create mode 100644 test/models_resnext3d_test.py create mode 100644 test/models_resnext_test.py create mode 100644 test/optim_param_scheduler_composite_test.py create mode 100644 test/optim_param_scheduler_constant_test.py create mode 100644 test/optim_param_scheduler_cosine_test.py create mode 100644 test/optim_param_scheduler_linear_test.py create mode 100644 test/optim_param_scheduler_multi_step_test.py create mode 100644 test/optim_param_scheduler_polynomial_test.py create mode 100644 test/optim_param_scheduler_step_test.py create mode 100644 test/optim_param_scheduler_step_with_fixed_gamma_test.py create mode 100644 test/optim_param_scheduler_test.py create mode 100644 test/optim_rmsprop_test.py create mode 100644 test/optim_sgd_test.py create mode 100644 test/suites.py create mode 100644 test/tasks_classification_task_test.py create mode 100644 test/tasks_fine_tuning_task_test.py create mode 100644 test/trainer_distributed_trainer_test.py create mode 100644 test/trainer_local_trainer_test.py create mode 100644 tutorials/classy_dataset.ipynb create mode 100644 tutorials/classy_loss.ipynb create mode 100644 tutorials/classy_model.ipynb create mode 100644 tutorials/fine_tuning.ipynb create mode 100644 tutorials/getting_started.ipynb create mode 100644 tutorials/pet_aws.ipynb create mode 100644 tutorials/ray_aws.ipynb create mode 100644 tutorials/torchscript.ipynb create mode 100644 tutorials/video_classification.ipynb create mode 100644 tutorials/wsl_model_predict.ipynb create mode 100644 website/README.md create mode 100644 website/core/Footer.js create mode 100644 website/core/Tutorial.js create mode 100644 website/core/TutorialSidebar.js create mode 100644 website/package.json create mode 100644 website/pages/en/help.js create mode 100755 website/pages/en/index.js create mode 100644 website/pages/en/users.js create mode 100644 website/pages/tutorials/index.js create mode 100644 website/sidebars.json create mode 100644 website/siteConfig.js create mode 100644 website/static/css/alabaster.css create mode 100644 website/static/css/basic.css create mode 100644 website/static/css/code_block_buttons.css create mode 100644 website/static/css/custom.css create mode 100644 website/static/img/cv-icon.png create mode 100644 website/static/img/cv-logo.png create mode 100644 website/static/img/cv-logo.svg create mode 100644 website/static/img/expanding_arrows.svg create mode 100644 website/static/img/favicon.ico create mode 100644 website/static/img/favicon.png create mode 100644 website/static/img/landing-background.jpg create mode 100644 website/static/img/modular.svg create mode 100644 website/static/img/multi-modal.png create mode 100644 website/static/img/oss_logo.png create mode 100644 website/static/img/pytorch_logo.svg create mode 100644 website/static/js/code_block_buttons.js create mode 100644 website/tutorials.json diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..5aed9769cc --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,157 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2 + +# ------------------------------------------------------------------------------------- +# Environments to run the jobs in +# ------------------------------------------------------------------------------------- +cpu: &cpu + docker: + machine: + image: default + resource_class: medium + +gpu: &gpu + environment: + CUDA_VERSION: "10.1" + machine: + image: default + resource_class: gpu.medium # Tesla M60 + +# ------------------------------------------------------------------------------------- +# Re-usable commands +# ------------------------------------------------------------------------------------- +setup_venv: &setup_venv + - run: + name: Activate Venv + command: | + python -m venv ~/venv + echo ". ~/venv/bin/activate" >> $BASH_ENV + . ~/venv/bin/activate + python --version + which python + which pip + +install_python: &install_python + - run: + name: Install Python + working_directory: ~/ + command: | + pyenv install 3.6.1 + pyenv global 3.6.1 + +install_dep: &install_dep + - run: + name: Install Dependencies + command: | + which python + which pip + pip install --upgrade pip + pip install --progress-bar off -r requirements.txt + pip list + +run_tests: &run_tests + - run: + name: Run Tests + command: | + python setup.py test -s test.suites.unittests -v + +# ------------------------------------------------------------------------------------- +# Jobs to run +# ------------------------------------------------------------------------------------- +jobs: + cpu_tests: + <<: *cpu + + working_directory: ~/ClassyVision + + steps: + - checkout + + - <<: *install_python + + - <<: *setup_venv + + # Download and cache dependencies + - restore_cache: + keys: + - v2-cpu-dependencies-{{ checksum "requirements.txt" }} + # fallback to using the latest cache if no exact match is found + - v2-cpu-dependencies- + + - <<: *install_dep + + - save_cache: + paths: + - ~/venv + key: v2-cpu-dependencies-{{ checksum "requirements.txt" }} + + - <<: *run_tests + + - run: + name: Run classy-project tests + command: | + pip install . + classy-project my-project + pushd my-project + ./classy_train.py --device cpu --config configs/template_config.json + popd + rm -rf my-project + + + gpu_tests: + <<: *gpu + + working_directory: ~/ClassyVision + + steps: + - checkout + + - run: + name: Setup CUDA + working_directory: ~/ + command: | + # download and install nvidia drivers, cuda, etc + wget -q 'https://s3.amazonaws.com/ossci-linux/nvidia_driver/NVIDIA-Linux-x86_64-430.40.run' + sudo /bin/bash ./NVIDIA-Linux-x86_64-430.40.run -s --no-drm + wget -q https://developer.download.nvidia.com/compute/cuda/10.1/Prod/local_installers/cuda-repo-ubuntu1404-10-1-local-10.1.243-418.87.00_1.0-1_amd64.deb + sudo dpkg -i cuda-repo-ubuntu1404-10-1-local-10.1.243-418.87.00_1.0-1_amd64.deb + sudo apt-key add /var/cuda-repo-10-1-local-10.1.243-418.87.00/7fa2af80.pub + nvidia-smi + + - <<: *install_python + + - <<: *setup_venv + + # Download and cache dependencies + - restore_cache: + keys: + - v1-gpu-dependencies-{{ checksum "requirements.txt" }} + # fallback to using the latest cache if no exact match is found + - v1-gpu-dependencies- + + - <<: *install_dep + + - run: + name: Check CUDA Available + command: python -c "import torch; assert torch.cuda.is_available(), 'CUDA not available'" + + - save_cache: + paths: + - ~/venv + key: v1-gpu-dependencies-{{ checksum "requirements.txt" }} + + - <<: *run_tests + + +# ------------------------------------------------------------------------------------- +# Workflows +# ------------------------------------------------------------------------------------- +workflows: + version: 2 + build_and_test: + jobs: + - cpu_tests + - gpu_tests diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000000..6556275399 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,53 @@ +--- +name: "\U0001F41B Bug Report" +about: Submit a bug report to help us improve Classy Vision + +--- + +## 🐛 Bug + + + +## To Reproduce + +Steps to reproduce the behavior: + +1. +1. +1. + + + +## Expected behavior + + + +## Environment + + - What commands did you use to install Classy Vision (conda/pip/build from source)? + - If you are building from source, which commit is it? + - What does `classy_vision.__version__` print? (If applicable) + +Please copy and paste the output from the Pytorch +[environment collection script](https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py) +(or fill out the checklist below manually). + +You can get the script and run it with: +``` +wget https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py +# For security purposes, please check the contents of collect_env.py before running it. +python collect_env.py +``` + + - PyTorch Version (e.g., 1.0): + - OS (e.g., Linux): + - How you installed PyTorch (`conda`, `pip`, source): + - Build command you used (if compiling from source): + - Python version: + - CUDA/cuDNN version: + - GPU models and configuration: + - Any other relevant information: + +## Additional context + + diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 0000000000..bbb0c23c7b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,9 @@ +--- +name: "\U0001F4DA Documentation" +about: Report an issue related to Classy Vision documentation + +--- + +## 📚 Documentation + + diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000000..66677b41ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,24 @@ +--- +name: "\U0001F680Feature Request" +about: Submit a proposal/request for a new Classy Vision feature + +--- + +## 🚀 Feature + + +## Motivation + + + +## Pitch + + + +## Alternatives + + + +## Additional context + + diff --git a/.github/ISSUE_TEMPLATE/questions-help-support.md b/.github/ISSUE_TEMPLATE/questions-help-support.md new file mode 100644 index 0000000000..78cc87af49 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/questions-help-support.md @@ -0,0 +1,8 @@ +--- +name: "❓Questions/Help/Support" +about: Do you need support? +--- + +## ❓ Questions and Help + +Before creating an issue, please go over our [Tutorials](https://classyvision.ai/tutorials) and [API Reference](https://classyvision.ai/api). If you cannot find the information you are looking for, please enquire in Classy Vision's `#help` [slack](https://classyvision.slack.com) channel before creating an issue. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..4a71e12c88 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +.DS_Store + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ + +# Jupyter Notebook +.ipynb_checkpoints + +node_modules + +lib/core/metadata.js +lib/core/MetadataBlog.js + +# Sphinx documentation +sphinx/build/ + +# Docusaurus +website/translated_docs +website/build/ +website/yarn.lock +website/node_modules +website/i18n/* + +## Generated for tutorials +website/_tutorials/ +website/static/files/ +website/pages/tutorials/* +!website/pages/tutorials/index.js + +## Generated for Sphinx +website/pages/api/ +website/static/js/* +!website/static/js/code_block_buttons.js +website/static/_sphinx-sources/ diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000000..efc0bdb07a --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +multi_line_output=3 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..7199f3ef2e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +0.1 (December 9, 2019) + +* Initial release diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..f049d4c531 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..d18f22348d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# Contributing to Facebook AI Research Classy Vision framework (ClassyVision) +We want to make contributing to this project as easy and transparent as +possible. + +## Pull Requests +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +## License +By contributing to Facebook AI Research Classy Vision framework (ClassyVision), +you agree that your contributions will be licensed under the LICENSE file in +the root directory of this source tree. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..b96dcb0480 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000..1d762a275e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +graft classy_vision/templates +graft classy_vision/configs +include classy_train.py +recursive-include classy_vision/hydra *.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000000..d7ec5048ca --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +

+ +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/facebookresearch/ClassyVision/blob/master/LICENSE) [![CircleCI](https://circleci.com/gh/facebookresearch/ClassyVision.svg?style=svg&circle-token=feeafa057f8d3f6c0c15dfd74db8dd596d9684c8)](https://circleci.com/gh/facebookresearch/ClassyVision) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/facebookresearch/ClassyVision/blob/master/CONTRIBUTING.md) + +-------------------------------------------------------------------------------- + +[Classy Vision](http://classyvision.ai) is a new end-to-end, PyTorch-based framework for large-scale training of state-of-the-art image and video classification models. Previous computer vision (CV) libraries have been focused on providing components for users to build their own frameworks for their research. While this approach offers flexibility for researchers, in production settings it leads to duplicative efforts, and requires users to migrate research between frameworks and to relearn the minutia of efficient distributed training and data loading. Our PyTorch-based CV framework offers a better solution for training at scale and for deploying to production. It offers several notable advantages: + +* Ease of use. The library features a modular, flexible design that allows anyone to train machine learning models on top of PyTorch using very simple abstractions. The system also has out-of-the-box integration with Amazon Web Services (AWS), facilitating research at scale and making it simple to move between research and production. +* High performance. Researchers can use the framework to train Resnet50 on ImageNet in as little as 15 minutes, for example. +* Demonstrated success in production at scale. The framework is currently in use at Facebook, where we’ve used it to replicate the state-of-the-art results from the paper [Exploring the Limits of Weakly Supervised Pretraining](https://arxiv.org/abs/1805.00932). +* Integration with PyTorch Hub. AI researchers and engineers can download and fine-tune the best publically available ImageNet models with just a few lines of code. +* Elastic training. We have also added experimental integration with PyTorch Elastic, which allows distributed training jobs to adjust as available resources in the cluster changes. It also makes distributed training robust to transient hardware failures. + +Classy Vision is beta software. The project is under active development and our APIs are subject to change in future releases. + +## Installation + +#### Installation Requirements +Make sure you have an up-to-date installation of PyTorch (1.3.1), Python (3.6) and torchvision (1.4). If you want to use GPUs, then a CUDA installation (10.1) is also required. + +#### Installing the latest stable release +To install Classy Vision: +```bash +pip install classy_vision +``` + +#### Manual install of latest commit on master + +Alternatively you can do a manual install. + +```bash +git clone https://github.com/facebookresearch/ClassyVision.git +cd ClassyVision +pip install . +``` + +## Getting started + +Classy Vision aims to support a variety of projects to be built and open sourced on top of the core library. We provide utilities for setting up a project in a standard format with some simple generated examples to get started with. To start a new project: + +```bash +classy-project my-project +cd my-project +``` + +We even include a simple, synthetic, training example to show how to use Classy Vision: + +```bash + ./classy_train.py --config configs/template_config.json + ``` + +Voila! A few seconds later your first training run using our classification task should be done. Check out the results in the output folder: +```bash +ls output_/checkpoints/ +checkpoint.torch model_phase-0_end.torch model_phase-1_end.torch model_phase-2_end.torch model_phase-3_end.torch +``` + +checkpoint.torch is the latest model (in this case, same as model_phase-3), a checkpoint is saved at the end of each phase. + +For more details / tutorials see the documentation section below. + +## Documentation + +Please see our [tutorials](https://classyvision.ai/tutorials/getting_started) to learn how to get started on Classy Vision and customize your training runs. Full documentation is available [here](http://TODO). + +## Join the Classy Vision community +* Website: http://classyvision.ai +* Slack: [invite link](https://join.slack.com/t/classyvision/shared_invite/enQtODMwODA5Mjg3MTI3LWM5NzNlOTZjNWY3ZTE5YTViYmU2NWM1MDBjMWIwZTIwNmIyY2JjOTkyMTVmMTYzMmIwZWRmZjZmYjhhZTBkZGE) + +See the [CONTRIBUTING](CONTRIBUTING.md) file for how to help out. + +## License +Classy Vision is MIT licensed, as found in the LICENSE file. + +## Citing Classy Vision +If you use Classy Vision in your work, please use the following BibTeX entry: + +``` +@article{adcock2019classy, + title={Classy Vision}, + author={{Adcock}, A. and {Reis}, V. and {Singh}, M. and {Yan}, Z. and {van der Maaten} L., and {Zhang}, K. and {Motwani}, S. and {Guerin}, J. and {Goyal}, N. and {Misra}, I. and {Gustafson}, L. and {Changhan}, C. and {Goyal}, P.}, + howpublished = {\url{https://github.com/facebookresearch/ClassyVision}}, + year={2019} +} +``` diff --git a/bin/classy-project b/bin/classy-project new file mode 100755 index 0000000000..b54510177c --- /dev/null +++ b/bin/classy-project @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import argparse +import logging +import os +import shutil +import sys +from pathlib import Path +import site + +logging.basicConfig(level=logging.INFO) +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("project_name") + parser.add_argument("--template-name", default="synthetic") + args = parser.parse_args() + + root = Path(site.getsitepackages()[0]) + egg_link = root / "classy-vision.egg-link" + dev_install = False + # Support development mode (pip install -e) + if egg_link.exists(): + dev_install = True + with egg_link.open("r") as f: + lines = f.read().split("\n") + if lines[1] != ".": + raise RuntimeError("Unexpected egg-link format") + root = Path(lines[0]) + + base_path = root / "classy_vision" + template_path = base_path / "templates" / args.template_name + destination_path = Path(os.getcwd()) / args.project_name + + if destination_path.exists(): + logging.error(f"Project directory '{destination_path}' already exists!") + sys.exit(1) + + if dev_install: + classy_train_path = root / "classy_train.py" + else: + classy_train_path = Path(sys.prefix) / "classy_vision" / "classy_train.py" + + shutil.copytree(template_path, destination_path) + shutil.copy(classy_train_path, destination_path) + + logging.info( + f""" + Successfully generated template project at '{destination_path}'. + To get started, run: + $ cd {args.project_name} + $ ./classy_train.py --config configs/template_config.json""" + ) diff --git a/classy_train.py b/classy_train.py new file mode 100755 index 0000000000..8ef5ebdd37 --- /dev/null +++ b/classy_train.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +""" +This is the main script used for training Classy Vision jobs. + +This can be used for training on your local machine, using CPU or GPU, and +for distributed training. This script also supports Tensorboard, Visdom and +checkpointing. + +Example: + For training locally, simply specify a configuration file and whether + to use CPU or GPU: + + $ ./classy_train.py --device gpu --config configs/my_config.json + + For distributed training, this can be invoked via + :func:`torch.distributed.launch`. For instance + + $ python -m torch.distributed.launch \ + --nnodes=1 \ + --nproc_per_node=1 \ + --master_addr=127.0.0.1 \ + --master_port=29500 \ + --use_env \ + classy_train.py \ + --device=gpu \ + --config=configs/resnet50_synthetic_image_classy_config.json \ + --num_workers=1 \ + --log_freq=100 + + For other use cases, try + + $ ./classy_train.py --help +""" + +import logging +import os +from datetime import datetime +from pathlib import Path + +import torch +from classy_vision.generic.args import parse_args +from classy_vision.generic.registry_utils import import_all_packages_from_directory +from classy_vision.generic.util import load_checkpoint +from classy_vision.hooks import ( + CheckpointHook, + LossLrMeterLoggingHook, + ProfilerHook, + ProgressBarHook, + TensorboardPlotHook, + TimeMetricsHook, + VisdomHook, +) +from classy_vision.tasks import FineTuningTask, build_task +from classy_vision.trainer import DistributedTrainer, LocalTrainer +from torchvision import set_image_backend, set_video_backend + + +def main(args, config): + # Global flags + torch.manual_seed(0) + set_image_backend(args.image_backend) + set_video_backend(args.video_backend) + + task = build_task(config) + + # Load checkpoint, if available. This automatically resumes from an + # existing checkpoint, in case training is being restarted. + checkpoint = load_checkpoint(args.checkpoint_folder, args.device) + task.set_checkpoint(checkpoint) + + # Load a checkpoint contraining a pre-trained model. This is how we + # implement fine-tuning of existing models. + pretrained_checkpoint = load_checkpoint( + args.pretrained_checkpoint_folder, args.device + ) + if pretrained_checkpoint is not None: + assert isinstance( + task, FineTuningTask + ), "Can only use a pretrained checkpoint for fine tuning tasks" + task.set_pretrained_checkpoint(pretrained_checkpoint) + + # Configure hooks to do tensorboard logging, checkpoints and so on + task.set_hooks(configure_hooks(args, config)) + + use_gpu = None + if args.device is not None: + use_gpu = args.device == "gpu" + + # LocalTrainer is used for a single node. DistributedTrainer will setup + # training to use PyTorch's DistributedDataParallel. + trainer_class = {"none": LocalTrainer, "ddp": DistributedTrainer}[ + args.distributed_backend + ] + + trainer = trainer_class(use_gpu=use_gpu, num_dataloader_workers=args.num_workers) + + # That's it! When this call returns, training is done. + trainer.train(task) + + output_folder = Path(args.checkpoint_folder).resolve() + logging.info("Training successful!") + logging.info(f'Results of this training run are available at: "{output_folder}"') + + +def configure_hooks(args, config): + hooks = [LossLrMeterLoggingHook(args.log_freq), TimeMetricsHook()] + + # Make a folder to store checkpoints and tensorboard logging outputs + suffix = datetime.now().isoformat() + base_folder = Path(__file__).parent / f"output_{suffix}" + if args.checkpoint_folder == "": + args.checkpoint_folder = base_folder / "checkpoints" + os.makedirs(args.checkpoint_folder, exist_ok=True) + + logging.info(f"Logging outputs to {base_folder.resolve()}") + logging.info(f"Logging checkpoints to {args.checkpoint_folder}") + + if not args.skip_tensorboard: + try: + from tensorboardX import SummaryWriter + + tb_writer = SummaryWriter(log_dir=base_folder / "tensorboard") + hooks.append(TensorboardPlotHook(tb_writer)) + except ImportError: + logging.warning("tensorboardX not installed, skipping tensorboard hooks") + + args_dict = vars(args) + args_dict["config"] = config + hooks.append( + CheckpointHook( + args.checkpoint_folder, args_dict, checkpoint_period=args.checkpoint_period + ) + ) + + if args.profiler: + hooks.append(ProfilerHook()) + if args.show_progress: + hooks.append(ProgressBarHook()) + if args.visdom_server != "": + hooks.append(VisdomHook(args.visdom_server, args.visdom_port)) + + return hooks + + +# run all the things: +if __name__ == "__main__": + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + logging.info("Classy Vision's default training script.") + + # This imports all modules in the same directory as classy_train.py + # Because of the way Classy Vision's registration decorators work, + # importing a module has a side effect of registering it with Classy + # Vision. This means you can give classy_train.py a config referencing your + # custom module (e.g. my_dataset) and it'll actually know how to + # instantiate it. + file_root = Path(__file__).parent + import_all_packages_from_directory(file_root) + + args, config = parse_args() + main(args, config) diff --git a/classy_vision/__init__.py b/classy_vision/__init__.py new file mode 100644 index 0000000000..090c7eaf6d --- /dev/null +++ b/classy_vision/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +__version__ = "0.1.0" diff --git a/classy_vision/configs/hmdb51/r3d34.json b/classy_vision/configs/hmdb51/r3d34.json new file mode 100644 index 0000000000..742632f7fb --- /dev/null +++ b/classy_vision/configs/hmdb51/r3d34.json @@ -0,0 +1,121 @@ +{ + "name": "classification_task", + "num_epochs": 300, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "hmdb51", + "split": "train", + "batchsize_per_replica": 16, + "use_shuffle": true, + "num_samples": null, + "frames_per_clip": 32, + "step_between_clips": 1, + "clips_per_video": 1, + "video_dir": "", + "splits_dir": "", + "metadata_file": "", + "fold": 1, + "transforms": { + "video": [ + { + "name": "video_default_augment", + "crop_size": 112, + "size_range": [128, 160] + } + ] + } + }, + "test": { + "name": "hmdb51", + "split": "test", + "batchsize_per_replica": 10, + "use_shuffle": false, + "num_samples": null, + "frames_per_clip": 32, + "step_between_clips": 1, + "clips_per_video": 10, + "video_dir": "", + "splits_dir": "", + "metadata_file": "", + "fold": 1, + "transforms": { + "video": [ + { + "name": "video_default_no_augment", + "size": 128 + } + ] + } + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + }, + "video_accuracy": { + "topk": [1, 5], + "clips_per_video_train": 1, + "clips_per_video_test": 10 + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 32, + "input_planes": 3, + "clip_crop_size": 112, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "basic_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 3, + "stem_maxpool": false, + "stage_planes": 64, + "stage_temporal_kernel_basis": [[3], [3], [3], [3]], + "temporal_conv_1x1": [false, false, false, false], + "stage_temporal_stride": [1, 2, 2, 2], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 51, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [4, 7, 7], + "activation_func": "softmax", + "num_classes": 51, + "fork_block": "pathway0-stage4-block2", + "in_plane": 512 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "composite", + "schedulers": [ + { + "name": "linear", + "start_lr": 0.005, + "end_lr": 0.04 + }, + { + "name": "cosine", + "start_lr": 0.04, + "end_lr": 0.00004 + } + ], + "update_interval": "epoch", + "interval_scaling": ["rescaled", "rescaled"], + "lengths": [0.13, 0.87] + }, + "weight_decay": 0.005, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/imagenet/densenet121_imagenet_classy_config.json b/classy_vision/configs/imagenet/densenet121_imagenet_classy_config.json new file mode 100644 index 0000000000..b482c3b794 --- /dev/null +++ b/classy_vision/configs/imagenet/densenet121_imagenet_classy_config.json @@ -0,0 +1,65 @@ +{ + "name": "classification_task", + "num_epochs": 90, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "densenet", + "num_blocks": [6, 12, 24, 16], + "num_classes": 1000, + "small_input": false + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01, 0.001] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/imagenet/postactivated_r2d101.json b/classy_vision/configs/imagenet/postactivated_r2d101.json new file mode 100644 index 0000000000..3b12106b0a --- /dev/null +++ b/classy_vision/configs/imagenet/postactivated_r2d101.json @@ -0,0 +1,99 @@ +{ + "name": "classification_task", + "num_epochs": 95, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": true, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": false, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 1, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "postactivated_bottleneck_transformation", + "num_blocks": [3, 4, 23, 3], + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 1, + "stem_spatial_kernel": 7, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[1], [1], [1], [1]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 1000, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [1, 7, 7], + "activation_func": "softmax", + "num_classes": 1000, + "fork_block": "pathway0-stage4-block2", + "in_plane": 2048, + "use_dropout": false + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "multistep", + "num_epochs": 95, + "values": [0.1, 0.01, 0.001, 0.0001], + "milestones": [30, 60, 90] + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/imagenet/postactivated_r2d50.json b/classy_vision/configs/imagenet/postactivated_r2d50.json new file mode 100644 index 0000000000..340264f00d --- /dev/null +++ b/classy_vision/configs/imagenet/postactivated_r2d50.json @@ -0,0 +1,99 @@ +{ + "name": "classification_task", + "num_epochs": 95, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": true, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": false, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 1, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "postactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 1, + "stem_spatial_kernel": 7, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[1], [1], [1], [1]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 1000, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [1, 7, 7], + "activation_func": "softmax", + "num_classes": 1000, + "fork_block": "pathway0-stage4-block2", + "in_plane": 2048, + "use_dropout": false + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "multistep", + "num_epochs": 95, + "values": [0.1, 0.01, 0.001, 0.0001], + "milestones": [30, 60, 90] + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/imagenet/preactivated_r2d101.json b/classy_vision/configs/imagenet/preactivated_r2d101.json new file mode 100644 index 0000000000..f126df0b38 --- /dev/null +++ b/classy_vision/configs/imagenet/preactivated_r2d101.json @@ -0,0 +1,99 @@ +{ + "name": "classification_task", + "num_epochs": 95, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": true, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": false, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 1, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "preactivated_shortcut", + "residual_transformation_type": "preactivated_bottleneck_transformation", + "num_blocks": [3, 4, 23, 3], + "stem_name": "resnext3d_stem", + "stem_planes": 32, + "stem_temporal_kernel": 1, + "stem_spatial_kernel": 5, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[1], [1], [1], [1]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 1000, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [1, 7, 7], + "activation_func": "softmax", + "num_classes": 1000, + "fork_block": "pathway0-stage4-block2-relu", + "in_plane": 2048, + "use_dropout": false + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "multistep", + "num_epochs": 95, + "values": [0.1, 0.01, 0.001, 0.0001], + "milestones": [30, 60, 90] + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/imagenet/preactivated_r2d50.json b/classy_vision/configs/imagenet/preactivated_r2d50.json new file mode 100644 index 0000000000..b1628e876c --- /dev/null +++ b/classy_vision/configs/imagenet/preactivated_r2d50.json @@ -0,0 +1,99 @@ +{ + "name": "classification_task", + "num_epochs": 95, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": true, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": false, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 1, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "preactivated_shortcut", + "residual_transformation_type": "preactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "stem_name": "resnext3d_stem", + "stem_planes": 32, + "stem_temporal_kernel": 1, + "stem_spatial_kernel": 5, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[1], [1], [1], [1]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 1000, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [1, 7, 7], + "activation_func": "softmax", + "num_classes": 1000, + "fork_block": "pathway0-stage4-block2-relu", + "in_plane": 2048, + "use_dropout": false + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "multistep", + "num_epochs": 95, + "values": [0.1, 0.01, 0.001, 0.0001], + "milestones": [30, 60, 90] + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/imagenet/resnet101_imagenet_classy_config.json b/classy_vision/configs/imagenet/resnet101_imagenet_classy_config.json new file mode 100644 index 0000000000..7846eac9e5 --- /dev/null +++ b/classy_vision/configs/imagenet/resnet101_imagenet_classy_config.json @@ -0,0 +1,74 @@ +{ + "name": "classification_task", + "num_epochs": 90, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnet", + "num_blocks": [3, 4, 23, 3], + "num_classes": 1000, + "small_input": false, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01, 0.001] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/imagenet/resnet50_imagenet_classy_config.json b/classy_vision/configs/imagenet/resnet50_imagenet_classy_config.json new file mode 100644 index 0000000000..6557efb38c --- /dev/null +++ b/classy_vision/configs/imagenet/resnet50_imagenet_classy_config.json @@ -0,0 +1,74 @@ +{ + "name": "classification_task", + "num_epochs": 90, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnet", + "num_blocks": [3, 4, 6, 3], + "small_input": false, + "zero_init_bn_residuals": true, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01, 0.001] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/imagenet/resnet50_label_smoothing_criterion.json b/classy_vision/configs/imagenet/resnet50_label_smoothing_criterion.json new file mode 100644 index 0000000000..0c9156f8d0 --- /dev/null +++ b/classy_vision/configs/imagenet/resnet50_label_smoothing_criterion.json @@ -0,0 +1,77 @@ +{ + "name": "classification_task", + "num_epochs": 90, + "loss": { + "name": "label_smoothing_cross_entropy", + "smoothing_param": 0.1 + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": true, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_augmentation": false, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnet", + "num_blocks": [3, 4, 6, 3], + "small_input": false, + "zero_init_bn_residuals": true, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01, 0.001] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/imagenet/resnext101_32x4d_fine_tuning_config.json b/classy_vision/configs/imagenet/resnext101_32x4d_fine_tuning_config.json new file mode 100644 index 0000000000..bd4e51ea3c --- /dev/null +++ b/classy_vision/configs/imagenet/resnext101_32x4d_fine_tuning_config.json @@ -0,0 +1,78 @@ +{ + "name": "fine_tuning", + "num_epochs": 10, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext", + "num_blocks": [3, 4, 23, 3], + "base_width_and_cardinality": [4, 32], + "small_input": false, + "zero_init_bn_residuals": true, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01, 0.001] + }, + "weight_decay": 1e-4, + "momentum": 0.9, + "nesterov": true + }, + "reset_heads": true, + "freeze_trunk": true +} diff --git a/classy_vision/configs/imagenet/resnext101_imagenet_classy_config.json b/classy_vision/configs/imagenet/resnext101_imagenet_classy_config.json new file mode 100644 index 0000000000..e252cdd191 --- /dev/null +++ b/classy_vision/configs/imagenet/resnext101_imagenet_classy_config.json @@ -0,0 +1,75 @@ +{ + "name": "classification_task", + "num_epochs": 90, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext", + "num_blocks": [3, 4, 23, 3], + "num_classes": 1000, + "base_width_and_cardinality": [4, 32], + "small_input": false, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01, 0.001] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/imagenet/resnext50_imagenet_classy_config.json b/classy_vision/configs/imagenet/resnext50_imagenet_classy_config.json new file mode 100644 index 0000000000..6ab0f82bbd --- /dev/null +++ b/classy_vision/configs/imagenet/resnext50_imagenet_classy_config.json @@ -0,0 +1,75 @@ +{ + "name": "classification_task", + "num_epochs": 90, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "classy_imagenet", + "split": "train", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + }, + "test": { + "name": "classy_imagenet", + "split": "val", + "batchsize_per_replica": 32, + "num_samples": null, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}], + "root": "/tmp/imagenet" + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext", + "num_blocks": [3, 4, 6, 3], + "num_classes": 1000, + "base_width_and_cardinality": [4, 32], + "small_input": false, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01, 0.001] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/kinetics400/postactivated_i3d50.json b/classy_vision/configs/kinetics400/postactivated_i3d50.json new file mode 100644 index 0000000000..bb2bebb3d8 --- /dev/null +++ b/classy_vision/configs/kinetics400/postactivated_i3d50.json @@ -0,0 +1,111 @@ +{ + "name": "classification_task", + "num_epochs": 80, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "kinetics400", + "split": "train", + "batchsize_per_replica": 8, + "use_shuffle": true, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 1, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_augment", + "crop_size": 224, + "size_range": [256, 320] + } + ] + } + }, + "test": { + "name": "kinetics400", + "split": "val", + "batchsize_per_replica": 10, + "use_shuffle": false, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 10, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_no_augment", + "size": 256 + } + ] + } + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + }, + "video_accuracy": { + "topk": [1, 5], + "clips_per_video_train": 1, + "clips_per_video_test": 10 + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 8, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "postactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 5, + "stem_spatial_kernel": 7, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[3], [3, 1], [3, 1], [1, 3]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 400, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [8, 7, 7], + "activation_func": "softmax", + "num_classes": 400, + "fork_block": "pathway0-stage4-block2", + "in_plane": 2048, + "use_dropout": true + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "cosine", + "start_lr": 0.1, + "end_lr": 0.0001 + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/kinetics400/postactivated_i3d50_fine_tuning.json b/classy_vision/configs/kinetics400/postactivated_i3d50_fine_tuning.json new file mode 100644 index 0000000000..2a52a795d2 --- /dev/null +++ b/classy_vision/configs/kinetics400/postactivated_i3d50_fine_tuning.json @@ -0,0 +1,113 @@ +{ + "name": "fine_tuning", + "reset_heads": true, + "num_epochs": 80, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "kinetics400", + "split": "train", + "batchsize_per_replica": 8, + "use_shuffle": true, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 1, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_augment", + "crop_size": 224, + "size_range": [256, 320] + } + ] + } + }, + "test": { + "name": "kinetics400", + "split": "val", + "batchsize_per_replica": 10, + "use_shuffle": false, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 10, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_no_augment", + "size": 256 + } + ] + } + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + }, + "video_accuracy": { + "topk": [1, 5], + "clips_per_video_train": 1, + "clips_per_video_test": 10 + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 8, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "postactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 5, + "stem_spatial_kernel": 7, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[3], [3, 1], [3, 1], [1, 3]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 400, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [8, 7, 7], + "activation_func": "softmax", + "num_classes": 400, + "fork_block": "pathway0-stage4-block2", + "in_plane": 2048, + "use_dropout": true + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "multistep", + "num_epochs": 80, + "values": [0.04, 0.004, 0.0004], + "milestones": [40, 70] + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/kinetics400/preactivated_i3d50.json b/classy_vision/configs/kinetics400/preactivated_i3d50.json new file mode 100644 index 0000000000..b363cf6335 --- /dev/null +++ b/classy_vision/configs/kinetics400/preactivated_i3d50.json @@ -0,0 +1,111 @@ +{ + "name": "classification_task", + "num_epochs": 160, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "kinetics400", + "split": "train", + "batchsize_per_replica": 8, + "use_shuffle": true, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 1, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_augment", + "crop_size": 224, + "size_range": [256, 320] + } + ] + } + }, + "test": { + "name": "kinetics400", + "split": "val", + "batchsize_per_replica": 10, + "use_shuffle": false, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 10, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_no_augment", + "size": 256 + } + ] + } + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + }, + "video_accuracy": { + "topk": [1, 5], + "clips_per_video_train": 1, + "clips_per_video_test": 10 + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 8, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "preactivated_shortcut", + "residual_transformation_type": "preactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 32, + "stem_temporal_kernel": 3, + "stem_spatial_kernel": 5, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[3], [3, 1], [3, 1], [1, 3]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 2, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 400, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [4, 7, 7], + "activation_func": "softmax", + "num_classes": 400, + "fork_block": "pathway0-stage4-block2-relu", + "in_plane": 2048, + "use_dropout": true + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "cosine", + "start_lr": 0.1, + "end_lr": 0.0001 + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/kinetics400/preactivated_i3d50_fine_tuning.json b/classy_vision/configs/kinetics400/preactivated_i3d50_fine_tuning.json new file mode 100644 index 0000000000..829610dcfc --- /dev/null +++ b/classy_vision/configs/kinetics400/preactivated_i3d50_fine_tuning.json @@ -0,0 +1,113 @@ +{ + "name": "fine_tuning", + "reset_heads": true, + "num_epochs": 120, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "kinetics400", + "split": "train", + "batchsize_per_replica": 8, + "use_shuffle": true, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 1, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_augment", + "crop_size": 224, + "size_range": [256, 320] + } + ] + } + }, + "test": { + "name": "kinetics400", + "split": "val", + "batchsize_per_replica": 10, + "use_shuffle": false, + "num_samples": null, + "frames_per_clip": 8, + "frame_rate": 4, + "step_between_clips": 1, + "clips_per_video": 10, + "video_dir": "", + "metadata_file": "", + "extensions": "mp4", + "transforms": { + "video": [ + { + "name": "video_default_no_augment", + "size": 256 + } + ] + } + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + }, + "video_accuracy": { + "topk": [1, 5], + "clips_per_video_train": 1, + "clips_per_video_test": 10 + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 8, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "preactivated_shortcut", + "residual_transformation_type": "preactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 32, + "stem_temporal_kernel": 3, + "stem_spatial_kernel": 5, + "stem_maxpool": true, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[3], [3, 1], [3, 1], [1, 3]], + "temporal_conv_1x1": [true, true, true, true], + "stage_temporal_stride": [1, 2, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 400, + "zero_init_residual_transform": true, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [4, 7, 7], + "activation_func": "softmax", + "num_classes": 400, + "fork_block": "pathway0-stage4-block2-relu", + "in_plane": 2048, + "use_dropout": true + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "multistep", + "num_epochs": 120, + "values": [0.04, 0.004, 0.0004], + "milestones": [60, 105] + }, + "weight_decay": 0.0001, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/configs/r3d34_synthetic_video.json b/classy_vision/configs/r3d34_synthetic_video.json new file mode 100644 index 0000000000..4072524c46 --- /dev/null +++ b/classy_vision/configs/r3d34_synthetic_video.json @@ -0,0 +1,80 @@ +{ + "name": "classification_task", + "num_epochs": 2, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "synthetic_video", + "split": "train", + "batchsize_per_replica": 16, + "use_shuffle": true, + "num_samples": 128, + "frames_per_clip": 32, + "video_height": 128, + "video_width": 160, + "num_classes": 50, + "clips_per_video": 1 + }, + "test": { + "name": "synthetic_video", + "split": "test", + "batchsize_per_replica": 10, + "use_shuffle": false, + "num_samples": 40, + "frames_per_clip": 32, + "video_height": 128, + "video_width": 160, + "num_classes": 50, + "clips_per_video": 10 + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnext3d", + "input_planes": 3, + "clip_crop_size": 112, + "frames_per_clip": 32, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "basic_transformation", + "num_blocks": [2, 2, 2, 2], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 3, + "stem_maxpool": false, + "stage_planes": 64, + "stage_temporal_kernel_basis": [[3], [3], [3], [3]], + "temporal_conv_1x1": [false, false, false, false], + "stage_temporal_stride": [1, 2, 2, 2], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 50, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [4, 7, 7], + "activation_func": "softmax", + "num_classes": 50, + "fork_block": "pathway0-stage4-block1", + "in_plane": 512 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.04, 0.004] + }, + "weight_decay": 5e-3, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/resnet50_synthetic_image_classy_config.json b/classy_vision/configs/resnet50_synthetic_image_classy_config.json new file mode 100644 index 0000000000..3abc18a438 --- /dev/null +++ b/classy_vision/configs/resnet50_synthetic_image_classy_config.json @@ -0,0 +1,74 @@ +{ + "name": "classification_task", + "num_epochs": 2, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "synthetic_image", + "split": "train", + "crop_size": 224, + "class_ratio": 0.5, + "num_samples": 2000, + "seed": 0, + "batchsize_per_replica": 32, + "use_shuffle": true, + "transforms": [{ + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + {"name": "Normalize", "mean": [0.485, 0.456, 0.406], "std": [0.229, 0.224, 0.225]} + ], + "key": "input" + }] + }, + "test": { + "name": "synthetic_image", + "split": "val", + "crop_size": 224, + "class_ratio": 0.5, + "num_samples": 2000, + "seed": 1, + "batchsize_per_replica": 32, + "use_shuffle": false, + "transforms": [{ + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + {"name": "Normalize", "mean": [0.485, 0.456, 0.406], "std": [0.229, 0.224, 0.225]} + ], + "key": "input" + }] + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + } + }, + "model": { + "name": "resnet", + "num_blocks": [3, 4, 6, 3], + "small_input": false, + "zero_init_bn_residuals": true, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/configs/ucf101/r3d34.json b/classy_vision/configs/ucf101/r3d34.json new file mode 100644 index 0000000000..745bcba191 --- /dev/null +++ b/classy_vision/configs/ucf101/r3d34.json @@ -0,0 +1,121 @@ +{ + "name": "classification_task", + "num_epochs": 300, + "loss": { + "name": "CrossEntropyLoss" + }, + "dataset": { + "train": { + "name": "ucf101", + "split": "train", + "batchsize_per_replica": 16, + "use_shuffle": true, + "num_samples": null, + "frames_per_clip": 32, + "step_between_clips": 1, + "clips_per_video": 1, + "video_dir": "", + "splits_dir": "", + "metadata_file": "", + "fold": 1, + "transforms": { + "video": [ + { + "name": "video_default_augment", + "crop_size": 112, + "size_range": [128, 160] + } + ] + } + }, + "test": { + "name": "ucf101", + "split": "test", + "batchsize_per_replica": 10, + "use_shuffle": false, + "num_samples": null, + "frames_per_clip": 32, + "step_between_clips": 1, + "clips_per_video": 10, + "video_dir": "", + "splits_dir": "", + "metadata_file": "", + "fold": 1, + "transforms": { + "video": [ + { + "name": "video_default_no_augment", + "size": 128 + } + ] + } + } + }, + "meters": { + "accuracy": { + "topk": [1, 5] + }, + "video_accuracy": { + "topk": [1, 5], + "clips_per_video_train": 1, + "clips_per_video_test": 10 + } + }, + "model": { + "name": "resnext3d", + "frames_per_clip": 32, + "input_planes": 3, + "clip_crop_size": 112, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "basic_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 3, + "stem_maxpool": false, + "stage_planes": 64, + "stage_temporal_kernel_basis": [[3], [3], [3], [3]], + "temporal_conv_1x1": [false, false, false, false], + "stage_temporal_stride": [1, 2, 2, 2], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 101, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [4, 7, 7], + "activation_func": "softmax", + "num_classes": 101, + "fork_block": "pathway0-stage4-block2", + "in_plane": 512 + } + ] + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "composite", + "schedulers": [ + { + "name": "linear", + "start_lr": 0.005, + "end_lr": 0.04 + }, + { + "name": "cosine", + "start_lr": 0.04, + "end_lr": 0.00004 + } + ], + "lengths": [0.13, 0.87], + "update_interval": "epoch", + "interval_scaling": ["rescaled", "rescaled"] + }, + "weight_decay": 0.005, + "momentum": 0.9, + "nesterov": true + } +} diff --git a/classy_vision/dataset/README.md b/classy_vision/dataset/README.md new file mode 100644 index 0000000000..682a041a49 --- /dev/null +++ b/classy_vision/dataset/README.md @@ -0,0 +1,3 @@ +Each dataset implements a single function `get_dataset(split)`, where `split` can +have the values `train` and `test`. It returns a tuple containing the torch +Dataset objects, and the number of classes in the dataset. diff --git a/classy_vision/dataset/__init__.py b/classy_vision/dataset/__init__.py new file mode 100644 index 0000000000..6f4d239705 --- /dev/null +++ b/classy_vision/dataset/__init__.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_dataset import ClassyDataset + + +FILE_ROOT = Path(__file__).parent + +DATASET_REGISTRY = {} +DATASET_CLASS_NAMES = set() + + +def build_dataset(config, *args, **kwargs): + """Builds a ClassyDataset from a config. + + This assumes a 'name' key in the config which is used to determine what + dataset class to instantiate. For instance, a config `{"name": "my_dataset", + "folder": "/data"}` will find a class that was registered as "my_dataset" + (see :func:`register_dataset`) and call .from_config on it.""" + return DATASET_REGISTRY[config["name"]].from_config(config, *args, **kwargs) + + +def register_dataset(name): + """Registers a ClassyDataset subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyDataset from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyDataset subclass like this: + + | @register_dataset("my_dataset") + | class MyDataset(ClassyDataset): + | ... + + To instantiate a dataset from a configuration file, see + :func:`build_dataset`.""" + + def register_dataset_cls(cls): + if name in DATASET_REGISTRY: + raise ValueError("Cannot register duplicate dataset ({})".format(name)) + if not issubclass(cls, ClassyDataset): + raise ValueError( + "Dataset ({}: {}) must extend ClassyDataset".format(name, cls.__name__) + ) + if cls.__name__ in DATASET_CLASS_NAMES: + raise ValueError( + "Cannot register dataset with duplicate class name({})".format( + cls.__name__ + ) + ) + DATASET_REGISTRY[name] = cls + DATASET_CLASS_NAMES.add(cls.__name__) + return cls + + return register_dataset_cls + + +# automatically import any Python files in the dataset/ directory +import_all_modules(FILE_ROOT, "classy_vision.dataset") + +from .classy_cifar import CIFARDataset # isort:skip +from .classy_hmdb51 import HMDB51Dataset # isort:skip +from .classy_imagenet import ImageNetDataset # isort:skip +from .classy_kinetics400 import Kinetics400Dataset # isort:skip +from .classy_synthetic_image import SyntheticImageDataset # isort:skip +from .classy_synthetic_video import SyntheticVideoDataset # isort:skip +from .classy_ucf101 import UCF101Dataset # isort:skip +from .classy_video_dataset import ClassyVideoDataset # isort:skip +from .image_path_dataset import ImagePathDataset # isort:skip + +__all__ = [ + "CIFARDataset", + "ClassyDataset", + "ClassyVideoDataset", + "HMDB51Dataset", + "ImageNetDataset", + "ImagePathDataset", + "Kinetics400Dataset", + "SyntheticImageDataset", + "SyntheticVideoDataset", + "UCF101Dataset", + "build_dataset", + "register_dataset", +] diff --git a/classy_vision/dataset/classy_cifar.py b/classy_vision/dataset/classy_cifar.py new file mode 100644 index 0000000000..7bcdf27980 --- /dev/null +++ b/classy_vision/dataset/classy_cifar.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Callable, Dict, Optional, Union + +from classy_vision.dataset import ClassyDataset, register_dataset +from classy_vision.dataset.transforms import ClassyTransform, build_transforms +from torchvision.datasets.cifar import CIFAR10, CIFAR100 + + +class CIFARDataset(ClassyDataset): + _CIFAR_TYPE = None + + def __init__( + self, + split: Optional[str], + batchsize_per_replica: int, + shuffle: bool, + transform: Optional[Union[ClassyTransform, Callable]], + num_samples: Optional[int], + root: str, + download: bool = None, + ): + assert self._CIFAR_TYPE in [ + "cifar10", + "cifar100", + ], "CIFARDataset must be subclassed and a valid _CIFAR_TYPE provided" + if self._CIFAR_TYPE == "cifar10": + dataset = CIFAR10(root=root, train=(split == "train"), download=download) + if self._CIFAR_TYPE == "cifar100": + dataset = CIFAR100(root=root, train=(split == "train"), download=download) + + super().__init__( + dataset, split, batchsize_per_replica, shuffle, transform, num_samples + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "CIFARDataset": + """Instantiates a CIFARDataset from a configuration. + + Args: + config: A configuration for a CIFARDataset. + See :func:`__init__` for parameters expected in the config. + + Returns: + A CIFARDataset instance. + """ + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + ) = cls.parse_config(config) + split = config.get("split") + root = config.get("root") + download = config.get("download") + + transform = build_transforms(transform_config) + return cls( + split=split, + batchsize_per_replica=batchsize_per_replica, + shuffle=shuffle, + transform=transform, + num_samples=num_samples, + root=root, + download=download, + ) + + +@register_dataset("classy_cifar10") +class CIFAR10Dataset(CIFARDataset): + _CIFAR_TYPE = "cifar10" + + +@register_dataset("classy_cifar100") +class CIFAR100Dataset(CIFARDataset): + _CIFAR_TYPE = "cifar100" diff --git a/classy_vision/dataset/classy_dataset.py b/classy_vision/dataset/classy_dataset.py new file mode 100644 index 0000000000..f13cbf79b7 --- /dev/null +++ b/classy_vision/dataset/classy_dataset.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Callable, Dict, Optional, Sequence, Union + +from classy_vision.dataset.transforms import ClassyTransform +from classy_vision.generic.distributed_util import get_rank, get_world_size +from classy_vision.generic.util import is_pos_int +from torch.utils.data import DataLoader +from torch.utils.data.distributed import DistributedSampler + + +def _return_true(_sample): + return True + + +class ClassyDataset: + """ + Class representing a dataset abstraction. + + This class wraps a :class:`torch.utils.data.Dataset` via the `dataset` attribute + and configures the dataloaders needed to access the datasets. + Transforms which need to be applied to the data should be specified in this class. + ClassyDataset can be used to instantiate datasets from a configuration file as well. + """ + + def __init__( + self, + dataset: Sequence, + split: Optional[str], + batchsize_per_replica: int, + shuffle: bool, + transform: Optional[Union[ClassyTransform, Callable]], + num_samples: Optional[int], + ) -> None: + """ + Constructor for a ClassyDataset. + + Args: + split: Split of dataset to use ("train", "test") + batchsize_per_replica: Positive integer indicating batch size for each + replica + shuffle: Whether we should shuffle between epochs + transform: Transform to be applied to each sample + num_samples: When set, this restricts the number of samples provided by + the dataset + """ + # Asserts: + assert is_pos_int( + batchsize_per_replica + ), "batchsize_per_replica must be a positive int" + assert isinstance(shuffle, bool), "shuffle must be a boolean" + assert num_samples is None or is_pos_int( + num_samples + ), "num_samples must be a positive int or None" + + # Assignments: + self.split = split + self.batchsize_per_replica = batchsize_per_replica + self.shuffle = shuffle + self.transform = transform + self.num_samples = num_samples + self.dataset = dataset + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyDataset": + """Instantiates a ClassyDataset from a configuration. + + Args: + config: A configuration for the ClassyDataset. + + Returns: + A ClassyDataset instance. + """ + raise NotImplementedError + + @classmethod + def parse_config(cls, config: Dict[str, Any]): + """ + This function parses out common config options. + + Args: + config: A dict with the following string keys - + + | batchsize_per_replica (int): Must be a positive int, batch size + | for each replica + | use_shuffle (bool): Whether to enable shuffling for the dataset + | num_samples (int, optional): When set, restricts the number of samples + | in a dataset + | transforms: list of tranform configurations to be applied in order + + Returns: + A tuple containing the following variables - + | transform_config: Config for the dataset transform. Can be passed to + | :func:`build_transform` + | batchsize_per_replica: Batch size per replica + | shuffle: Whether we should shuffle between epochs + | num_samples: When set, restricts the number of samples in a dataset + """ + batchsize_per_replica = config.get("batchsize_per_replica") + shuffle = config.get("use_shuffle") + num_samples = config.get("num_samples") + transform_config = config.get("transforms") + return transform_config, batchsize_per_replica, shuffle, num_samples + + def __getitem__(self, idx: int): + assert idx >= 0 and idx < len( + self.dataset + ), "Provided idx is outside of dataset range" + sample = self.dataset[idx] + if self.transform is None: + return sample + return self.transform(sample) + + def __len__(self): + assert self.num_samples is None or self.num_samples <= len( + self.dataset + ), "Num samples mus be less than length of base dataset" + return len(self.dataset) if self.num_samples is None else self.num_samples + + def _get_sampler(self, epoch: int): + """ + Return a :class:`torch.utils.data.sampler.Sampler` to sample the data. + + This is used to distribute the data across the replicas. If shuffling + is enabled, every epoch will have a different shuffle. + + Args: + epoch: The epoch being fetched. + + Returns: + A sampler which tells the data loader which sample to load next. + """ + world_size = get_world_size() + rank = get_rank() + sampler = DistributedSampler( + self, num_replicas=world_size, rank=rank, shuffle=self.shuffle + ) + sampler.set_epoch(epoch) + return sampler + + def iterator(self, *args, **kwargs): + """ + Returns an iterable which can be used to iterate over the data. + + Args: + shuffle_seed (int, optional): Seed for the shuffle + current_phase_id (int, optional): The epoch being fetched. Needed so that + each epoch has a different shuffle order + Returns: + An iterable over the data + """ + # TODO: Fix naming to be consistent (i.e. everyone uses epoch) + shuffle_seed = kwargs.get("shuffle_seed", 0) + assert isinstance(shuffle_seed, int), "Shuffle seed must be an int" + epoch = kwargs.get("current_phase_id", 0) + assert isinstance(epoch, int), "Epoch must be an int" + + offset_epoch = shuffle_seed + epoch + + return DataLoader( + self, + batch_size=self.batchsize_per_replica, + num_workers=kwargs.get("num_workers", 0), + pin_memory=kwargs.get("pin_memory", False), + multiprocessing_context=kwargs.get("multiprocessing_context", None), + sampler=self._get_sampler(epoch=offset_epoch), + ) + + def get_batchsize_per_replica(self): + """ + Get the batch size per replica. + + Returns: + The batch size for each replica. + """ + return self.batchsize_per_replica + + def get_global_batchsize(self): + """ + Get the global batch size, combined over all the replicas. + + Returns: + The overall batch size of the dataset. + """ + return self.get_batchsize_per_replica() * get_world_size() diff --git a/classy_vision/dataset/classy_hmdb51.py b/classy_vision/dataset/classy_hmdb51.py new file mode 100644 index 0000000000..b13715e54e --- /dev/null +++ b/classy_vision/dataset/classy_hmdb51.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +from typing import Any, Callable, Dict, Optional + +import torch +from torchvision.datasets.hmdb51 import HMDB51 + +from . import register_dataset +from .classy_video_dataset import ClassyVideoDataset +from .transforms.util_video import build_video_field_transform_default + + +@register_dataset("hmdb51") +class HMDB51Dataset(ClassyVideoDataset): + """HMDB51 is an action recognition video dataset, and it has 51 classes. + + Page: + + This dataset consider every video as a collection of video clips of fixed size, + specified by ``frames_per_clip``, where the step in frames between each clip + is given by ``step_between_clips``. It uses clip sampler to sample clips + from each video. For training set, a random clip sampler is used to + sample a small number of clips (e.g. 1) from each video + For testing set, a uniform clip sampler is used to evenly sample a large + number of clips (e.g. 10) from the video. + + To give an example, for 2 videos with 10 and 15 frames respectively, + if ``frames_per_clip=5`` and ``step_between_clips=5``, the dataset size + will be (2 + 3) = 5, where the first two elements will come from video 1, + and the next three elements from video 2. Note that we drop clips which do + not have exactly ``frames_per_clip`` elements, so not all frames in a video + might be present. + + It is built on top of HMDB51 dataset class in TorchVision. + + """ + + def __init__( + self, + split: str, + batchsize_per_replica: int, + shuffle: bool, + transform: Callable, + num_samples: Optional[int], + frames_per_clip: int, + video_width: int, + video_height: int, + video_min_dimension: int, + audio_samples: int, + step_between_clips: int, + frame_rate: Optional[int], + clips_per_video: int, + video_dir: str, + splits_dir: str, + fold: int, + metadata_filepath: str, + ): + """The constructor of HMDB51Dataset. + + Args: + split: dataset split which can be either "train" or "test" + batchsize_per_replica: batch size per model replica + shuffle: If true, shuffle the dataset + transform: a dict where transforms video and audio data + num_samples: if not None, it will subsample dataset + frames_per_clip: the No. of frames in a video clip + video_width: rescaled video width. If 0, keep original width + video_height: rescaled video height. If 0, keep original height + video_min_dimension: rescale video so that min(height, width) = + video_min_dimension. If 0, keep original video resolution. Note + only one of (video_width, video_height) and (video_min_dimension) + can be set + audio_samples: desired audio sample rate. If 0, keep original + audio sample rate. + step_between_clips: No. of frames between each clip. + frame_rate: desired video frame rate. If None, keep + orignal video frame rate. + clips_per_video: No. of clips to sample from each video + video_dir: path to video folder + splits_dir: path to dataset splitting file folder + fold: HMDB51 dataset has 3 folds. Valid values are 1, 2 and 3. + metadata_filepath: path to the dataset meta data + + """ + # dataset metadata includes the path of video file, the pts of frames in + # the video and other meta info such as video fps, duration, audio sample rate. + # Users do not need to know the details of metadata. The computing, loading + # and saving logic of metata are all handled inside of the dataset. + # Given the "metadata_file" path, if such file exists, we load it as meta data. + # Otherwise, we compute the meta data, and save it at "metadata_file" path. + metadata = None + if os.path.exists(metadata_filepath): + metadata = HMDB51Dataset.load_metadata( + metadata_filepath, video_dir=video_dir, update_file_path=True + ) + + dataset = HMDB51( + video_dir, + splits_dir, + frames_per_clip, + step_between_clips=step_between_clips, + frame_rate=frame_rate, + _precomputed_metadata=metadata, + fold=fold, + train=(split == "train"), + num_workers=torch.get_num_threads(), + _video_width=video_width, + _video_height=video_height, + _video_min_dimension=video_min_dimension, + _audio_samples=audio_samples, + ) + metadata = dataset.metadata + if metadata and not os.path.exists(metadata_filepath): + HMDB51Dataset.save_metadata(metadata, metadata_filepath) + + super().__init__( + dataset, + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + clips_per_video, + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "HMDB51Dataset": + """Instantiates a HMDB51Dataset from a configuration. + + Args: + config: A configuration for a HMDB51Dataset. + See :func:`__init__` for parameters expected in the config. + + Returns: + A HMDB51Dataset instance. + """ + required_args = ["split", "metadata_file", "video_dir", "splits_dir"] + assert all( + arg in config for arg in required_args + ), f"The arguments {required_args} are all required." + + split = config["split"] + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + ) = cls.parse_config(config) + + transform = build_video_field_transform_default(transform_config, split) + + return cls( + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + config["video_dir"], + config["splits_dir"], + config["fold"] + if "fold" in config + else 1, # HMDB51 has 3 folds. Use fold 1 by default + config["metadata_file"], + ) diff --git a/classy_vision/dataset/classy_imagenet.py b/classy_vision/dataset/classy_imagenet.py new file mode 100644 index 0000000000..440aa17973 --- /dev/null +++ b/classy_vision/dataset/classy_imagenet.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +from typing import Any, Callable, Dict, Optional, Union + +from classy_vision.dataset import ClassyDataset, register_dataset +from classy_vision.dataset.transforms import ClassyTransform, build_transforms +from torchvision.datasets import ImageFolder + + +@register_dataset("classy_imagenet") +class ImageNetDataset(ClassyDataset): + def __init__( + self, + split: str, + batchsize_per_replica: int, + shuffle: bool, + transform: Optional[Union[ClassyTransform, Callable]], + num_samples: Optional[int], + root: str, + ): + img_dir = os.path.join(root, split) + dataset = ImageFolder(img_dir) + super().__init__( + dataset, split, batchsize_per_replica, shuffle, transform, num_samples + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ImageNetDataset": + """Instantiates a ImageNetDataset from a configuration. + + Args: + config: A configuration for a ImageNetDataset. + See :func:`__init__` for parameters expected in the config. + + Returns: + A ImageNetDataset instance. + """ + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + ) = cls.parse_config(config) + split = config.get("split") + root = config.get("root") + + transform = build_transforms(transform_config) + return cls( + split=split, + batchsize_per_replica=batchsize_per_replica, + shuffle=shuffle, + transform=transform, + num_samples=num_samples, + root=root, + ) diff --git a/classy_vision/dataset/classy_kinetics400.py b/classy_vision/dataset/classy_kinetics400.py new file mode 100644 index 0000000000..6d0cc64367 --- /dev/null +++ b/classy_vision/dataset/classy_kinetics400.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +from typing import Any, Callable, Dict, List, Optional + +import torch +from torchvision.datasets.kinetics import Kinetics400 + +from . import register_dataset +from .classy_video_dataset import ClassyVideoDataset +from .transforms.util_video import build_video_field_transform_default + + +@register_dataset("kinetics400") +class Kinetics400Dataset(ClassyVideoDataset): + """Kinetics-400 is an action recognition video dataset, and it has 400 classes. + + Page: + It is originally published in (https://arxiv.org/pdf/1705.06950.pdf). + + This dataset consider every video as a collection of video clips of fixed size, + specified by ``frames_per_clip``, where the step in frames between each clip + is given by ``step_between_clips``. It uses clip sampler to sample clips + from each video. For training set, a random clip sampler is used to + sample a small number of clips (e.g. 1) from each video + For testing set, a uniform clip sampler is used to evenly sample a large + number of clips (e.g. 10) from the video. + + To give an example, for 2 videos with 10 and 15 frames respectively, if + ``frames_per_clip=5`` and ``step_between_clips=5``, the dataset size + will be (2 + 3) = 5, where the first two elements will come from video 1, + and the next three elements from video 2. Note that we drop clips which do + not have exactly ``frames_per_clip`` elements, so not all frames in a video + might be present. + + We assume videos are already trimmed to 10-second clip, and are stored in a + folder. + + It is built on top of Kinetics400 dataset class in TorchVision. + + """ + + def __init__( + self, + split: str, + batchsize_per_replica: int, + shuffle: bool, + transform: Callable, + num_samples: Optional[int], + frames_per_clip: int, + video_width: int, + video_height: int, + video_min_dimension: int, + audio_samples: int, + audio_channels: int, + step_between_clips: int, + frame_rate: Optional[int], + clips_per_video: int, + video_dir: str, + extensions: List[str], + metadata_filepath: str, + ): + """The constructor of Kinetics400Dataset. + + Args: + split: dataset split which can be either "train" or "test" + batchsize_per_replica: batch size per model replica + shuffle: If true, shuffle the dataset + transform: a dict where transforms video and audio data + num_samples: if provided, it will subsample dataset + frames_per_clip: the No. of frames in a video clip + video_width: rescaled video width. If 0, keep original width + video_height: rescaled video height. If 0, keep original height + video_min_dimension: rescale video so that min(height, width) = + video_min_dimension. If 0, keep original video resolution. Note + only one of (video_width, video_height) and (video_min_dimension) + can be set + audio_samples: desired audio sample rate. If 0, keep original + audio sample rate + audio_channels: desire No. of audio channel. If 0, keep original audio + channels + step_between_clips: No. of frames between each clip. + frame_rate: desired video frame rate. If None, keep + orignal video frame rate. + clips_per_video: No. of clips to sample from each video + video_dir: path to video folder + extensions: A list of file extensions, such as "avi" and "mp4". Only + video matching those file extensions are added to the dataset + metadata_filepath: path to the dataset meta data + + """ + # dataset metadata includes the path of video file, the pts of frames in + # the video and other meta info such as video fps, duration, audio sample rate. + # Users do not need to know the details of metadata. The computing, loading + # and saving logic of metata are all handled inside of the dataset. + # Given the "metadata_file" path, if such file exists, we load it as meta data. + # Otherwise, we compute the meta data, and save it at "metadata_file" path. + metadata = None + if os.path.exists(metadata_filepath): + metadata = Kinetics400Dataset.load_metadata( + metadata_filepath, video_dir=video_dir, update_file_path=True + ) + + dataset = Kinetics400( + video_dir, + frames_per_clip, + step_between_clips=step_between_clips, + frame_rate=frame_rate, + _precomputed_metadata=metadata, + extensions=extensions, + num_workers=torch.get_num_threads(), + _video_width=video_width, + _video_height=video_height, + _video_min_dimension=video_min_dimension, + _audio_samples=audio_samples, + _audio_channels=audio_channels, + ) + metadata = dataset.metadata + if metadata and not os.path.exists(metadata_filepath): + Kinetics400Dataset.save_metadata(metadata, metadata_filepath) + + super().__init__( + dataset, + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + clips_per_video, + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "Kinetics400Dataset": + """Instantiates a UCF101Dataset from a configuration. + + Args: + config: A configuration for a Kinetics400Dataset. + See :func:`__init__` for parameters expected in the config. + + Returns: + A Kinetics400Dataset instance. + """ + required_args = ["split", "metadata_file", "video_dir"] + assert all( + arg in config for arg in required_args + ), f"The arguments {required_args} are all required." + + split = config["split"] + audio_channels = config.get("audio_channels", 0) + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + ) = cls.parse_config(config) + extensions = config.get("extensions", ("mp4")) + + transform = build_video_field_transform_default(transform_config, split) + + return cls( + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + audio_channels, + step_between_clips, + frame_rate, + clips_per_video, + config["video_dir"], + extensions, + config["metadata_file"], + ) diff --git a/classy_vision/dataset/classy_synthetic_image.py b/classy_vision/dataset/classy_synthetic_image.py new file mode 100644 index 0000000000..4654f0471b --- /dev/null +++ b/classy_vision/dataset/classy_synthetic_image.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from typing import Any, Callable, Dict, List, Optional + +import torchvision.transforms as transforms + +from . import register_dataset +from .classy_dataset import ClassyDataset +from .core import RandomImageBinaryClassDataset +from .transforms import build_transforms +from .transforms.util import ImagenetConstants, build_field_transform_default_imagenet + + +@register_dataset("synthetic_image") +class SyntheticImageDataset(ClassyDataset): + """Classy Dataset which produces random synthetic images with binary targets. + + The underlying dataset sets targets based on the image channel, so users can + validate their setup by checking if they can get 100% accuracy on this dataset. + Useful for testing since the dataset is much faster to initialize and fetch samples + from, compared to real world datasets. + """ + + def __init__( + self, + batchsize_per_replica: int, + shuffle: bool, + transform: Optional[Callable], + num_samples: int, + crop_size: int, + class_ratio: float, + seed: int, + split: Optional[str] = None, + ) -> None: + """ + Args: + batchsize_per_replica: Positive integer indicating batch size for each + replica + shuffle: Whether we should shuffle between epochs + transform: Transform to be applied to each sample + num_samples: Number of samples to return + crop_size: Image size, used for both height and width + class_ratio: Ratio of the distribution of target classes + seed: Seed used for image generation. Use the same seed to generate the same + set of samples. + split: Split of dataset to use + """ + dataset = RandomImageBinaryClassDataset( + crop_size, class_ratio, num_samples, seed + ) + super().__init__( + dataset, split, batchsize_per_replica, shuffle, transform, num_samples + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "SyntheticImageDataset": + """Instantiates a SyntheticImageDataset from a configuration. + + Args: + config: A configuration for a SyntheticImageDataset. + See :func:`__init__` for parameters expected in the config. + + Returns: + A SyntheticImageDataset instance. + """ + assert all(key in config for key in ["crop_size", "class_ratio", "seed"]) + split = config.get("split") + crop_size = config["crop_size"] + class_ratio = config["class_ratio"] + seed = config["seed"] + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + ) = cls.parse_config(config) + + try: + transform = build_transforms(transform_config) + except Exception: + logging.error( + "We recently changed transform behavior" + " do you need to update your config?" + " See resnet50_synthetic_image_classy_config.json" + " as an example." + ) + raise + + return cls( + batchsize_per_replica, + shuffle, + transform, + num_samples, + crop_size, + class_ratio, + seed, + split=split, + ) diff --git a/classy_vision/dataset/classy_synthetic_video.py b/classy_vision/dataset/classy_synthetic_video.py new file mode 100644 index 0000000000..fe90c1bcdb --- /dev/null +++ b/classy_vision/dataset/classy_synthetic_video.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +from typing import Any, Callable, Dict + +from classy_vision.generic.distributed_util import get_rank, get_world_size +from torch.utils.data.distributed import DistributedSampler + +from . import register_dataset +from .classy_video_dataset import ClassyVideoDataset +from .core import RandomVideoDataset +from .transforms.util_video import build_video_field_transform_default + + +@register_dataset("synthetic_video") +class SyntheticVideoDataset(ClassyVideoDataset): + """Classy Dataset which produces random synthetic video clips. + + Useful for testing since the dataset is much faster to initialize and fetch samples + from, compared to real world datasets. + + Note: Unlike SyntheticImageDataset, this dataset generates targets randomly, + independent of the video clips. + """ + + def __init__( + self, + num_classes: int, + split: str, + batchsize_per_replica: int, + shuffle: bool, + transform: Callable, + num_samples: int, + frames_per_clip: int, + video_width: int, + video_height: int, + audio_samples: int, + clips_per_video: int, + ): + """The constructor of SyntheticVideoDataset. + + Args: + num_classes: Number of classes in the generated targets. + split: Split of dataset to use + batchsize_per_replica: batch size per model replica + shuffle: Whether we should shuffle between epochs + transform: Transform to be applied to each sample + num_samples: Number of samples to return + frames_per_clip: Number of frames in a video clip + video_width: Width of the video clip + video_height: Height of the video clip + audio_samples: Audio sample rate + clips_per_video: Number of clips per video + """ + dataset = RandomVideoDataset( + num_classes, + split, + num_samples, + frames_per_clip, + video_width, + video_height, + audio_samples, + clips_per_video, + ) + super().__init__( + dataset, + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + clips_per_video, + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "SyntheticVideoDataset": + """Instantiates a SyntheticVideoDataset from a configuration. + + Args: + config: A configuration for a SyntheticVideoDataset. + See :func:`__init__` for parameters expected in the config. + + Returns: + A SyntheticVideoDataset instance. + """ + split = config["split"] + num_classes = config["num_classes"] + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + ) = cls.parse_config(config) + + transform = build_video_field_transform_default(transform_config, split) + return cls( + num_classes, + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + frames_per_clip, + video_width, + video_height, + audio_samples, + clips_per_video, + ) + + @property + def video_clips(self): + raise NotImplementedError() + + def _get_sampler(self, epoch): + world_size = get_world_size() + rank = get_rank() + sampler = DistributedSampler( + self, num_replicas=world_size, rank=rank, shuffle=self.shuffle + ) + sampler.set_epoch(epoch) + return sampler diff --git a/classy_vision/dataset/classy_ucf101.py b/classy_vision/dataset/classy_ucf101.py new file mode 100644 index 0000000000..d94f521728 --- /dev/null +++ b/classy_vision/dataset/classy_ucf101.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +from typing import Any, Callable, Dict, Optional + +import torch +from torchvision.datasets.ucf101 import UCF101 + +from . import register_dataset +from .classy_video_dataset import ClassyVideoDataset +from .transforms.util_video import build_video_field_transform_default + + +@register_dataset("ucf101") +class UCF101Dataset(ClassyVideoDataset): + """UCF101 is an action recognition video dataset, and it has 101 classes. + + Page: + + This dataset consider every video as a collection of video clips of fixed size, + specified by ``frames_per_clip``, where the step in frames between each clip + is given by ``step_between_clips``. It uses clip sampler to sample clips + from each video. For training set, a random clip sampler is used to + sample a small number of clips (e.g. 1) from each video + For testing set, a uniform clip sampler is used to evenly sample a large + number of clips (e.g. 10) from the video. + + To give an example, for 2 videos with 10 and 15 frames respectively, + if ``frames_per_clip=5`` and ``step_between_clips=5``, + the dataset size will be (2 + 3) = 5, where the first two elements will come + from video 1, and the next three elements from video 2. Note that we drop + clips which do not have exactly ``frames_per_clip`` elements, so not all + frames in a video might be present. + + It is built on top of UCF101 dataset class in TorchVision. + + """ + + def __init__( + self, + split: str, + batchsize_per_replica: int, + shuffle: bool, + transform: Callable, + num_samples: Optional[int], + frames_per_clip: int, + video_width: int, + video_height: int, + video_min_dimension: int, + audio_samples: int, + step_between_clips: int, + frame_rate: Optional[int], + clips_per_video: int, + video_dir: str, + splits_dir: str, + fold: int, + metadata_filepath: str, + ): + """The constructor of UCF101Dataset. + + Args: + split: dataset split which can be either "train" or "test" + batchsize_per_replica: batch size per model replica + shuffle: If true, shuffle the dataset + transform: a dict where transforms video and audio data + num_samples: if not None, it will subsample dataset + frames_per_clip: the No. of frames in a video clip + video_width: rescaled video width. If 0, keep original width + video_height: rescaled video height. If 0, keep original height + video_min_dimension: rescale video so that min(height, width) = + video_min_dimension. If 0, keep original video resolution. Note + only one of (video_width, video_height) and (video_min_dimension) + can be set + audio_samples: desired audio sample rate. If 0, keep original + audio sample rate. + step_between_clips: No. of frames between each clip. + frame_rate: desired video frame rate. If None, keep original video + frame rate. + clips_per_video: No. of clips to sample from each video + video_dir: path to video folder + splits_dir: path to dataset splitting file folder + fold: UCF101 dataset has 3 folds. Valid values are 1, 2 and 3. + metadata_filepath: path to the dataset meta data + + """ + # dataset metadata includes the path of video file, the pts of frames in + # the video and other meta info such as video fps, duration, audio sample rate. + # Users do not need to know the details of metadata. The computing, loading + # and saving logic of metata are all handled inside of the dataset. + # Given the "metadata_file" path, if such file exists, we load it as meta data. + # Otherwise, we compute the meta data, and save it at "metadata_file" path. + metadata = None + if os.path.exists(metadata_filepath): + metadata = UCF101Dataset.load_metadata( + metadata_filepath, video_dir=video_dir, update_file_path=True + ) + + dataset = UCF101( + video_dir, + splits_dir, + frames_per_clip, + step_between_clips=step_between_clips, + frame_rate=frame_rate, + _precomputed_metadata=metadata, + fold=fold, + train=True if split == "train" else False, + num_workers=torch.get_num_threads(), + _video_width=video_width, + _video_height=video_height, + _video_min_dimension=video_min_dimension, + _audio_samples=audio_samples, + ) + metadata = dataset.metadata + if metadata and not os.path.exists(metadata_filepath): + UCF101Dataset.save_metadata(metadata, metadata_filepath) + + super().__init__( + dataset, + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + clips_per_video, + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "UCF101Dataset": + """Instantiates a UCF101Dataset from a configuration. + + Args: + config: A configuration for a UCF101Dataset. + See :func:`__init__` for parameters expected in the config. + + Returns: + A UCF101Dataset instance. + """ + required_args = ["split", "metadata_file", "video_dir", "splits_dir"] + assert all( + arg in config for arg in required_args + ), f"The arguments {required_args} are all required." + + split = config["split"] + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + ) = cls.parse_config(config) + + transform = build_video_field_transform_default(transform_config, split) + return cls( + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + config["video_dir"], + config["splits_dir"], + config["fold"] + if "fold" in config + else 1, # UCF101 has 3 folds. Use fold 1 by default + config["metadata_file"], + ) diff --git a/classy_vision/dataset/classy_video_dataset.py b/classy_vision/dataset/classy_video_dataset.py new file mode 100644 index 0000000000..318aa4c9be --- /dev/null +++ b/classy_vision/dataset/classy_video_dataset.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import multiprocessing +import os +from typing import Any, Callable, Dict, Optional + +import torch +from classy_vision.generic.distributed_util import get_rank, get_world_size +from torch.utils.data import Sampler +from torchvision.datasets.samplers.clip_sampler import ( + DistributedSampler, + RandomClipSampler, + UniformClipSampler, +) + +from .classy_dataset import ClassyDataset + + +class MaxLengthClipSampler(Sampler): + """MaxLengthClipSampler is a thin wrapper on top of clip samplers in TorchVision. + + It takes as input a TorchVision clip sampler, and an optional argument + `num_samples` to limit the number of samples. + """ + + def __init__(self, clip_sampler, num_samples=None): + """The constructor method of MaxLengthClipSampler. + + Args: + clip_sampler: clip sampler without a limit on the total number of clips + it can sample, such as RandomClipSampler and UniformClipSampler. + num_samples: if provided, it denotes the maximal number of clips the sampler + will return + + """ + self.clip_sampler = clip_sampler + self.num_samples = num_samples + + def __iter__(self): + num_samples = len(self) + n = 0 + for clip in self.clip_sampler: + if n < num_samples: + yield clip + n += 1 + else: + break + + def __len__(self): + full_size = len(self.clip_sampler) + if self.num_samples is None: + return full_size + + return min(full_size, self.num_samples) + + +class ClassyVideoDataset(ClassyDataset): + """Interface specifying what a ClassyVision video dataset is expected to provide. + """ + + def __init__( + self, + dataset: Any, + split: str, + batchsize_per_replica: int, + shuffle: bool, + transform: Callable, + num_samples: Optional[int], + clips_per_video: int, + ): + """The constructor method of ClassyVideoDataset. + + Args: + dataset: the underlying video dataset from either TorchVision or other + source. It should have an attribute `video_clips` of type + torchvision.datasets.video_utils.VideoClips + split: dataset split. Must be either "train" or "test" + batchsize_per_replica: batch size per model replica + shuffle: If true, shuffle video clips. + transform: callable function to transform video clip sample from + ClassyVideoDataset + num_samples: If provided, return at most `num_samples` video clips + clips_per_video: The number of clips sampled from each video + + """ + super(ClassyVideoDataset, self).__init__( + dataset, split, batchsize_per_replica, shuffle, transform, num_samples + ) + # Assignments: + self.clips_per_video = clips_per_video + + @classmethod + def parse_config(cls, config: Dict[str, Any]): + """Parse config to prepare arguments needed by the class constructor.""" + assert "frames_per_clip" in config, "frames_per_clip must be set" + + video_width = config.get("video_width", 0) + video_height = config.get("video_height", 0) + video_min_dimension = config.get("video_min_dimension", 0) + audio_samples = config.get("audio_samples", 0) + step_between_clips = config.get("step_between_clips", 1) + frame_rate = config.get("frame_rate", None) + clips_per_video = config.get("clips_per_video", 1) + + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + ) = super().parse_config(config) + + if not config["split"] == "train": + # At testing time, we do not crop frames but conduct a FCN-style evaluation. + # Video spatial resolution can vary from video to video. So we test one + # video at a time, and NO. of clips in a minibatch should be equal to + # No. of clips sampled from a video + if not batchsize_per_replica == clips_per_video: + logging.warning( + f"For testing, batchsize per replica ({batchsize_per_replica})" + + f"should be equal to clips_per_video ({clips_per_video})" + ) + + return ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + config["frames_per_clip"], + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + ) + + @classmethod + def load_metadata( + cls, + filepath: str, + video_dir: Optional[str] = None, + update_file_path: bool = False, + ) -> Dict[str, Any]: + """Load pre-computed video dataset meta data. + + Video dataset meta data computation takes minutes on small dataset and hours + on large dataset, and thus is time-consuming. However, it only needs to be + computed once, and can be saved into file. Later we can load the meta data + to reuse it. + + The format of meta data is defined in TorchVision as shown below. + https://github.com/pytorch/vision/blob/master/torchvision/datasets/ + video_utils.py#L131 + + For each video, meta data contains the video file path, presentation + timestamps of all video frames, and video fps. + + Args: + filepath: file path of pre-computed meta data + video_dir: If provided, the folder where video files are stored. + update_file_path: If true, replace the directory part of video file path + in meta data with the actual video directory provided in `video_dir`. + This is needed for successsfully reusing pre-computed meta data + when video directory has been moved and it is no longer consitent + with the full video file path saved in the meta data. + """ + metadata = torch.load(filepath) + if video_dir is not None and update_file_path: + # video path in meta data can be computed in a different root video folder + # If we use a different root video folder, we need to update the video paths + assert os.path.exists(video_dir), "folder does not exist: %s" % video_dir + for idx, video_path in enumerate(metadata["video_paths"]): + # video path template is $VIDEO_DIR/$CLASS_NAME/$VIDEO_FILE + dirname, filename = os.path.split(video_path) + _, class_name = os.path.split(dirname) + metadata["video_paths"][idx] = os.path.join( + video_dir, class_name, filename + ) + return metadata + + @classmethod + def save_metadata(cls, metadata: Dict[str, Any], filepath: str): + """Save dataset meta data into a file. + + Args: + metadata: dataset meta data, which contains video meta infomration, such + as video file path, video fps, video frame timestamp in each video. + For the format of dataset meta data, check the TorchVision + documentations below. + https://github.com/pytorch/vision/blob/master/torchvision/datasets + /video_utils.py#L132-L137 + + filepath: file path where the meta data will be saved + + """ + filedir = os.path.dirname(filepath) + if not os.path.exists(filedir): + try: + os.mkdirs(filedir) + except Exception as err: + logging.warn(f"Fail to create folder: {filedir}") + raise err + logging.info(f"Save metadata to file: {filedir}") + try: + torch.save(metadata, filepath) + except ValueError: + logging.warn(f"Fail to save metadata to file: {filepath}") + + @property + def video_clips(self): + """Attribute video_clips. + + It is used in `_get_sampler` method. Its data type should be + torchvision.datasets.video_utils.VideoClips. + """ + return self.dataset.video_clips + + def _get_sampler(self, epoch) -> "DistributedSampler": + if self.split == "train": + # For video model training, we don't necessarily want to use all possible + # clips in the video in one training epoch. More often, we randomly + # sample at most N clips per training video. In practice, N is often 1 + clip_sampler = RandomClipSampler(self.video_clips, self.clips_per_video) + else: + # For video model testing, we sample N evenly spaced clips per test + # video. We will simply average predictions over them + clip_sampler = UniformClipSampler(self.video_clips, self.clips_per_video) + clip_sampler = MaxLengthClipSampler(clip_sampler, num_samples=self.num_samples) + world_size = get_world_size() + rank = get_rank() + sampler = DistributedSampler( + clip_sampler, + num_replicas=world_size, + rank=rank, + shuffle=self.shuffle, + group_size=self.clips_per_video, + ) + sampler.set_epoch(epoch) + return sampler + + def iterator(self, *args, **kwargs): + """Overrides the implementation in parent class `ClassyDataset`. + + You can check all the usable positional and keyword arguments in parent + class `ClassyDataset.iterator(...)`. + For video dataset, it may use VideoClips class from TorchVision, + which may use a cpp python extension for video decoding when video backend + is set to `video_reader`. In such case, it is difficult to use "spawning" + as multiprocessing start method. Thus we choose "fork" as multiprocessing + start method. + """ + if "num_workers" in kwargs and kwargs["num_workers"] > 0: + mp = multiprocessing.get_context("fork") + kwargs["multiprocessing_context"] = mp + return super(ClassyVideoDataset, self).iterator(*args, **kwargs) diff --git a/classy_vision/dataset/core/__init__.py b/classy_vision/dataset/core/__init__.py new file mode 100644 index 0000000000..a7f5c1d4ea --- /dev/null +++ b/classy_vision/dataset/core/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from .list_dataset import ListDataset +from .random_image_datasets import RandomImageBinaryClassDataset, RandomImageDataset +from .random_video_datasets import RandomVideoDataset + + +__all__ = [ + "ListDataset", + "RandomImageBinaryClassDataset", + "RandomImageDataset", + "RandomVideoDataset", +] diff --git a/classy_vision/dataset/core/list_dataset.py b/classy_vision/dataset/core/list_dataset.py new file mode 100644 index 0000000000..3fe1d77c89 --- /dev/null +++ b/classy_vision/dataset/core/list_dataset.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from torchvision.datasets.folder import default_loader + + +class ListDataset: + """Dataset that loads data using a list of items, a corresponding loader, + and a list of metadata. The default loader is an image file loader so this + dataset can be used directly with a list of image files. + You can use it without metadata if you set metadata parameter to None + """ + + def __init__(self, files, metadata, loader=default_loader): + """ + metadata (List[Dict[Type]] or List[Type], Optional): + metadata to be added to each sample. + The Type can be anything that pytorch default_collate can handle. + If Type is tensor, make sure that the tensors are of same dimension. + """ + if metadata is not None: + assert isinstance(metadata, list), "metadata should be a list" + assert len(files) == len(metadata) + assert len(files) > 0, "Empty ListDataset is not allowed" + if not isinstance(metadata[0], dict): + metadata = [{"target": target} for target in metadata] + self.files = files + self.metadata = metadata + self.loader = loader + + def __getitem__(self, idx): + assert idx >= 0 and idx < len(self) + img = self.loader(self.files[idx]) + item = {"input": img} + if self.metadata is not None: + item.update(self.metadata[idx]) + + return item + + def __len__(self): + return len(self.files) diff --git a/classy_vision/dataset/core/random_image_datasets.py b/classy_vision/dataset/core/random_image_datasets.py new file mode 100644 index 0000000000..a084c044e9 --- /dev/null +++ b/classy_vision/dataset/core/random_image_datasets.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from enum import Enum + +import numpy as np +from PIL import Image + +from ...generic.util import numpy_seed + + +class SampleType(Enum): + DICT = "dict" + TUPLE = "tuple" + LIST = "list" + + +def _get_typed_sample(input, target, sample_type): + if sample_type == SampleType.DICT: + return {"input": input, "target": target} + elif sample_type == SampleType.TUPLE: + return (input, target) + elif sample_type == SampleType.LIST: + return [input, target] + else: + raise TypeError("Provided sample_type is not dict, list, tuple") + + +class RandomImageDataset: + def __init__( + self, + crop_size, + num_channels, + num_classes, + num_samples, + seed, + sample_type=SampleType.DICT, + ): + self.crop_size = crop_size + self.num_channels = num_channels + self.num_classes = num_classes + self.num_samples = num_samples + self.seed = seed + self.sample_type = sample_type + + def __getitem__(self, idx): + with numpy_seed(self.seed + idx): + input = Image.fromarray( + ( + np.random.standard_normal( + [self.crop_size, self.crop_size, self.num_channels] + ) + * 255 + ).astype(np.uint8) + ) + target = np.random.randint(self.num_classes) + return _get_typed_sample(input, target, self.sample_type) + + def __len__(self): + return self.num_samples + + +class RandomImageBinaryClassDataset: + def __init__( + self, crop_size, class_ratio, num_samples, seed, sample_type=SampleType.DICT + ): + self.crop_size = crop_size + # User Defined Class Imbalace Ratio + self.class_ratio = class_ratio + self.num_samples = num_samples + self.seed = seed + self.sample_type = sample_type + + def __getitem__(self, idx): + with numpy_seed(self.seed + idx): + class_id = int(np.random.random() < self.class_ratio) + image = np.zeros((self.crop_size, self.crop_size, 3)) + image[:, :, class_id] = np.random.random([self.crop_size, self.crop_size]) + image[:, :, 2] = np.random.random([self.crop_size, self.crop_size]) + input = Image.fromarray((image * 255).astype(np.uint8)) + target = class_id + return _get_typed_sample(input, target, self.sample_type) + + def __len__(self): + return self.num_samples diff --git a/classy_vision/dataset/core/random_video_datasets.py b/classy_vision/dataset/core/random_video_datasets.py new file mode 100644 index 0000000000..396801ec63 --- /dev/null +++ b/classy_vision/dataset/core/random_video_datasets.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch + +from ...generic.util import torch_seed + + +class RandomVideoDataset: + def __init__( + self, + num_classes, + split, + num_samples, + frames_per_clip, + video_width, + video_height, + audio_samples, + clips_per_video, + seed=10, + ): + self.num_classes = num_classes + self.split = split + # video config + self.video_channels = 3 + self.num_samples = num_samples + self.frames_per_clip = frames_per_clip + self.video_width = video_width + self.video_height = video_height + # audio config + self.audio_samples = audio_samples + self.clips_per_video = clips_per_video + # misc config + self.seed = seed + + def __getitem__(self, idx): + if self.split == "train": + # assume we only sample 1 clip from each training video + target_seed_offset = idx + else: + # for video model testing, clips from the same video share the same + # target label + target_seed_offset = idx // self.clips_per_video + with torch_seed(self.seed + target_seed_offset): + target = torch.randint(0, self.num_classes, (1,)).item() + + with torch_seed(self.seed + idx): + return { + "input": { + "video": torch.randint( + 0, + 256, + ( + self.frames_per_clip, + self.video_height, + self.video_width, + self.video_channels, + ), + dtype=torch.uint8, + ), + "audio": torch.rand((self.audio_samples, 1), dtype=torch.float), + }, + "target": target, + } + + def __len__(self): + return self.num_samples diff --git a/classy_vision/dataset/generic/hive.py b/classy_vision/dataset/generic/hive.py new file mode 100644 index 0000000000..8d01893a6e --- /dev/null +++ b/classy_vision/dataset/generic/hive.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +import logging +import os +import sys + +# NOTE: Edit /usr/local/jdk-8u60-64/jre/lib/logging.properties to hide all logs +import metastore +from hiveio import par_init +from hiveio_cpp import hiveio + + +# set classpath: +par_init.install_class_path() + + +def download_from_hive( + namespace, + tablename, + everstore_column, + label_column, + partition_column, + partition_column_values, +): + """ + Function to load IDs from Hive. + """ + # disable HiveIO info logs + os.environ["GLOG_minloglevel"] = "3" + # set partition name and columns: + partitions = [ + "{0}={1}".format(partition_column, partition_column_value) + for partition_column_value in partition_column_values + ] + columns = [everstore_column, label_column] + + # check metastore for number of rows: + sys.argv = ["."] + ms = metastore.metastore(namespace=namespace) + num_rows = 0 + for partition in partitions: + assert ms.exists_partition( + tablename, partition + ), "partition not found: {0}/{1}".format(tablename, partition) + + p = ms.get_partition(tablename, partition) + num_rows += int(p.parameters["numRows"]) + + # start HiveIO reader: + batch_size = 10000 + hiveio.start_reading( + namespace=namespace, + table=tablename, + partitions=partitions, + column_names=columns, + batch_size=batch_size, + max_queued_batches=10, + ) + + # read all data: + handles, labels, cnt = [None] * num_rows, [None] * num_rows, 0 + while True: + # read and process batch: + batch = hiveio.get_batch() + if batch == [] or cnt + len(batch) > num_rows: + break + cur_handles = [val[0] for val in batch] + cur_labels = [val[1] for val in batch] + + # store data: + handles[cnt : cnt + len(batch)] = cur_handles + labels[cnt : cnt + len(batch)] = cur_labels + cnt += len(batch) + logging.info("Downloaded {0} of {1} rows.".format(cnt, num_rows)) + + # close reader and return data: + hiveio.stop_reading() + if cnt < num_rows: + handles = handles[0:cnt] + labels = labels[0:cnt] + return handles, labels + + +def get_partition_to_num_rows( + namespace, tablename, partition_column, partition_column_values +): + """ + Helper function to get total num_rows in hive for given + partition_column_values. + """ + partitions = { + "{0}={1}".format(partition_column, partition_column_value) + for partition_column_value in partition_column_values + } + # Setting higher number of retries, as during testing, sometimes default + # "retries" values didn't seem enough in some cases. + ms = metastore.metastore( + namespace=namespace, + meta_only=True, + retries=10, + # timeout in milliseconds. + timeout=1800000, + ) + partition_to_num_rows = {} + + all_partitions = ms.get_partitions(tablename) + for hive_partition in all_partitions: + assert "numRows" in hive_partition.parameters, ( + "numRows not in hive_partition.parameters," + "Do not use Presto tables, only Hive tables!')" + ) + if hive_partition.partitionName in partitions: + patition_column_value = hive_partition.partitionName.split("=")[1] + partition_to_num_rows[patition_column_value] = int( + hive_partition.parameters["numRows"] + ) + + return partition_to_num_rows diff --git a/classy_vision/dataset/image_path_dataset.py b/classy_vision/dataset/image_path_dataset.py new file mode 100644 index 0000000000..f4f2f6c391 --- /dev/null +++ b/classy_vision/dataset/image_path_dataset.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os.path +from typing import Any, Callable, Dict, List, Optional, Union + +import torch +import torchvision.datasets as datasets +import torchvision.transforms as transforms + +from .classy_dataset import ClassyDataset +from .core import ListDataset +from .transforms import build_transforms +from .transforms.classy_transform import ClassyTransform +from .transforms.util import TupleToMapTransform + + +def _load_dataset(image_paths, targets): + if targets is None: + targets = [torch.tensor([]) for _ in image_paths] + if isinstance(image_paths, str): + assert os.path.isdir( + image_paths + ), "Expect image_paths to be a dir when it is a string" + dataset = datasets.ImageFolder(image_paths) + preproc_transform = TupleToMapTransform(list_of_map_keys=["input", "target"]) + else: + dataset = ListDataset(image_paths, targets) + preproc_transform = None + return dataset, preproc_transform + + +class ImagePathDataset(ClassyDataset): + """Dataset which reads images from a local filesystem. Implements ClassyDataset. + + The image paths provided can be: + - A single directory location, in which case the data is expected to be + arranged in a format similar to :class:`torchvision.datasets.ImageFolder`. + The targets will be inferred from the directory structure. + - A list of paths, in which case the list will contain the paths to all the + images. In this situation, the targets can be specified by the targets + argument. + """ + + def __init__( + self, + batchsize_per_replica: int, + shuffle: bool, + transform: Optional[Union[ClassyTransform, Callable]], + num_samples: Optional[int], + image_paths: Union[str, List[str]], + targets: Optional[List[Any]] = None, + split: Optional[str] = None, + ): + """Constructor for ImagePathDataset. + + Args: + batchsize_per_replica: Positive integer indicating batch size for each + replica + shuffle: Whether we should shuffle between epochs + transform: Transform to be applied to each sample + num_samples: When set, this restricts the number of samples provided by + the dataset + image_paths: A directory or a list of file paths where images can be found. + targets: If a list of file paths is specified, this argument can + be used to specify a target for each path (must be same length + as list of file paths). If no targets are needed or image_paths is + a directory, then targets should be None. + split: Split of dataset ("train", "test") + + """ + # TODO(@mannatsingh): we should be able to call build_dataset() to create + # datasets from this class. + assert image_paths is not None, "image_paths needs to be provided" + assert targets is None or isinstance(image_paths, list), ( + "targets cannot be specified when image_paths is a directory containing " + "the targets in the directory structure" + ) + dataset, preproc_transform = _load_dataset(image_paths, targets) + super().__init__( + dataset, split, batchsize_per_replica, shuffle, transform, num_samples + ) + # Some of the base datasets from _load_dataset have different + # sample formats, the preproc_transform should map them all to + # the dict {"input": img, "target": label} format + if preproc_transform is not None: + self.transform = transforms.Compose([preproc_transform, self.transform]) + + @classmethod + def from_config( + cls, + config: Dict[str, Any], + image_paths: Union[str, List[str]], + targets: Optional[List[Any]] = None, + ): + """Instantiates ImagePathDataset from a config. + + Because image_paths / targets can be arbitrarily long, we + allow passing in the image paths and targets from python in + addition to the configuration parameter. + + Args: + config: A configuration for ImagePathDataset. + See :func:`__init__` for parameters expected in the config. + image_paths: Directory or list of image paths. + See :func:`__init__` for more details + targets: Optional list of targets for dataset. + See :func:`__init__` for more details + """ + split = config.get("split") + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + ) = cls.parse_config(config) + + transform = build_transforms(transform_config) + return cls( + batchsize_per_replica, + shuffle, + transform, + num_samples, + image_paths, + targets=targets, + split=split, + ) diff --git a/classy_vision/dataset/transforms/__init__.py b/classy_vision/dataset/transforms/__init__.py new file mode 100644 index 0000000000..812849d73f --- /dev/null +++ b/classy_vision/dataset/transforms/__init__.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +import copy +from pathlib import Path +from typing import Any, Callable, Dict, List + +import torchvision.transforms as transforms +import torchvision.transforms._transforms_video as transforms_video +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_transform import ClassyTransform + + +FILE_ROOT = Path(__file__).parent + + +TRANSFORM_REGISTRY = {} + + +def build_transform(transform_config: Dict[str, Any]) -> Callable: + """Builds a ClassyTransform from a config. + + This assumes a 'name' key in the config which is used to determine what + transform class to instantiate. For instance, a config `{"name": + "my_transform", "foo": "bar"}` will find a class that was registered as + "my_transform" (see :func:`register_transform`) and call .from_config on + it. + + In addition to transforms registered with :func:`register_transform`, we + also support instantiating transforms available in the + `torchvision.transforms` module. Any keys in the config will get expanded + to parameters of the transform constructor. For instance, the following + call will instantiate a :class:`torchvision.transforms.CenterCrop`: + + build_transform({"name": "CenterCrop", "size": 224}) + """ + assert ( + "name" in transform_config + ), f"name not provided for transform: {transform_config}" + name = transform_config["name"] + transform_args = copy.deepcopy(transform_config) + del transform_args["name"] + if name in TRANSFORM_REGISTRY: + return TRANSFORM_REGISTRY[name].from_config(transform_args) + # the name should be available in torchvision.transforms + assert hasattr(transforms, name) or hasattr(transforms_video, name), ( + f"{name} isn't a registered tranform" + ", nor is it available in torchvision.transforms" + ) + if hasattr(transforms, name): + return getattr(transforms, name)(**transform_args) + else: + return getattr(transforms_video, name)(**transform_args) + + +def build_transforms(transforms_config: List[Dict[str, Any]]) -> Callable: + """ + Builds a transform from the list of transform configurations. + """ + transform_list = [build_transform(config) for config in transforms_config] + return transforms.Compose(transform_list) + + +def register_transform(name: str): + """Registers a ClassyTransform subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyTransform from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyTransform subclass like this: + + @register_transform("my_transform") + class MyTransform(ClassyTransform): + ... + + To instantiate a transform from a configuration file, see + :func:`build_transform`.""" + + def register_transform_cls(cls: Callable[..., Callable]): + if name in TRANSFORM_REGISTRY: + raise ValueError("Cannot register duplicate transform ({})".format(name)) + TRANSFORM_REGISTRY[name] = cls + return cls + + return register_transform_cls + + +# automatically import any Python files in the transforms/ directory +import_all_modules(FILE_ROOT, "classy_vision.dataset.transforms") + +from .lighting_transform import LightingTransform # isort:skip +from .util import ApplyTransformToKey # isort:skip +from .util import ImagenetAugmentTransform # isort:skip +from .util import ImagenetAugmentTransform # isort:skip +from .util import ImagenetNoAugmentTransform # isort:skip +from .util import GenericImageTransform # isort:skip +from .util import TupleToMapTransform # isort:skip + + +__all__ = [ + "ClassyTransform", + "ImagenetAugmentTransform", + "ImagenetNoAugmentTransform", + "GenericImageTransform", + "ApplyTransformToKey", + "TupleToMapTransform", + "LightingTransform", + "register_transform", + "build_transform", + "build_transforms", +] diff --git a/classy_vision/dataset/transforms/classy_transform.py b/classy_vision/dataset/transforms/classy_transform.py new file mode 100644 index 0000000000..589800d050 --- /dev/null +++ b/classy_vision/dataset/transforms/classy_transform.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from abc import ABC, abstractmethod +from typing import Any, Dict + + +class ClassyTransform(ABC): + """ + Class representing a data transform abstraction. + + Data transform is most often needed to pre-process input data (e.g. image, video) + before sending it to a model. But it can also be used for other purposes. + """ + + @abstractmethod + def __call__(self, image): + """ + The interface `__call__` is used to transform the input data. It should contain + the actual implementation of data transform. + + Args: + image: input image data + """ + pass + + @classmethod + def from_config(cls, config: Dict[str, Any]): + return cls(**config) diff --git a/classy_vision/dataset/transforms/lighting_transform.py b/classy_vision/dataset/transforms/lighting_transform.py new file mode 100644 index 0000000000..2d5de21399 --- /dev/null +++ b/classy_vision/dataset/transforms/lighting_transform.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch + +from . import register_transform +from .classy_transform import ClassyTransform + + +_IMAGENET_EIGEN_VAL = [0.2175, 0.0188, 0.0045] +_IMAGENET_EIGEN_VEC = [ + [-144.7125, 183.396, 102.2295], + [-148.104, -1.1475, -207.57], + [-148.818, -177.174, 107.1765], +] + +_DEFAULT_COLOR_LIGHTING_STD = 0.1 + + +@register_transform("lighting") +class LightingTransform(ClassyTransform): + """ + Lighting noise(AlexNet - style PCA - based noise). + This trick was originally used in AlexNet paper + https://papers.nips.cc/paper/4824-imagenet-classification + -with-deep-convolutional-neural-networks.pdf + + The eigen values and eigen vectors, are taken from caffe2 ImageInputOp.h + (https://github.com/pytorch/pytorch/blob/master/caffe2/image/image_input_op.h#L265). + """ + + def __init__( + self, + alphastd=_DEFAULT_COLOR_LIGHTING_STD, + eigval=_IMAGENET_EIGEN_VAL, + eigvec=_IMAGENET_EIGEN_VEC, + ): + self.alphastd = alphastd + # Divide by 255 as the Lighting operation is expected to be applied + # on `img` pixels ranging between [0.0, 1.0] + self.eigval = torch.tensor(eigval) / 255.0 + self.eigvec = torch.tensor(eigvec) / 255.0 + + def __call__(self, img): + """ + img: (C x H x W) Tensor with values in range [0.0, 1.0] + """ + assert ( + img.min() >= 0.0 and img.max() <= 1.0 + ), "Image should be normalized by 255 and be in range [0.0, 1.0]" + if self.alphastd == 0: + return img + + alpha = img.new().resize_(3).normal_(0, self.alphastd) + rgb = ( + self.eigvec.type_as(img) + .clone() + .mul(alpha.view(1, 3).expand(3, 3)) + .mul(self.eigval.view(1, 3).expand(3, 3)) + .sum(1) + .squeeze() + ) + + return img.add(rgb.view(3, 1, 1).expand_as(img)) diff --git a/classy_vision/dataset/transforms/util.py b/classy_vision/dataset/transforms/util.py new file mode 100644 index 0000000000..06cc8e7cbd --- /dev/null +++ b/classy_vision/dataset/transforms/util.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import torchvision.transforms as transforms + +from . import ClassyTransform, build_transforms, register_transform + + +class ImagenetConstants: + """Constant variables related to the image classification. + + MEAN: often used to be subtracted from image RGB value. Computed on ImageNet. + STD: often used to divide the image RGB value after mean centering. Computed + on ImageNet. + CROP_SIZE: the size of image cropping which is often the input to deep network. + RESIZE: the size of rescaled image. + + """ + + MEAN = [0.485, 0.456, 0.406] + STD = [0.229, 0.224, 0.225] + CROP_SIZE = 224 + RESIZE = 256 + + +@register_transform("apply_transform_to_key") +class ApplyTransformToKey: + """Serializable class that applies a transform to a key specified field in samples. + """ + + def __init__(self, transform: Callable, key: Union[int, str] = "input") -> None: + """The constructor method of ApplyTransformToKey class. + + Args: + transform: a callable function that takes sample data of type dict as input + key: the key in sample whose corresponding value will undergo + the transform + + """ + self.key: Union[int, str] = key + self.transform: Callable = transform + + @classmethod + def from_config(cls, config: Dict[str, Any]): + transform = build_transforms(config["transforms"]) + + return cls(transform=transform, key=config["key"]) + + def __call__( + self, sample: Union[Tuple[Any], Dict[str, Any]] + ) -> Union[Tuple[Any], Dict[str, Any]]: + """Updates sample by applying a transform to the value at the specified key. + + Args: + sample: input sample which will be transformed + + """ + if sample is None: + return sample + + # Asserts + deal with tuple immutability + convert_to_tuple = False + if isinstance(sample, dict): + assert ( + self.key in sample + ), "This transform only supports dicts with key '{}'".format(self.key) + elif isinstance(sample, (tuple, list)): + assert self.key < len( + sample + ), "This transform only supports tuples / lists with key less " + "than {length}, key provided {key}".format(length=len(sample), key=self.key) + # Convert to list for transformation + if isinstance(sample, tuple): + convert_to_tuple = True + sample = list(sample) + + sample[self.key] = self.transform(sample[self.key]) + if convert_to_tuple: + sample = tuple(sample) + + return sample + + +@register_transform("imagenet_augment") +class ImagenetAugmentTransform(ClassyTransform): + """The default image transform with data augmentation. + + It is often useful for training models on Imagenet. It sequentially resizes + the image into a random scale, takes a random spatial cropping, randomly flips + the image horizontally, transforms PIL image data into a torch.Tensor and + normalizes the pixel values by mean subtraction and standard deviation division. + """ + + def __init__( + self, + crop_size: int = ImagenetConstants.CROP_SIZE, + mean: List[float] = ImagenetConstants.MEAN, + std: List[float] = ImagenetConstants.STD, + ): + """The constructor method of ImagenetAugmentTransform class. + + Args: + crop_size: expected output size of random cropping + mean: a 3-tuple denoting the pixel RGB mean + std: a 3-tuple denoting the pixel RGB standard deviation + + """ + self.transform = transforms.Compose( + [ + transforms.RandomResizedCrop(crop_size), + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + transforms.Normalize(mean=mean, std=std), + ] + ) + + def __call__(self, img): + """Callable function which applies the tranform to the input image. + + Args: + image: input image that will undergo the transform + + """ + return self.transform(img) + + +@register_transform("imagenet_no_augment") +class ImagenetNoAugmentTransform(ClassyTransform): + """The default image transform without data augmentation. + + It is often useful for testing models on Imagenet. It sequentially resizes + the image, takes a central cropping, transforms PIL image data into a + torch.Tensor and normalizes the pixel values by mean subtraction and standard + deviation division. + + """ + + def __init__( + self, + resize: int = ImagenetConstants.RESIZE, + crop_size: int = ImagenetConstants.CROP_SIZE, + mean: List[float] = ImagenetConstants.MEAN, + std: List[float] = ImagenetConstants.STD, + ): + """The constructor method of ImagenetNoAugmentTransform class. + + Args: + resize: expected image size after resizing + crop_size: expected size of central cropping + mean: a 3-tuple denoting the pixel RGB mean + std: a 3-tuple denoting the pixel RGB standard deviation + + """ + self.transform = transforms.Compose( + [ + transforms.Resize(resize), + transforms.CenterCrop(crop_size), + transforms.ToTensor(), + transforms.Normalize(mean=mean, std=std), + ] + ) + + def __call__(self, img): + """Callable function which applies the tranform to the input image. + + Args: + image: input image that will undergo the transform + + """ + return self.transform(img) + + +@register_transform("generic_image_transform") +class GenericImageTransform(ClassyTransform): + """Default transform for images used in the classification task + + This transform does several things. First, it expects a tuple or + list input (torchvision datasets supply tuples / lists). Second, + it applies a user-provided image transforms to the first entry in + the tuple (again, matching the torchvision tuple format). Third, + it transforms the tuple to a dict sample with entries "input" and + "target". + + The defaults are for the standard imagenet augmentations + + This is just a convenience wrapper to cover the common + use-case. You can get the same behavior by composing torchvision + transforms + ApplyTransformToKey + TupleToMap. + + """ + + def __init__( + self, transform: Optional[Callable] = None, split: Optional[str] = None + ): + """Constructor for GenericImageTransfrom + + Args: + transform: A callable or ClassyTransform to be applied to the image only + split: 'train' or 'test'. Only one of the two arguments + should be specified + """ + assert ( + transform is not None or split is not None + ), "One of transform / split must be specified" + assert ( + transform is None or split is None + ), "Only one of transform / split should be specified" + assert split in [None, "train", "test"], ( + "If specified, split should be either 'train' or 'test', " + "instead got {}".format(split) + ) + + if transform is not None: + self._transform = transform + + if split is not None: + self._transform = ( + ImagenetAugmentTransform() + if split == "train" + else ImagenetNoAugmentTransform() + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]): + transform = None + if "transforms" in config: + transform = build_transforms(config["transforms"]) + split = config.get("split") + return cls(transform, split) + + def __call__(self, sample: Tuple[Any]): + """Applied transform to sample + + Args: + sample: A tuple with length >= 2. The first entry should + be the image data, the second entry should be the + target data. + """ + image = sample[0] + transformed_image = self._transform(image) + new_sample = {"input": transformed_image, "target": sample[1]} + # Any additional metadata is just appended under index of tuple + if len(sample) > 2: + for i in range(2, len(sample)): + new_sample[str(i)] = sample[i] + + return new_sample + + +@register_transform("tuple_to_map") +class TupleToMapTransform(ClassyTransform): + """A transform which maps image data from tuple to dict. + + This transform takes a sample of the form (data1, data2, ...) and + returns a sample of the form {key1: data1, key2: data2, ...} + + It is useful for mapping output from datasets like the PyTorch + ImageFolder dataset (tuple) to dict with named data fields. + + If sample is already a dict with the required keys, pass sample through. + + """ + + def __init__(self, list_of_map_keys: List[str]): + """The constructor method of TupleToMapTransform class. + + Args: + list_of_map_keys: a list of dict keys that will be mapped to item + in the input sample of data type list + + """ + self._map_keys = list_of_map_keys + + def __call__(self, sample): + """Transform sample from type tuple to type dict. + + Args: + sample: input sample which will be transformed + + """ + # If already a dict/map with appropriate keys, exit early + if isinstance(sample, dict): + for key in self._map_keys: + assert ( + key in sample + ), "Sample {sample} must be a tuple or a dict with keys {keys}".format( + sample=str(sample), keys=str(self._map_keys) + ) + return sample + + assert len(sample) == len(self._map_keys), ( + "Provided sample tuple must have same number of keys " + "as provided to transform" + ) + output_sample = {} + for idx, s in enumerate(sample): + output_sample[self._map_keys[idx]] = s + + return output_sample + + +DEFAULT_KEY_MAP = TupleToMapTransform(["input", "target"]) + + +def build_field_transform_default_imagenet( + config: Optional[List[Dict[str, Any]]], + default_transform: Optional[Callable] = None, + split: Optional[bool] = None, + key: Union[int, str] = "input", + key_map_transform: Optional[Callable] = DEFAULT_KEY_MAP, +) -> Callable: + """Returns a ApplyTransformToKey which applies a transform on the specified key. + + The transform is built from the config, if it is not None. + + Otherwise, uses one of the two mutually exclusive args: If + default_transform is not None, it is used. If split is not None, + imagenet transforms are used, using augmentation for "train", no + augmentation otherwise. + + This function also provides an additional + function for mapping from tuples (or other keys) to a desired set + of keys + + Args: + config: field transform config + default_transform: used if config is None + split: split for dataset, e.g. "train" or "test" + key: Key to apply transform to + key_map_transform: Used to produce desired map / keys + (e.g. for torchvision datasets, default samples is a + tuple so this argument can be used to map + (input, target) -> {"input": input, "target": target}) + + """ + assert ( + default_transform is None or split is None + ), "Can only specify one of default_transform and split" + if config is None: + if default_transform is not None: + transform = default_transform + elif split is not None: + transform = ( + ImagenetAugmentTransform() + if split == "train" + else ImagenetNoAugmentTransform() + ) + else: + raise ValueError("No transform config provided with no defaults") + else: + transform = build_transforms(config) + + transform = ApplyTransformToKey(transform, key=key) + if key_map_transform is None: + return transform + + return transforms.Compose([key_map_transform, transform]) + + +def default_unnormalize(img): + """Default unnormalization transform which undo the "transforms.Normalize". + + Specially, it cancels out mean subtraction and standard deviation division. + + Args: + img (torch.Tensor): image data to which the transform will be applied + + """ + # TODO T39752655: Allow this to be configurable + img = img.clone() + for channel, std, mean in zip(img, ImagenetConstants.STD, ImagenetConstants.MEAN): + channel.mul_(std).add_(mean) + return img diff --git a/classy_vision/dataset/transforms/util_video.py b/classy_vision/dataset/transforms/util_video.py new file mode 100644 index 0000000000..f2cd264006 --- /dev/null +++ b/classy_vision/dataset/transforms/util_video.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import random +from typing import Any, Callable, Dict, List, Optional, Union + +import torch +import torchvision.transforms as transforms +import torchvision.transforms._transforms_video as transforms_video + +from . import ClassyTransform, build_transforms, register_transform +from .util import ApplyTransformToKey, ImagenetConstants + + +class VideoConstants: + """Constant variables related to the video classification. + + Use the same mean/std from image classification to enable the parameter + inflation where parameters of 2D conv in image model can be inflated into + 3D conv in video model. + + MEAN: often used to be subtracted from pixel RGB value. + STD: often used to divide the pixel RGB value after mean centering. + SIZE_RANGE: a (min_size, max_size) tuple which denotes the range of + size of the rescaled video clip. + CROP_SIZE: the size of spatial cropping in the video clip. + """ + + MEAN = ImagenetConstants.MEAN # + STD = ImagenetConstants.STD + SIZE_RANGE = (128, 160) + CROP_SIZE = 112 + + +def _get_rescaled_size(scale, h, w): + if h < w: + new_h = scale + new_w = int(scale * w / h) + else: + new_w = scale + new_h = int(scale * h / w) + return new_h, new_w + + +@register_transform("video_clip_random_resize_crop") +class VideoClipRandomResizeCrop(ClassyTransform): + """A video clip transform that is often useful for trainig data. + + Given a size range, randomly choose a size. Rescale the clip so that + its short edge equals to the chosen size. Then randomly crop the video + clip with the specified size. + Such training data augmentation is used in VGG net + (https://arxiv.org/abs/1409.1556). + Also see reference implementation `Kinetics.spatial_sampling` in SlowFast + codebase. + """ + + def __init__( + self, + crop_size: Union[int, List[int]], + size_range: List[int], + interpolation_mode: str = "bilinear", + ): + """The constructor method of VideoClipRandomResizeCrop class. + + Args: + crop_size: int or 2-tuple as the expected output crop_size (height, width) + size_range: the min- and max size + interpolation_mode: Default: "bilinear" + + """ + if isinstance(crop_size, tuple): + assert len(crop_size) == 2, "crop_size should be tuple (height, width)" + self.crop_size = crop_size + else: + self.crop_size = (crop_size, crop_size) + + self.interpolation_mode = interpolation_mode + self.size_range = size_range + + def __call__(self, clip): + """Callable function which applies the tranform to the input clip. + + Args: + clip (torch.Tensor): input clip tensor + + """ + # clip size: C x T x H x W + rand_size = random.randint(self.size_range[0], self.size_range[1]) + new_h, new_w = _get_rescaled_size(rand_size, clip.size()[2], clip.size()[3]) + clip = torch.nn.functional.interpolate( + clip, size=(new_h, new_w), mode=self.interpolation_mode + ) + assert ( + self.crop_size[0] <= new_h and self.crop_size[1] <= new_w + ), "crop size can not be larger than video frame size" + + i = random.randint(0, new_h - self.crop_size[0]) + j = random.randint(0, new_w - self.crop_size[1]) + clip = clip[:, :, i : i + self.crop_size[0], j : j + self.crop_size[1]] + return clip + + +@register_transform("video_clip_resize") +class VideoClipResize(ClassyTransform): + """A video clip transform that is often useful for testing data. + + Given an input size, rescale the clip so that its short edge equals to + the input size while aspect ratio is preserved. + """ + + def __init__(self, size: int, interpolation_mode: str = "bilinear"): + """The constructor method of VideoClipResize class. + + Args: + size: input size + interpolation_mode: Default: "bilinear". See valid values in + (https://pytorch.org/docs/stable/nn.functional.html#torch.nn. + functional.interpolate) + + """ + self.interpolation_mode = interpolation_mode + self.size = size + + def __call__(self, clip): + """Callable function which applies the tranform to the input clip. + + Args: + clip (torch.Tensor): input clip tensor + + """ + # clip size: C x T x H x W + if not min(clip.size()[2], clip.size()[3]) == self.size: + new_h, new_w = _get_rescaled_size(self.size, clip.size()[2], clip.size()[3]) + clip = torch.nn.functional.interpolate( + clip, size=(new_h, new_w), mode=self.interpolation_mode + ) + return clip + + +@register_transform("video_tuple_to_map_transform") +class VideoTupleToMapTransform(ClassyTransform): + """A video transform which maps video data from tuple to dict. + + It takes a sample of the form (video, audio, target) and returns a sample of + the form {"input": {"video" video, "audio": audio}, "target": target}. If + the sample is a map with these keys already present, it will pass the sample + through. + + It's particularly useful for remapping torchvision samples which are + tuples of the form (video, audio, target). + """ + + def __call__(self, sample): + """Callable function which applies the tranform to the input sample data. + + Args: + sample: input sample data that will undergo the transform + + """ + # If sample is a map and already has input / target keys, pass through + if isinstance(sample, dict): + assert "input" in sample and "target" in sample, ( + "Input to tuple to map transform must be a tuple of length 3 " + "or a dict with keys 'input' and 'target'" + ) + assert ( + "video" in sample["input"] and "audio" in sample["input"] + ), "Input data must include video / audio fields" + return sample + + # Should be a tuple (or other sequential) of length 3, transform to map + assert len(sample) == 3, "Sequential must be length 3 for conversion" + video, audio, target = sample + output_sample = {"input": {"video": video, "audio": audio}, "target": target} + return output_sample + + +@register_transform("video_default_augment") +class VideoDefaultAugmentTransform(ClassyTransform): + """This is the default video transform with data augmentation which is useful for + training. + + It sequentially prepares a torch.Tensor of video data, randomly + resizes the video clip, takes a random spatial cropping, randomly flips the + video clip horizontally, and normalizes the pixel values by mean subtraction + and standard deviation division. + + """ + + def __init__( + self, + crop_size: Union[int, List[int]] = VideoConstants.CROP_SIZE, + size_range: List[int] = VideoConstants.SIZE_RANGE, + mean: List[float] = VideoConstants.MEAN, + std: List[float] = VideoConstants.STD, + ): + """The constructor method of VideoDefaultAugmentTransform class. + + Args: + crop_size: expected output crop_size (height, width) + size_range : a 2-tuple denoting the min- and max size + mean: a 3-tuple denoting the pixel RGB mean + std: a 3-tuple denoting the pixel RGB standard deviation + + """ + + self._transform = transforms.Compose( + [ + transforms_video.ToTensorVideo(), + # TODO(zyan3): migrate VideoClipRandomResizeCrop to TorchVision + VideoClipRandomResizeCrop(crop_size, size_range), + transforms_video.RandomHorizontalFlipVideo(), + transforms_video.NormalizeVideo(mean=mean, std=std), + ] + ) + + def __call__(self, video): + """Apply the default transform with data augmentation to video. + + Args: + video: input video that will undergo the transform + + """ + return self._transform(video) + + +@register_transform("video_default_no_augment") +class VideoDefaultNoAugmentTransform(ClassyTransform): + """This is the default video transform without data augmentation which is useful + for testing. + + It sequentially prepares a torch.Tensor of video data, resize the + video clip to have the specified short edge, and normalize the pixel values + by mean subtraction and standard deviation division. + + """ + + def __init__( + self, + size: int = VideoConstants.SIZE_RANGE[0], + mean: List[float] = VideoConstants.MEAN, + std: List[float] = VideoConstants.STD, + ): + """The constructor method of VideoDefaultNoAugmentTransform class. + + Args: + size: the short edge of rescaled video clip + mean: a 3-tuple denoting the pixel RGB mean + std: a 3-tuple denoting the pixel RGB standard deviation + + """ + self._transform = transforms.Compose( + # At testing stage, central cropping is not used because we + # conduct fully convolutional-style testing + [ + transforms_video.ToTensorVideo(), + # TODO(zyan3): migrate VideoClipResize to TorchVision + VideoClipResize(size), + transforms_video.NormalizeVideo(mean=mean, std=std), + ] + ) + + def __call__(self, video): + """Apply the default transform without data augmentation to video. + + Args: + video: input video that will undergo the transform + + """ + return self._transform(video) + + +@register_transform("dummy_audio_transform") +class DummyAudioTransform(ClassyTransform): + """This is a dummy audio transform. + + It ignores actual audio data, and returns an empty tensor. It is useful when + actual audio data is raw waveform and has a varying number of waveform samples + which makes minibatch assembling impossible + + """ + + def __init__(self): + """The constructor method of DummyAudioTransform class. + """ + + pass + + def __call__(self, _audio): + """Callable function which applies the tranform to the input audio data. + + Args: + audio: input audio data that will undergo the dummy transform + + """ + return torch.zeros(0, 1, dtype=torch.float) + + +class ClassyVideoGenericTransform(object): + """This is a generic video transform which includes both video transform + and audio transform. + """ + + def __init__( + self, + config: Optional[Dict[str, List[Dict[str, Any]]]] = None, + split: str = "train", + ): + """The constructor method of ClassyVideoGenericTransform class. + + Args: + config: If provided, it is a dict where key is the data modality, and + value is a dict specifying the transform config + split: the split of the data to which the transform will be applied + """ + self.transforms = { + "video": VideoDefaultAugmentTransform() + if split == "train" + else VideoDefaultNoAugmentTransform(), + "audio": DummyAudioTransform(), + } + if config is not None: + for mode, modal_config in config.items(): + assert mode in ["video", "audio"], ( + "unknown video data modality %s" % mode + ) + self.transforms[mode] = build_transforms(modal_config) + + def __call__(self, video: Dict): + """Callable function which applies the tranform to the input video data. + + Args: + video: input video data that will undergo the transform + + """ + assert isinstance(video, dict), "video data is expected be a dict" + for mode, modal_data in video.items(): + if mode in self.transforms: + video[mode] = self.transforms[mode](modal_data) + return video + + +DEFAULT_KEY_MAP = VideoTupleToMapTransform() + + +def build_video_field_transform_default( + config: Optional[Dict[str, List[Dict[str, Any]]]], + split: str = "train", + key: str = "input", + key_map_transform: Optional[Callable] = DEFAULT_KEY_MAP, +) -> Callable: + """Returns transform that first maps sample to video keys, then + returns a transform on the specified key in dict. + + Converts tuple (list, etc) sample to dict with input / target keys. + For a dict sample, verifies that dict has input / target keys. + For all other samples throws. + + Args: + config: If provided, it is a dict where key is the data modality, and + value is a dict specifying the transform config + split: the split of the data to which the transform will be applied + key: the key in data sample of type dict whose corresponding value will + undergo the transform + key_map_transform: If provided, it is a transform which maps sample of type + tuple to sample of type dict. See default value VideoTupleToMapTransform() + as an example + + """ + transform = ApplyTransformToKey(ClassyVideoGenericTransform(config, split), key=key) + if key_map_transform is None: + return transform + + return transforms.Compose([key_map_transform, transform]) diff --git a/classy_vision/distributed/__init__.py b/classy_vision/distributed/__init__.py new file mode 100644 index 0000000000..734a1eb4e2 --- /dev/null +++ b/classy_vision/distributed/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. diff --git a/classy_vision/distributed/launch_ray.py b/classy_vision/distributed/launch_ray.py new file mode 100755 index 0000000000..6c86a4a854 --- /dev/null +++ b/classy_vision/distributed/launch_ray.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 -u +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +import logging +import os +import socket +import subprocess +import sys +import time +from argparse import REMAINDER, ArgumentParser +from contextlib import closing + +import ray + + +class NodeLaunchActor: + """Ray actor. The code here will run in each node allocated by Ray.""" + + def run(self, master_addr, master_port, node_rank, dist_world_size, args): + processes = [] + + # set PyTorch distributed related environmental variables + current_env = os.environ.copy() + current_env["MASTER_ADDR"] = master_addr + current_env["MASTER_PORT"] = str(master_port) + current_env["WORLD_SIZE"] = str(dist_world_size) + + if "OMP_NUM_THREADS" not in os.environ and args.nproc_per_node > 1: + current_env["OMP_NUM_THREADS"] = str(1) + print( + "*****************************************\n" + "Setting OMP_NUM_THREADS environment variable for each process " + "to be {} in default, to avoid your system being overloaded, " + "please further tune the variable for optimal performance in " + "your application as needed. \n" + "*****************************************".format( + current_env["OMP_NUM_THREADS"] + ) + ) + + # Set the init_method and rank of the process for distributed training. + for local_rank in range(0, args.nproc_per_node): + # each process's rank + dist_rank = args.nproc_per_node * node_rank + local_rank + current_env["RANK"] = str(dist_rank) + current_env["LOCAL_RANK"] = str(local_rank) + + # spawn the processes + with_python = not args.no_python + cmd = [] + if with_python: + cmd = [sys.executable, "-u"] + if args.module: + cmd.append("-m") + else: + if not args.use_env: + raise ValueError( + "When using the '--no_python' flag, " + "you must also set the '--use_env' flag." + ) + if args.module: + raise ValueError( + "Don't use both the '--no_python' flag" + "and the '--module' flag at the same time." + ) + + cmd.append(args.training_script) + + if not args.use_env: + cmd.append("--local_rank={}".format(local_rank)) + + cmd.extend(args.training_script_args) + process = subprocess.Popen(cmd, env=current_env) + processes.append(process) + + for process in processes: + process.wait() + if process.returncode != 0: + raise subprocess.CalledProcessError( + returncode=process.returncode, cmd=cmd + ) + + def get_node_ip(self): + return ray.services.get_node_ip_address() + + def find_free_port(self): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(("", 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] + + +def wait_for_gpus(world_size, timeout_secs=3600): + n_gpus = int(ray.cluster_resources().get("GPU", 0)) + elapsed_time = 0 + while n_gpus < world_size: + logging.warning( + f"Not enough GPUs available ({n_gpus} available," + f"need {world_size}), waiting 10 seconds" + ) + time.sleep(10) + elapsed_time += 10 + if elapsed_time > timeout_secs: + raise RuntimeError("Timeout: could not find enough GPUs") + n_gpus = int(ray.cluster_resources().get("GPU", 0)) + + +def parse_args(): + """Helper function parsing the command line options. + @retval ArgumentParser + """ + parser = ArgumentParser( + description="Classy Vision distributed training launch " + "helper utility that will spawn up multiple nodes using Ray" + ) + + # Optional arguments for the launch helper + parser.add_argument( + "--nnodes", + type=int, + default=1, + help="The number of nodes to use for distributed training", + ) + parser.add_argument( + "--nproc_per_node", + type=int, + default=1, + help="The number of processes to launch on each node, " + "for GPU training, this is recommended to be set " + "to the number of GPUs in your system so that " + "each process can be bound to a single GPU.", + ) + parser.add_argument( + "--use_env", + default=False, + action="store_true", + help="Use environment variable to pass " + "'local rank'." + "If set to True, the script will not pass " + "--local_rank as argument, and will instead set LOCAL_RANK.", + ) + parser.add_argument( + "-m", + "--module", + default=False, + action="store_true", + help="Changes each process to interpret the launch script " + "as a python module, executing with the same behavior as" + "'python -m'.", + ) + parser.add_argument( + "--no_python", + default=False, + action="store_true", + help='Do not prepend the training script with "python" - just exec ' + "it directly. Useful when the script is not a Python script.", + ) + # Ray-related arguments + group = parser.add_argument_group("Ray related arguments") + group.add_argument("--ray-address", default="auto", type=str) + + # positional + parser.add_argument( + "training_script", + type=str, + help="The full path to the single GPU training " + "program/script to be launched in parallel, " + "followed by all the arguments for the " + "training script", + ) + + # rest from the training program + parser.add_argument("training_script_args", nargs=REMAINDER) + return parser.parse_args() + + +def main(): + args = parse_args() + + ray.init(address=args.ray_address) + + # world size in terms of number of processes + dist_world_size = args.nproc_per_node * args.nnodes + + wait_for_gpus(dist_world_size) + + # Set up Ray distributed actors. + actor = ray.remote(num_cpus=1, num_gpus=args.nproc_per_node)(NodeLaunchActor) + workers = [actor.remote() for i in range(args.nnodes)] + + # Set worker 0 as the master + master_addr = ray.get(workers[0].get_node_ip.remote()) + master_port = ray.get(workers[0].find_free_port.remote()) + + unfinished = [ + worker.run.remote( + master_addr=master_addr, + master_port=master_port, + node_rank=i, + dist_world_size=dist_world_size, + args=args, + ) + for i, worker in enumerate(workers) + ] + + try: + while len(unfinished) > 0: + finished, unfinished = ray.wait(unfinished) + finished = ray.get(finished) + except Exception as inst: + logging.exception("An error occurred:") + + ray.shutdown() + + +if __name__ == "__main__": + main() diff --git a/classy_vision/generic/__init__.py b/classy_vision/generic/__init__.py new file mode 100644 index 0000000000..734a1eb4e2 --- /dev/null +++ b/classy_vision/generic/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. diff --git a/classy_vision/generic/args.py b/classy_vision/generic/args.py new file mode 100644 index 0000000000..cfec2d1c75 --- /dev/null +++ b/classy_vision/generic/args.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from classy_vision.generic.opts import check_generic_args, parse_train_arguments +from classy_vision.generic.util import load_json + + +try: + import hydra + + hydra_available = True +except ImportError: + hydra_available = False + +args = None +config = None + + +if hydra_available: + + @hydra.main(config_path="../hydra/args.yaml") + def _parse_hydra_args(cfg): + # This need to be a separate function which sets globals because hydra doesn't + # support returning from its main function + global args, config + args = cfg + check_generic_args(args) + config = args.config.to_container() + + +def parse_args(): + """Parse arguments. + + Parses the args from argparse. If hydra is installed, uses hydra based args + (experimental). + """ + if hydra_available: + global args, config + _parse_hydra_args() + return args, config + else: + args = parse_train_arguments() + config = load_json(args.config_file) + return args, config diff --git a/classy_vision/generic/debug.py b/classy_vision/generic/debug.py new file mode 100644 index 0000000000..fc9e6f72fe --- /dev/null +++ b/classy_vision/generic/debug.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +import sys + + +def debug_info(type, value, tb): + if hasattr(sys, "ps1") or not sys.stderr.isatty(): + sys.__excepthook__(type, value, tb) + else: + import traceback + import pdb + + traceback.print_exception(type, value, tb) + print + pdb.post_mortem(tb) diff --git a/classy_vision/generic/distributed_util.py b/classy_vision/generic/distributed_util.py new file mode 100644 index 0000000000..1d802c02c8 --- /dev/null +++ b/classy_vision/generic/distributed_util.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch + + +# Default to GPU 0 +_cuda_device_index: int = 0 + +# Setting _cuda_device_index to -1 internally implies that we should use CPU +_CPU_DEVICE_INDEX = -1 + + +def convert_to_distributed_tensor(tensor): + """ + For some backends, such as NCCL, communication only works if the + tensor is on the GPU. This helper function converts to the correct + device and returns the tensor + original device. + """ + orig_device = "cpu" if not tensor.is_cuda else "gpu" + if ( + torch.distributed.is_available() + and torch.distributed.get_backend() == torch.distributed.Backend.NCCL + and not tensor.is_cuda + ): + tensor = tensor.cuda() + return (tensor, orig_device) + + +def convert_to_normal_tensor(tensor, orig_device): + """ + For some backends, such as NCCL, communication only works if the + tensor is on the GPU. This converts the tensor back to original device. + """ + if tensor.is_cuda and orig_device == "cpu": + tensor = tensor.cpu() + return tensor + + +def is_distributed_training_run(): + return ( + torch.distributed.is_available() + and torch.distributed.is_initialized() + and (torch.distributed.get_world_size() > 1) + ) + + +def is_master(): + """ + Returns True if this is rank 0 of a distributed training job OR if it is + a single trainer job. Otherwise False. + """ + return get_rank() == 0 + + +def all_reduce_mean(tensor): + """ + Wrapper over torch.distributed.all_reduce for performing mean reduction + of tensor over all processes. + """ + if is_distributed_training_run(): + tensor, orig_device = convert_to_distributed_tensor(tensor) + torch.distributed.all_reduce(tensor, torch.distributed.ReduceOp.SUM) + tensor = tensor / torch.distributed.get_world_size() + tensor = convert_to_normal_tensor(tensor, orig_device) + return tensor + + +def all_reduce_sum(tensor): + """ + Wrapper over torch.distributed.all_reduce for performing sum + reduction of tensor over all processes in both distributed / + non-distributed scenarios. + """ + if is_distributed_training_run(): + tensor, orig_device = convert_to_distributed_tensor(tensor) + torch.distributed.all_reduce(tensor, torch.distributed.ReduceOp.SUM) + tensor = convert_to_normal_tensor(tensor, orig_device) + return tensor + + +def barrier(): + """ + Wrapper over torch.distributed.barrier, returns without waiting + if the distributed process group is not initialized instead of throwing error. + """ + if not torch.distributed.is_available() or not torch.distributed.is_initialized(): + return + torch.distributed.barrier() + + +def get_world_size(): + """ + Simple wrapper for correctly getting worldsize in both distributed + / non-distributed settings + """ + return ( + torch.distributed.get_world_size() + if torch.distributed.is_available() and torch.distributed.is_initialized() + else 1 + ) + + +def get_rank(): + """ + Simple wrapper for correctly getting rank in both distributed + / non-distributed settings + """ + return ( + torch.distributed.get_rank() + if torch.distributed.is_available() and torch.distributed.is_initialized() + else 0 + ) + + +def set_cuda_device_index(idx: int): + global _cuda_device_index + _cuda_device_index = idx + torch.cuda.set_device(_cuda_device_index) + + +def set_cpu_device(): + global _cuda_device_index + _cuda_device_index = _CPU_DEVICE_INDEX + + +def get_cuda_device_index() -> int: + return _cuda_device_index + + +def init_distributed_data_parallel_model(model): + global _cuda_device_index + + if _cuda_device_index == _CPU_DEVICE_INDEX: + # CPU-only model, don't specify device + return torch.nn.parallel.DistributedDataParallel(model, broadcast_buffers=False) + else: + # GPU model + return torch.nn.parallel.DistributedDataParallel( + model, + device_ids=[_cuda_device_index], + output_device=_cuda_device_index, + broadcast_buffers=False, + ) diff --git a/classy_vision/generic/opts.py b/classy_vision/generic/opts.py new file mode 100644 index 0000000000..0029088f51 --- /dev/null +++ b/classy_vision/generic/opts.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import argparse +import os + +import torch +import torchvision +from classy_vision.generic.util import is_pos_int + + +def add_generic_args(parser): + """ + Adds generic command-line arguments for convnet training / testing to parser. + """ + parser.add_argument( + "--config_file", type=str, help="path to config file for model", required=True + ) + parser.add_argument( + "--device", + default=None, + type=str, + help="device to use: either 'cpu' or 'gpu'. If unspecified, will use GPU when available and CPU otherwise.", + ) + parser.add_argument( + "--num_workers", + default=4, + type=int, + help="number of dataloading workers (default = 4)", + ) + parser.add_argument( + "--checkpoint_folder", + default="", + type=str, + help="""folder to use for checkpoints: + epochal checkpoints are stored as model_.torch, + latest epoch checkpoint is at checkpoint.torch""", + ) + parser.add_argument( + "--pretrained_checkpoint_folder", + default="", + type=str, + help="""folder to use for pre-trained checkpoints: + epochal checkpoints are stored as model_.torch, + latest epoch checkpoint is at checkpoint.torch, + checkpoint is used for fine-tuning task, and it will + not resume training from the checkpoint""", + ) + parser.add_argument( + "--checkpoint_period", + default=1, + type=int, + help="""Checkpoint every x phases (default 1)""", + ) + parser.add_argument( + "--show_progress", + default=False, + action="store_true", + help="shows progress bar during training / testing", + ) + parser.add_argument( + "--skip_tensorboard", + default=False, + action="store_true", + help="do not perform tensorboard visualization", + ) + parser.add_argument( + "--visdom_server", + default="", + type=str, + help="visdom server to use (default None)", + ) + parser.add_argument( + "--visdom_port", + default=8097, + type=int, + help="port of visdom server (default = 8097)", + ) + parser.add_argument( + "--profiler", + default=False, + action="store_true", + help="specify this argument to profile training code", + ) + parser.add_argument( + "--debug", + default=False, + action="store_true", + help="specify this argument for debugging mode", + ) + parser.add_argument( + "--ignore_checkpoint_config", + default=False, + action="store_true", + help="""specify this argument to ignore + the compatibility of the config (or lack of config) attached + to the checkpoint; this will allow mismatches between + the training specified in the config and the + actual training of the model""", + ) + parser.add_argument( + "--log_freq", + default=5, + type=int, + help="Logging frequency for LossLrMeterLoggingHook (default 5)", + ) + parser.add_argument( + "--image_backend", + default="PIL", + type=str, + help="torchvision image decoder backend (PIL or accimage). Default PIL", + ) + parser.add_argument( + "--video_backend", + default="pyav", + type=str, + help="torchvision video decoder backend (pyav or video_reader). Default pyav", + ) + parser.add_argument( + "--distributed_backend", + default="none", + type=str, + help="""Distributed backend: either 'none' (for non-distributed runs) + or 'ddp' (for distributed runs). Default none.""", + ) + + return parser + + +def check_generic_args(args): + """ + Perform assertions on generic command-line arguments. + """ + + # check types and values: + assert is_pos_int(args.num_workers), "incorrect number of workers" + assert is_pos_int(args.visdom_port), "incorrect visdom port" + assert ( + args.device is None or args.device == "cpu" or args.device == "gpu" + ), "unknown device" + + # check that CUDA is available: + if args.device == "gpu": + assert torch.cuda.is_available(), "CUDA required to train on GPUs" + + # create checkpoint folder if it does not exist: + if args.checkpoint_folder != "" and not os.path.exists(args.checkpoint_folder): + os.makedirs(args.checkpoint_folder, exist_ok=True) + assert os.path.exists(args.checkpoint_folder), ( + "could not create folder %s" % args.checkpoint_folder + ) + + # when in debugging mode, enter debugger upon error: + if args.debug: + import sys + from classy_vision.generic.debug import debug_info + + sys.excepthook = debug_info + + # check visdom server name: + if args.visdom_server != "": + if args.visdom_server.startswith("https://"): + print("WARNING: Visdom does not work over HTTPS.") + args.visdom_server = args.visdom_server[8:] + if not args.visdom_server.startswith("http://"): + args.visdom_server = "http://%s" % args.visdom_server + + # return input arguments: + return args + + +def get_parser(): + """ + Return a standard command-line parser. + """ + parser = argparse.ArgumentParser( + description="""Start a Classy Vision training job. + + This can be used for training on your local machine, using CPU or GPU, and + for distributed training. This script also supports Tensorboard, Visdom and + checkpointing.""" + ) + + parser = add_generic_args(parser) + return parser + + +def parse_train_arguments(parser=None): + """ + Assert and parse the command-line arguments of a given (or default) parser. + """ + + # set input arguments: + if parser is None: + parser = get_parser() + + # parse input arguments: + args = parser.parse_args() + + # assertions: + args = check_generic_args(args) + return args diff --git a/classy_vision/generic/pdb.py b/classy_vision/generic/pdb.py new file mode 100644 index 0000000000..f37f63ff41 --- /dev/null +++ b/classy_vision/generic/pdb.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import multiprocessing +import os +import pdb +import sys + + +__all__ = ["set_trace"] + + +_stdin = [None] +_stdin_lock = multiprocessing.Lock() +try: + _stdin_fd = sys.stdin.fileno() +except Exception: + _stdin_fd = None + + +class MultiprocessingPdb(pdb.Pdb): + """A Pdb wrapper that works in a multiprocessing environment. + + Usage: `from fairseq import pdb; pdb.set_trace()` + """ + + def __init__(self): + pdb.Pdb.__init__(self, nosigint=True) + + def _cmdloop(self): + stdin_bak = sys.stdin + with _stdin_lock: + try: + if _stdin_fd is not None: + if not _stdin[0]: + _stdin[0] = os.fdopen(_stdin_fd) + sys.stdin = _stdin[0] + self.cmdloop() + finally: + sys.stdin = stdin_bak + + +def set_trace(): + pdb = MultiprocessingPdb() + pdb.set_trace(sys._getframe().f_back) diff --git a/classy_vision/generic/perf_stats.py b/classy_vision/generic/perf_stats.py new file mode 100644 index 0000000000..8f030b84d1 --- /dev/null +++ b/classy_vision/generic/perf_stats.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from collections import defaultdict, deque +from time import perf_counter +from typing import List, Mapping, Optional, Tuple + +import torch +from torch.cuda import Event as CudaEvent + + +class PerfTimer: + """ + Very simple timing wrapper, with context manager wrapping. + Typical usage: + + with PerfTimer('forward_pass', perf_stats): + model.forward(data) + # ... + with PerfTimer('backward_pass', perf_stats): + model.backward(loss) + # ... + print(perf_stats.report_str()) + + Note that timer stats accumulate by name, so you can as if resume them + by re-using the name. + + You can also use it without context manager, i.e. via start() / stop() directly. + + If supplied PerfStats is constructed with use_cuda_events=True (which is default), + then Cuda events will be added to correctly track time of async execution + of Cuda kernels: + + with PerfTimer('foobar', perf_stats): + some_cpu_work() + schedule_some_cuda_work() + + In example above, the "Host" column will capture elapsed time from the perspective + of the Python process, and "CudaEvent" column will capture elapsed time between + scheduling of Cuda work (within the PerfTimer scope) and completion of this work, + some of which might happen outside the PerfTimer scope. + + If perf_stats is None, using PerfTimer does nothing. + """ + + def __init__(self, timer_name: str, perf_stats: Optional["PerfStats"]): + self.skip: bool = False + if perf_stats is None: + self.skip = True + return + + self.name: str = timer_name + self.elapsed: float = 0.0 + + self._last_interval: float = 0.0 + self._perf_stats: PerfStats = perf_stats + self._is_running: bool = False + + if perf_stats.use_cuda_events(): + self._cuda_event_intervals: List[Tuple[CudaEvent, CudaEvent]] = [] + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exception, traceback): + self.stop() + if exc_type is None: + # Only record timer value if with-context finished without error + self.record() + + return False # re-raise if there was exception + + def start(self): + if self.skip or self._is_running: + return + + self._last_interval = 0.0 + self._is_running = True + self._start_time: float = perf_counter() + if self._perf_stats.use_cuda_events(): + self._start_event = torch.cuda.Event(enable_timing=True) + self._start_event.record() + + def stop(self): + if self.skip or not self._is_running: + return + + self._last_interval = perf_counter() - self._start_time + self.elapsed += self._last_interval + + if self._perf_stats.use_cuda_events(): + # Two cuda events will measure real GPU time within PerfTimer scope: + end_event = torch.cuda.Event(enable_timing=True) + end_event.record() + self._cuda_event_intervals.append((self._start_event, end_event)) + + self._is_running = False + + def record(self): + if self.skip: + return + assert not self._is_running + self._perf_stats.update_with_timer(self) + + +class PerfMetric: + """ + Encapsulates numerical tracking of a single metric, with a `.update(value)` API. + Under-the-hood this can additionally keep track of sums, (exp.) moving averages, + sum of squares (e.g. for stdev), filtered values, etc. + """ + + # Coefficient for exponential moving average (EMA). + # Value of 0.1 means last 8 values account for ~50% of weight. + EMA_FACTOR = 0.1 + + def __init__(self): + self.last_value: Optional[float] = None + self.smoothed_value: Optional[float] = None + + self.sum_values: float = 0.0 + self.num_updates: int = 0 + + def update(self, value: float): + self.last_value = value + if self.smoothed_value is None: + self.smoothed_value = value + else: + # TODO (T47970762): correct for initialization bias + self.smoothed_value = ( + PerfMetric.EMA_FACTOR * value + + (1.0 - PerfMetric.EMA_FACTOR) * self.smoothed_value + ) + + self.sum_values += value + self.num_updates += 1 + + def get_avg(self): + if self.num_updates == 0: + return 0.0 + else: + return self.sum_values / self.num_updates + + +class PerfStats: + """ + Accumulate stats (from timers) over many iterations + """ + + MAX_PENDING_TIMERS = 1000 + + def __init__(self, use_cuda_events=True): + self._host_stats: Mapping[str, PerfMetric] = defaultdict(PerfMetric) + self._cuda_stats: Mapping[str, PerfMetric] = defaultdict(PerfMetric) + + if use_cuda_events: + if torch.cuda.is_available(): + self._cuda_pending_timers = deque(maxlen=PerfStats.MAX_PENDING_TIMERS) + else: + logging.warning("CUDA unavailable: CUDA events are not logged.") + self._cuda_pending_timers = None + else: + self._cuda_pending_timers = None + + def update_with_timer(self, timer: PerfTimer): + self._host_stats[timer.name].update(timer._last_interval) + + if self.use_cuda_events(): + if len(self._cuda_pending_timers) >= self._cuda_pending_timers.maxlen: + logging.error( + "Too many pending timers. CudaEvent-based stats will be inaccurate!" + ) + else: + self._cuda_pending_timers.append(timer) + self._process_cuda_events() + + def _process_cuda_events(self): + """ + Service pending timers. Dequeue timers and aggregate Cuda time intervals, + until the first "pending" timer (i.e. dependent on a not-yet-ready cuda event). + """ + while len(self._cuda_pending_timers) > 0: + timer = self._cuda_pending_timers[0] + elapsed_cuda = 0.0 + + for ev_start, ev_end in timer._cuda_event_intervals: + if not ev_start.query() or not ev_end.query(): + # Cuda events associated with this timer aren't ready yet, + # stop servicing the queue. + return + # Use seconds (instead of ms) for consistency with "host" timers + elapsed_cuda += ev_start.elapsed_time(ev_end) / 1000.0 + + # All time intervals for this timer are now accounted for. + # Aggregate stats and pop from pending queue. + self._cuda_stats[timer.name].update(elapsed_cuda) + self._cuda_pending_timers.popleft() + + def report_str(self): + """ + Fancy column-aligned human-readable report. + If using Cuda events, calling this invokes cuda.synchronize(), which is needed + to capture pending Cuda work in the report. + """ + if self.use_cuda_events(): + torch.cuda.synchronize() + self._process_cuda_events() + + name_width = max(len(k) for k in self._host_stats.keys()) + + header = ("{:>" + str(name_width + 4) + "s} {:>7s} {:>7s}").format( + "Timer", "Host", "CudaEvent" + ) + row_fmt = "{:>" + str(name_width + 4) + "s}: {:>7.2f} ms {:>7.2f} ms" + + rows = [] + rows.append(header) + for name, metric in self._host_stats.items(): + rows.append( + row_fmt.format( + name, + metric.get_avg() * 1000.0, + self._cuda_stats[name].get_avg() * 1000.0, + ) + ) + return "\n".join(rows) + + def use_cuda_events(self): + return torch.cuda.is_available() and self._cuda_pending_timers is not None + + def __str__(self): + return str((self._host_stats, self._cuda_stats)) diff --git a/classy_vision/generic/profiler.py b/classy_vision/generic/profiler.py new file mode 100644 index 0000000000..052d2b3d3e --- /dev/null +++ b/classy_vision/generic/profiler.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import collections.abc as abc +import logging +import operator + +import torch +import torch.nn as nn +from classy_vision.generic.util import get_model_dummy_input, is_leaf, is_on_gpu +from torch.cuda import cudart + + +def profile( + model, + batchsize_per_replica=32, + input_shape=(3, 224, 224), + use_nvprof=False, + input_key=None, +): + """ + Performs CPU or GPU profiling of the specified model on the specified input. + """ + + # assertions: + if use_nvprof: + raise NotImplementedError + # FIXME (mannatsingh): in case of use_nvprof, exit() is called at the end + # and we do not return a profile. + assert is_on_gpu(model), "can only nvprof model that lives on GPU" + logging.info("CUDA profiling: Make sure you are running under nvprof!") + + # input for model: + input = get_model_dummy_input( + model, + input_shape, + input_key, + batchsize=batchsize_per_replica, + non_blocking=False, + ) + # perform profiling: + with torch.no_grad(): + model(input) # warm up CUDA memory allocator and profiler + if use_nvprof: # nvprof profiling (TODO: Can we infer this?) + cudart().cudaProfilerStart() + model(input) + cudart().cudaProfilerStop() + exit() # exit gracefully + else: # regular profiling + with torch.autograd.profiler.profile(use_cuda=True) as profiler: + model(input) + return profiler + + +def _layer_flops(layer, x): + """ + Computes the number of FLOPs required for a single layer. + """ + + # get layer type: + typestr = layer.__repr__() + layer_type = typestr[: typestr.find("(")].strip() + batchsize_per_replica = x.size()[0] + # 2D convolution: + if layer_type in ["Conv2d"]: + out_h = int( + (x.size()[2] + 2 * layer.padding[0] - layer.kernel_size[0]) + / layer.stride[0] + + 1 + ) + out_w = int( + (x.size()[3] + 2 * layer.padding[1] - layer.kernel_size[1]) + / layer.stride[1] + + 1 + ) + return ( + batchsize_per_replica + * layer.in_channels + * layer.out_channels + * layer.kernel_size[0] + * layer.kernel_size[1] + * out_h + * out_w + / layer.groups + ) + + # learned group convolution: + elif layer_type in ["LearnedGroupConv"]: + conv = layer.conv + out_h = int( + (x.size()[2] + 2 * conv.padding[0] - conv.kernel_size[0]) / conv.stride[0] + + 1 + ) + out_w = int( + (x.size()[3] + 2 * conv.padding[1] - conv.kernel_size[1]) / conv.stride[1] + + 1 + ) + count1 = _layer_flops(layer.relu, x) + _layer_flops(layer.norm, x) + count2 = ( + batchsize_per_replica + * conv.in_channels + * conv.out_channels + * conv.kernel_size[0] + * conv.kernel_size[1] + * out_h + * out_w + / layer.condense_factor + ) + return count1 + count2 + + # non-linearities: + elif layer_type in ["ReLU", "Tanh", "Sigmoid", "Softmax"]: + return x.numel() + + # 2D pooling layers: + elif layer_type in ["AvgPool2d", "MaxPool2d"]: + in_h = x.size()[2] + in_w = x.size()[3] + if isinstance(layer.kernel_size, int): + layer.kernel_size = (layer.kernel_size, layer.kernel_size) + kernel_ops = layer.kernel_size[0] * layer.kernel_size[1] + out_h = 1 + int( + (in_h + 2 * layer.padding - layer.kernel_size[0]) / layer.stride + ) + out_w = 1 + int( + (in_w + 2 * layer.padding - layer.kernel_size[1]) / layer.stride + ) + return x.size()[0] * x.size()[1] * out_w * out_h * kernel_ops + + # adaptive avg pool2d + # This is approximate and works only for downsampling without padding + # based on aten/src/ATen/native/AdaptiveAveragePooling.cpp + elif layer_type in ["AdaptiveAvgPool2d"]: + in_h = x.size()[2] + in_w = x.size()[3] + out_h = layer.output_size[0] + out_w = layer.output_size[1] + if out_h > in_h or out_w > in_w: + raise NotImplementedError() + batchsize_per_replica = x.size()[0] + num_channels = x.size()[1] + kh = in_h - out_h + 1 + kw = in_w - out_w + 1 + kernel_ops = kh * kw + return batchsize_per_replica * num_channels * out_h * out_w * kernel_ops + + # linear layer: + elif layer_type in ["Linear"]: + weight_ops = layer.weight.numel() + bias_ops = layer.bias.numel() + return x.size()[0] * (weight_ops + bias_ops) + + # 2D/3D batch normalization: + elif layer_type in ["BatchNorm2d", "BatchNorm3d"]: + return 2 * x.numel() + + # 3D convolution + elif layer_type in ["Conv3d"]: + out_t = int( + (x.size()[2] + 2 * layer.padding[0] - layer.kernel_size[0]) + // layer.stride[0] + + 1 + ) + out_h = int( + (x.size()[3] + 2 * layer.padding[1] - layer.kernel_size[1]) + // layer.stride[1] + + 1 + ) + out_w = int( + (x.size()[4] + 2 * layer.padding[2] - layer.kernel_size[2]) + // layer.stride[2] + + 1 + ) + return ( + batchsize_per_replica + * layer.in_channels + * layer.out_channels + * layer.kernel_size[0] + * layer.kernel_size[1] + * layer.kernel_size[2] + * out_t + * out_h + * out_w + / layer.groups + ) + + # 3D pooling layers + elif layer_type in ["AvgPool3d", "MaxPool3d"]: + in_t = x.size()[2] + in_h = x.size()[3] + in_w = x.size()[4] + if isinstance(layer.kernel_size, int): + layer.kernel_size = ( + layer.kernel_size, + layer.kernel_size, + layer.kernel_size, + ) + if isinstance(layer.padding, int): + layer.padding = (layer.padding, layer.padding, layer.padding) + if isinstance(layer.stride, int): + layer.stride = (layer.stride, layer.stride, layer.stride) + kernel_ops = layer.kernel_size[0] * layer.kernel_size[1] * layer.kernel_size[2] + out_t = 1 + int( + (in_t + 2 * layer.padding[0] - layer.kernel_size[0]) / layer.stride[0] + ) + out_h = 1 + int( + (in_h + 2 * layer.padding[1] - layer.kernel_size[1]) / layer.stride[1] + ) + out_w = 1 + int( + (in_w + 2 * layer.padding[2] - layer.kernel_size[2]) / layer.stride[2] + ) + return batchsize_per_replica * x.size()[1] * out_t * out_h * out_w * kernel_ops + + # dropout layer + elif layer_type in ["Dropout"]: + # At test time, we do not drop values but scale the feature map by the + # dropout ratio + flops = 1 + for dim_size in x.size(): + flops *= dim_size + return flops + + # not implemented: + raise NotImplementedError("FLOPs not implemented for %s layer" % layer_type) + + +def summarize_profiler_info(prof): + """ + Summarizes the statistics in the specified profiler. + """ + + # create sorted list of times per operator: + op2time = {} + for item in prof.key_averages(): + op2time[item.key] = ( + item.cpu_time_total / 1000.0, + item.cuda_time_total / 1000.0, + ) # to milliseconds + op2time = sorted(op2time.items(), key=operator.itemgetter(1), reverse=True) + + # created string containing information: + str = "\n%s\tCPU Time\tCUDA Time\n" % ("Key".rjust(20)) + for (key, value) in op2time: + str += "%s\t%2.5f ms\t%2.5f ms\n" % (key.rjust(20), value[0], value[1]) + return str + + +def _flops_module(module, flops_list): + """ + Convert module into FLOP-counting module. + """ + ty = type(module) + typestring = module.__repr__() + + class FLOPsModule(ty): + orig_type = ty + + def _original_forward(self, *args, **kwargs): + return ty.forward(self, *args, **kwargs) + + def forward(self, *args, **kwargs): + flops_list.append(_layer_flops(self, args[0])) + return self._original_forward(*args, **kwargs) + + def __repr__(self): + return typestring + + return FLOPsModule + + +def modify_forward(model, flops_list): + """ + Modify forward pass to measure FLOPs: + """ + if is_leaf(model): + model.__class__ = _flops_module(model, flops_list) + for child in model.children(): + modify_forward(child, flops_list) + + return model + + +def restore_forward(model): + """ + Restore original forward in model: + """ + if is_leaf(model): + model.__class__ = model.orig_type + for child in model.children(): + restore_forward(child) + + return model + + +def compute_flops(model, input_shape=(3, 244, 244), input_key=None): + """ + Compute the number of FLOPs needed for a forward pass. + """ + + # assertions, input, and upvalue in which we will perform the count: + assert isinstance(model, nn.Module) + if not isinstance(input_shape, abc.Sequence): + return None + input = get_model_dummy_input(model, input_shape, input_key) + flops_list = [] + + # measure FLOPs: + modify_forward(model, flops_list) + try: + model.forward(input) + except NotImplementedError as err: + raise err + finally: + restore_forward(model) + return sum(flops_list) + + +def count_params(model): + """ + Count the number of parameters in a model. + """ + assert isinstance(model, nn.Module) + count = 0 + for child in model.children(): + if is_leaf(child): + if hasattr(child, "_mask"): # for masked modules (like LGC) + count += child._mask.long().sum().item() + # FIXME: BatchNorm parameters in LGC are not counted. + else: # for regular modules + for p in child.parameters(): + count += p.nelement() + else: + count += count_params(child) + return count diff --git a/classy_vision/generic/registry_utils.py b/classy_vision/generic/registry_utils.py new file mode 100644 index 0000000000..9db51158f3 --- /dev/null +++ b/classy_vision/generic/registry_utils.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import importlib +import logging +import os +import sys +from pathlib import Path + + +def import_all_modules(root, base_module): + for file in os.listdir(root): + if file.endswith((".py", ".pyc")) and not file.startswith("_"): + module = file[: file.find(".py")] + if module not in sys.modules: + module_name = ".".join([base_module, module]) + importlib.import_module(module_name) + + +def import_all_packages_from_directory(root): + """Automatically imports all packages under the root directory. + + For instance, if your directories look like: + root / foo / __init__.py + root / foo / abc.py + root / bar.py + root / baz / xyz.py + + This function will import the package foo, but not bar or baz.""" + + for file in os.listdir(root): + # Try to import each file in the directory. Our previous implementation + # would look for directories here and see if there's a __init__.py + # under that directory, but that turns out to be unreliable while + # running on AWS: EFS filesystems cache metadata bits so the directory + # and existence checks fail even when the import succeeds. We should + # find a better workaround eventually, but this will do for now. + try: + file = Path(file) + module_name = file.name + # Dots have special meaning in Python packages -- it's a relative + # import or a subpackage. Skip these. + if "." not in module_name and module_name not in sys.modules: + logging.debug(f"Automatically importing {module_name}") + importlib.import_module(module_name) + except ModuleNotFoundError: + pass diff --git a/classy_vision/generic/util.py b/classy_vision/generic/util.py new file mode 100644 index 0000000000..8cda2aa485 --- /dev/null +++ b/classy_vision/generic/util.py @@ -0,0 +1,749 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import argparse +import collections +import contextlib +import json +import logging +import math +import os +import sys +import tempfile +import traceback + +import numpy as np +import torch +import torch.nn as nn +from torch._six import container_abcs + + +# constants: +CHECKPOINT_FILE = "checkpoint.torch" + + +def is_pos_int(number): + """ + Returns True if a number is a positive integer. + """ + return type(number) == int and number >= 0 + + +def is_pos_float(number): + """ + Returns True if a number is a positive float. + """ + return type(number) == float and number >= 0.0 + + +def is_pos_int_list(l): + """ + Returns True if a list contains positive integers + """ + return type(l) == list and all(is_pos_int(n) for n in l) + + +def is_long_tensor(tensor): + """ + Returns True if a tensor is a long tensor. + """ + if torch.is_tensor(tensor): + return tensor.type().endswith("LongTensor") + else: + return False + + +def is_float_tensor(tensor): + """ + Returns True if a tensor is a float tensor. + """ + if torch.is_tensor(tensor): + return tensor.type().endswith("FloatTensor") + else: + return False + + +def is_double_tensor(tensor): + """ + Returns True if a tensor is a double tensor. + """ + if torch.is_tensor(tensor): + return tensor.type().endswith("DoubleTensor") + else: + return False + + +def is_leaf(module): + """ + Returns True if module is leaf in the graph. + """ + assert isinstance(module, nn.Module), "module should be nn.Module" + return len([c for c in module.children()]) == 0 or hasattr(module, "_mask") + + +def is_on_gpu(model): + """ + Returns True if all parameters of a model live on the GPU. + """ + assert isinstance(model, torch.nn.Module) + on_gpu = True + has_params = False + for param in model.parameters(): + has_params = True + if not param.data.is_cuda: + on_gpu = False + return has_params and on_gpu + + +def is_not_none(sample): + """ + Returns True if sample is not None and constituents are not none. + """ + if sample is None: + return False + + if isinstance(sample, (list, tuple)): + if any(s is None for s in sample): + return False + + if isinstance(sample, dict): + if any(s is None for s in sample.values()): + return False + return True + + +def copy_model_to_gpu(model, loss=None): + """ + Copies a model and (optional) loss to GPU and enables cudnn benchmarking. + For multiple gpus training, the model in DistributedDataParallel for + distributed training. + """ + if not torch.backends.cudnn.deterministic: + torch.backends.cudnn.benchmark = True + model = model.cuda() + + if loss is not None: + loss = loss.cuda() + return model, loss + else: + return model + + +def copy_upvalue(value, upvalue): + """ + Iteratively copies a particular value into an upvalue dict. + """ + assert type(value) == type(upvalue), "type of value and upvalue must match" + if type(value) == dict: + upvalue.clear() + for key, val in value.items(): + upvalue[key] = val + elif type(value) == list: + del upvalue[:] + for _, val in value: + upvalue.append(val) + else: + raise BaseException("unsupported upvalue type") + + +def recursive_copy_to_gpu(value, non_blocking=True, max_depth=3, curr_depth=0): + """ + Recursively searches lists, tuples, dicts and copies to GPU if possible. + Note: These are all copies, so if there are two objects that reference + the same object, then after this call, there will be two different objects + referenced on the GPU. + """ + if curr_depth >= max_depth: + raise ValueError("Depth of value object is too deep") + + try: + return value.cuda(non_blocking=non_blocking) + except AttributeError: + if isinstance(value, container_abcs.Sequence): + gpu_val = [] + for val in value: + gpu_val.append( + recursive_copy_to_gpu( + val, + non_blocking=non_blocking, + max_depth=max_depth, + curr_depth=curr_depth + 1, + ) + ) + + return gpu_val if isinstance(value, list) else tuple(gpu_val) + elif isinstance(value, container_abcs.Mapping): + gpu_val = {} + for key, val in value.items(): + gpu_val[key] = recursive_copy_to_gpu( + val, + non_blocking=non_blocking, + max_depth=max_depth, + curr_depth=curr_depth + 1, + ) + + return gpu_val + + raise AttributeError("Value must have .cuda attr or be a Seq / Map iterable") + + +@contextlib.contextmanager +def numpy_seed(seed, *addl_seeds): + """Context manager which seeds the NumPy PRNG with the specified seed and + restores the state afterward""" + if seed is None: + yield + return + if len(addl_seeds) > 0: + seed = int(hash((seed, *addl_seeds)) % 1e6) + state = np.random.get_state() + np.random.seed(seed) + try: + yield + finally: + np.random.set_state(state) + + +def accuracy(output, target, topk=(1,)): + """ + Computes the top-k accuracy in a multi-class problem from an NxK output + matrix and a corresponding Nx1 target matrix with values in 0, ..., K-1. + """ + + # assertions: + assert torch.is_tensor(output) + assert torch.is_tensor(target) + assert output.size(0) == target.size(0) + if type(topk) == int: + topk = (topk,) + assert type(topk) == tuple or type(topk) == list + assert all(is_pos_int(k) for k in topk) + maxk = max(topk) + assert maxk < output.size(1) + + # determine whether predictions are correct: + _, pred = output.topk(maxk, 1, True, True) + pred = pred.t() + correct = pred.eq(target.view(1, -1).expand_as(pred)) + + # compute accuracies: + res = [] + for k in topk: + correct_k = correct[:k].view(-1).float().sum(0, keepdim=True) + res.append(correct_k.mul_(100.0 / target.size(0)).item()) + return res + + +def binary_accuracy(output, target, threshold=0.0): + """ + Computes the accuracy based on a real-valued output tensor and a corresponding + target matrix of the same size with valuesin [0, 1]. + + An optional threshold for positive classification can be specified (default = 0). + """ + + # assertions: + assert torch.is_tensor(output) + assert torch.is_tensor(target) + assert output.size() == target.size() + + # compute accuracy: + correct = output.ge(threshold).type_as(target).eq_(target).long() + return correct.sum().item() * 100.0 / float(target.nelement()) + + +def create_class_histograms(pred_prob, target, num_bins): + """ + Creates two histograms to contain total positive or true samples + for a given class at a given score value and total samples at a + given predicted probability. Then for a given threshold we can compute + precision = true_samples_above_threshold / + total_samples_above_threshold and recall = + true_samples_above_threshold / true_samples. Output is expected to + be prediction probabilities. + + The pred_prob vector is num_classes x batchsize_per_replica, the target vector is + num_classes x 1, should contain integers corresponding to each class. + Output is two tensors of same size: num_bins x num_classes + where the bins are equispaced from 0 to 1 across the score. + The class_hist[:, c] contains true example histogram for class c + prediction probabilities while total_hist[:, c] contains histogram + for all samples for class c prediction probabilities. + """ + + # assertions: + assert torch.is_tensor(pred_prob) + assert torch.is_tensor(target) + assert pred_prob.size()[0] == target.size()[0], "%s pred_prob, %s target" % ( + str(pred_prob.size()), + str(target.size()), + ) + assert ( + pred_prob.lt(0.0).sum() == 0 and pred_prob.gt(1.0).sum() == 0 + ), "Prediction probability must be between 0 and 1" + + num_classes = pred_prob.size()[1] + class_hist = torch.zeros([num_bins, num_classes], dtype=torch.int64) + total_hist = torch.zeros([num_bins, num_classes], dtype=torch.int64) + for c in range(num_classes): + total_hist[:, c] = torch.histc( + pred_prob[:, c], bins=num_bins, min=0.0, max=1.0 + ).long() + class_hist[:, c] = torch.histc( + pred_prob[:, c][(target == c).nonzero().squeeze(1)], + bins=num_bins, + min=0.0, + max=1.0, + ).long() + + return class_hist, total_hist + + +def _find_last_larger_than(target, val_array): + """ + Takes an array and finds the last value larger than the target value. + Returns the index of that value, returns -1 if none exists in array. + """ + ind = -1 + for j in range(len(val_array), 0, -1): + if val_array[j - 1] > target: + ind = j - 1 + break + return ind + + +def calc_ap(prec, recall): + """ + Computes average precision from precision recall curves. Curves + are expected to be same size and 1D. + """ + assert prec.size() == recall.size(), "Precision and recall curves must be same size" + ap = 0.0 + if len(prec) == 0: + return ap + prev_r = 0.0 + prev_p = prec[0] + for p, r in zip(prec, recall): + ap += (r - prev_r) * (p + prev_p) / 2.0 + prev_r = r + prev_p = p + + return ap + + +def compute_pr_curves(class_hist, total_hist): + """ + Computes precision recall curves from the true sample / total + sample histogram tensors. The histogram tensors are num_bins x num_classes + and each column represents a histogram over + prediction_probabilities. + + The two tensors should have the same dimensions. + The two tensors should have nonnegative integer values. + + Returns map of precision / recall values from highest precision to lowest + and the calculated AUPRC (i.e. the average precision). + """ + assert torch.is_tensor(class_hist) and torch.is_tensor( + total_hist + ), "Both arguments must be tensors" + assert ( + class_hist.dtype == torch.int64 and total_hist.dtype == torch.int64 + ), "Both arguments must contain int64 values" + assert ( + len(class_hist.size()) == 2 and len(total_hist.size()) == 2 + ), "Both arguments must have 2 dimensions, (score_bin, class)" + assert ( + class_hist.size() == total_hist.size() + ), """ + For compute_pr_curve, arguments must be of same size. + class_hist.size(): %s + total_hist.size(): %s + """ % ( + str(class_hist.size()), + str(total_hist.size()), + ) + assert ( + class_hist > total_hist + ).sum() == 0, ( + "Invalid. Class histogram must be less than or equal to total histogram" + ) + + num_bins = class_hist.size()[0] + # Cumsum from highest bucket to lowest + cum_class_hist = torch.cumsum(torch.flip(class_hist, dims=(0,)), dim=0).double() + cum_total_hist = torch.cumsum(torch.flip(total_hist, dims=(0,)), dim=0).double() + class_totals = cum_class_hist[-1, :] + + prec_t = cum_class_hist / cum_total_hist + recall_t = cum_class_hist / class_totals + + prec = torch.unbind(prec_t, dim=1) + recall = torch.unbind(recall_t, dim=1) + assert len(prec) == len( + recall + ), "The number of precision curves does not match the number of recall curves" + + final_prec = [] + final_recall = [] + final_ap = [] + for c, prec_curve in enumerate(prec): + recall_curve = recall[c] + assert ( + recall_curve.size()[0] == num_bins and prec_curve.size()[0] == num_bins + ), "Precision and recall curves do not have the correct number of entries" + + # Check if any samples from class were seen + if class_totals[c] == 0: + continue + + # Remove duplicate entries + prev_r = torch.tensor(-1.0).double() + prev_p = torch.tensor(1.1).double() + new_recall_curve = torch.tensor([], dtype=torch.double) + new_prec_curve = torch.tensor([], dtype=torch.double) + for idx, r in enumerate(recall_curve): + p = prec_curve[idx] + # Remove points on PR curve that are invalid + if r.item() <= 0: + continue + + # Remove duplicates (due to empty buckets): + if r.item() == prev_r.item() and p.item() == prev_p.item(): + continue + + # Add points to curve + new_recall_curve = torch.cat((new_recall_curve, r.unsqueeze(0)), dim=0) + new_prec_curve = torch.cat((new_prec_curve, p.unsqueeze(0)), dim=0) + prev_r = r + prev_p = p + + ap = calc_ap(new_prec_curve, new_recall_curve) + final_prec.append(new_prec_curve) + final_recall.append(new_recall_curve) + final_ap.append(ap) + + return {"prec": final_prec, "recall": final_recall, "ap": final_ap} + + +def get_checkpoint_dict(task, input_args): + assert isinstance( + input_args, dict + ), f"Unexpected input_args of type: {type(input_args)}" + return {"input_args": input_args, "classy_state_dict": task.get_classy_state()} + + +# function that tries to load a checkpoint: +def load_checkpoint(checkpoint_folder, device=None, checkpoint_file=CHECKPOINT_FILE): + """ + Loads a state variable from the specified checkpoint folder. + """ + if not checkpoint_folder: + return None + + if device is None: + device = "gpu" if torch.cuda.is_available() else "cpu" + + if not os.path.exists(checkpoint_folder): + logging.warning("Checkpoint folder '%s' not found" % checkpoint_folder) + return None + logging.info("Attempting to load checkpoint from '%s'" % checkpoint_folder) + + # read what the latest model file is: + filename = os.path.join(checkpoint_folder, checkpoint_file) + if not os.path.exists(filename): + logging.warning("Checkpoint file %s not found." % filename) + return None + + # load and return the checkpoint: + if device == "cpu": + checkpoint = torch.load(filename, map_location=torch.device("cpu")) + else: + assert device == "gpu" + # Load model on current device and not on saved device for model. + checkpoint = torch.load( + filename, + map_location=torch.device("cuda:{0}".format(torch.cuda.current_device())), + ) + logging.info(f"Loaded checkpoint from {filename}") + return checkpoint + + +def update_classy_model(model, model_state_dict, reset_heads): + """ + Updates the model with the provided model state dictionary. + + Args: + model: ClassyVisionModel instance to update + model_state_dict: State dict, should be the output of a call to + ClassyVisionModel.get_classy_state(). + reset_heads: if False, uses the heads' state from model_state_dict. + """ + try: + if reset_heads: + current_model_state_dict = model.get_classy_state() + # replace the checkpointed head states with source head states + model_state_dict["model"]["heads"] = current_model_state_dict["model"][ + "heads" + ] + model.set_classy_state(model_state_dict) + logging.info("Model state load successful") + return True + except Exception: + logging.exception("Could not load the model state") + return False + + +def update_classy_state(task, state_dict): + """ + Updates the task with the provided task dictionary. + + Args: + task: ClassyTask instance to update + state_dict: State dict, should be the output of a call to + ClassyTask.get_classy_state(). + """ + logging.info("Loading classy state from checkpoint") + + try: + task.set_classy_state(state_dict) + logging.info("Checkpoint load successful") + return True + except Exception: + logging.exception("Could not load the checkpoint") + + return False + + +def save_checkpoint(checkpoint_folder, state, checkpoint_file=CHECKPOINT_FILE): + """ + Saves a state variable to the specified checkpoint folder. Returns filename + of checkpoint if successful, and False otherwise. + """ + + # make sure that we have a checkpoint folder: + if not os.path.isdir(checkpoint_folder): + try: + os.makedirs(checkpoint_folder) + except BaseException: + logging.warning( + "Could not create folder %s." % checkpoint_folder, exc_info=True + ) + if not os.path.isdir(checkpoint_folder): + return False + + # write checkpoint atomically: + try: + with tempfile.NamedTemporaryFile( + "w+b", dir=checkpoint_folder, delete=False + ) as fwrite: + tmp_fname = fwrite.name + torch.save(state, fwrite.name) + full_filename = os.path.join(checkpoint_folder, checkpoint_file) + os.rename(tmp_fname, full_filename) + os.chmod(full_filename, 0o666) + return full_filename + except BaseException: + logging.warning( + "Did not write checkpoint to %s." % checkpoint_folder, exc_info=True + ) + return False + + +def stepwise_learning_rate(base_lr, epoch, optimizer, epoch_step=30): + """ + Step-wise reduction of learning rate by a factor of 10 every epoch_step epochs. + """ + lr = base_lr * (0.1 ** (epoch // epoch_step)) + for param_group in optimizer.param_groups: + param_group["lr"] = lr + + +def cosine_learning_rate(base_lr, epoch, optimizer, max_epoch=90): + """ + Cosine-shaped reduction of learning rate. + """ + lr = 0.5 * base_lr * (1.0 + math.cos(math.pi * epoch / max_epoch)) + for param_group in optimizer.param_groups: + param_group["lr"] = lr + + +def visualize_image(img, mean=None, std=None): + """ + Make preprocessed image look pretty again to facilitate visualization. + """ + + # assertions: + assert torch.is_tensor(img) and img.dim() == 3 + for val in [mean, std]: + if val is not None: + assert torch.is_tensor(val) + assert val.dim() == 1 and len(val) == img.size(0) + + # undo mean/std normalization: + new_img = img.clone() + if mean is not None and std is not None: + for c in range(new_img.size(0)): + new_img[c].mul_(std[c]).add_(mean[c]) + + # normalize image to be a byte image: + if new_img.max() < 1.1: + new_img.mul_(255.0) + return new_img.byte() + + +def set_proxies(): + """Set proxies to allow downloading of external URLs.""" + os.environ["HTTP_PROXY"] = "http://fwdproxy:8080" + os.environ["HTTPS_PROXY"] = "http://fwdproxy:8080" + os.environ["http_proxy"] = "fwdproxy:8080" + os.environ["https_proxy"] = "fwdproxy:8080" + + +def unset_proxies(): + """Unset proxies to prevent downloading of external URLs.""" + del os.environ["HTTP_PROXY"] + del os.environ["HTTPS_PROXY"] + del os.environ["http_proxy"] + del os.environ["https_proxy"] + + +def flatten_dict(value_dict, prefix="", sep="_"): + """ + Flattens nested dict into (key, val) dict. Used for flattening meters + structure, so that they can be visualized. + """ + items = [] + for k, v in value_dict.items(): + key = prefix + sep + k if prefix else k + if isinstance(v, collections.MutableMapping): + items.extend(flatten_dict(value_dict=v, prefix=key, sep=sep).items()) + else: + items.append((key, v)) + return dict(items) + + +def load_json(json_path): + """ + Loads a json config from a file. + """ + assert os.path.exists(json_path), "Json file %s not found" % json_path + json_file = open(json_path) + json_config = json_file.read() + json_file.close() + try: + config = json.loads(json_config) + except BaseException as err: + raise Exception("Failed to validate config with error: %s" % str(err)) + + return config + + +@contextlib.contextmanager +def torch_seed(seed): + """Context manager which seeds the PyTorch PRNG with the specified seed and + restores the state afterward. Setting seed to None is equivalent to running + the code without the context manager.""" + if seed is None: + yield + return + state = torch.get_rng_state() + torch.manual_seed(seed) + try: + yield + finally: + torch.set_rng_state(state) + + +# NOTE [ Python Traceback Reference Cycle Problem ] +# +# When using sys.exc_info(), it is important to **not** store the exc_info[2], +# which is the traceback, because otherwise you will run into the traceback +# reference cycle problem, i.e., the traceback holding reference to the frame, +# and the frame (which holds reference to all the object in its temporary scope) +# holding reference the traceback. +# +# TODO: aadcock: This is a fork of the PyTorch ExceptionWrapper class +# to facilitate the backfill dataloader until we kill it. Once we kill +# datasets/core/backfill_async_dataset_iterator.py we can kill these functions +class KeyErrorMessage(str): + r"""str subclass that returns itself in repr""" + + def __repr__(self): + return self + + +class ExceptionWrapper(object): + r"""Wraps an exception plus traceback to communicate across threads""" + + def __init__(self, exc_info=None, where="in background"): + # It is important that we don't store exc_info, see + # NOTE [ Python Traceback Reference Cycle Problem ] + if exc_info is None: + exc_info = sys.exc_info() + self.exc_type = exc_info[0] + self.exc_msg = "".join(traceback.format_exception(*exc_info)) + self.where = where + + def reraise(self): + r"""Reraises the wrapped exception in the current thread""" + # Format a message such as: "Caught ValueError in DataLoader worker + # process 2. Original Traceback:", followed by the traceback. + msg = "Caught {} {}.\nOriginal {}".format( + self.exc_type.__name__, self.where, self.exc_msg + ) + if self.exc_type == KeyError: + # KeyError calls repr() on its argument (usually a dict key). This + # makes stack traces unreadable. It will not be changed in Python + # (https://bugs.python.org/issue2651), so we work around it. + msg = KeyErrorMessage(msg) + raise self.exc_type(msg) + + +def convert_to_one_hot(targets, classes): + """ + This function converts target class indices to one-hot vectors, + given the number of classes. + + """ + assert ( + torch.max(targets).item() < classes + ), "Class Index must be less than number of classes" + one_hot_targets = torch.zeros( + (targets.shape[0], classes), dtype=torch.long, device=targets.device + ) + one_hot_targets.scatter_(1, targets.long(), 1) + return one_hot_targets + + +def bind_method_to_class(method, cls): + """ + Binds an already bound method to the provided class. + """ + return method.__func__.__get__(cls) + + +def get_model_dummy_input( + model, input_shape, input_key, batchsize=1, non_blocking=False +): + if isinstance(input_key, list): + # av mode, with multiple input keys + input = {} + for i, key in enumerate(input_key): + shape = (batchsize,) + tuple(input_shape[i]) + cur_input = torch.zeros(shape) + if next(model.parameters()).is_cuda: + cur_input = cur_input.cuda(non_blocking=non_blocking) + input[key] = cur_input + else: + # add a dimension to represent minibatch axis + shape = (batchsize,) + tuple(input_shape) + input = torch.zeros(shape) + if next(model.parameters()).is_cuda: + input = input.cuda(non_blocking=non_blocking) + if input_key: + input = {input_key: input} + return input diff --git a/classy_vision/generic/visualize.py b/classy_vision/generic/visualize.py new file mode 100644 index 0000000000..9a29342890 --- /dev/null +++ b/classy_vision/generic/visualize.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import math + +import numpy as np +import torch +import torch.nn.modules as nn +from classy_vision.generic.util import get_model_dummy_input, is_pos_int +from PIL import Image + + +try: + import visdom + from tensorboardX import SummaryWriter +except ImportError: + pass + +# define loss types: +vis = [] # using a list makes this work as an upvalue +UNSUPPORTED_LOSSES = ( + nn.CosineEmbeddingLoss, + nn.PoissonNLLLoss, + nn.KLDivLoss, + nn.TripletMarginLoss, +) +REGRESSION_LOSSES = (nn.L1Loss, nn.SmoothL1Loss, nn.MSELoss) + + +# connection to visdom: +def visdom_connect(server=None, port=None): + """Connects to a visdom server if not currently connected.""" + if not visdom_connected(): + vis.append(visdom.Visdom(server=server, port=port)) + + +# check if we are connected to visdom: +def visdom_connected(): + """Returns True if the client is connected to a visdom server.""" + return ( + len(vis) > 0 + and hasattr(vis[-1], "check_connection") + and vis[-1].check_connection() + ) + + +# function that plots learning curve: +def plot_learning_curves(curves, visdom_server=None, env=None, win=None, title=""): + """Plots the specified dict of learning curves in visdom. Optionally, the + environment, window handle, and title for the visdom plot can be specified. + """ + + if visdom_server is None and visdom_connected(): + visdom_server = vis[-1] + + # return if we are not connected to visdom server: + if not visdom_server or not visdom_server.check_connection(): + print("WARNING: Not connected to visdom. Skipping plotting.") + return + + # assertions: + assert type(curves) == dict + assert all(type(curve) == list for _, curve in curves.items()) + + # remove batch time curves: + _curves = {k: curves[k] for k in curves.keys() if "batch time" not in k} + + # show plot: + X = torch.stack([torch.FloatTensor(curve) for _, curve in _curves.items()], dim=1) + Y = torch.arange(0, X.size(0)) + Y = Y.view(Y.numel(), 1).expand(Y.numel(), X.size(1)) + opts = {"title": title, "legend": list(_curves.keys()), "xlabel": "Epochs"} + return visdom_server.line(X, Y, env=env, win=win, opts=opts) + + +# function that plots loss functions: +def plot_losses(losses, visdom_server=None, env=None, win=None, title=""): + """Constructs a plot of specified losses as function of y * f(x). The losses + are a list of nn.Module losses. Optionally, the environment, window handle, + and title for the visdom plot can be specified. + """ + + if visdom_server is None and visdom_connected(): + visdom_server = vis[-1] + + # return if we are not connected to visdom server: + if not visdom_server or not visdom_server.check_connection(): + print("WARNING: Not connected to visdom. Skipping plotting.") + return + + # assertions: + if isinstance(losses, nn.Module): + losses = [losses] + assert type(losses) == list + assert all(isinstance(loss, nn.Module) for loss in losses) + if any(isinstance(loss, UNSUPPORTED_LOSSES) for loss in losses): + raise NotImplementedError("loss function not supported") + + # loop over all loss functions: + for idx, loss in enumerate(losses): + + # construct scores and targets: + score = torch.arange(-5.0, 5.0, 0.005) + if idx == 0: + loss_val = torch.FloatTensor(score.size(0), len(losses)) + if isinstance(loss, REGRESSION_LOSSES): + target = torch.FloatTensor(score.size()).fill_(0.0) + else: + target = torch.LongTensor(score.size()).fill_(1) + + # compute loss values: + for n in range(0, score.nelement()): + loss_val[n][idx] = loss( + score.narrow(0, n, 1), target.narrow(0, n, 1) + ).item() + + # show plot: + title = str(loss) if title == "" else title + legend = [str(loss) for loss in losses] + opts = {"title": title, "xlabel": "Score", "ylabel": "Loss", "legend": legend} + win = visdom_server.line(loss_val, score, env=env, win=win, opts=opts) + return win + + +def plot_model( + model, size=(3, 224, 224), input_key=None, writer=None, folder="", train=True +): + """Visualizes a model in TensorBoard. + + The TensorBoard writer can be either specified directly via `writer` or can + be specified via a `folder`. + + The model can be run in training or evaluation model via the `train` argument. + + Example usage on devserver: + - Install TensorBoard using: `sudo feature install tensorboard` + - Start TensorBoard using: `tensorboard --port=8098 --logdir ` + """ + + assert ( + writer is not None or folder != "" + ), "must specify SummaryWriter or folder to create SummaryWriter in" + input = get_model_dummy_input(model, size, input_key) + if writer is None: + writer = SummaryWriter(log_dir=folder, comment="Model graph") + with writer: + orig_train = model.training + model.train(train) # visualize model in desired mode + writer.add_graph(model, input_to_model=(input,)) + model.train(orig_train) + + +# function that produces an image map: +def image_map( + mapcoord, dataset, mapsize=5000, imsize=32, unnormalize=None, snap_to_grid=False +): + """Constructs a 2D map of images. + + The 2D coordinates for each of the images are specified in `mapcoord`, the + corresponding images are in `dataset`. Optional arguments set the size of + the map images, the size of the images themselves, the unnormalization + transform, and whether or not to snap images to a grid. + """ + + # assertions: + if type(mapcoord) == np.ndarray: + mapcoord = torch.from_numpy(mapcoord) + assert torch.is_tensor(mapcoord) + if isinstance(dataset, torch.utils.data.dataloader.DataLoader): + dataset = dataset.dataset + assert isinstance(dataset, torch.utils.data.dataset.Dataset) + assert is_pos_int(mapsize) + assert is_pos_int(imsize) + if unnormalize is not None: + assert callable(unnormalize) + + # initialize some variables: + import torchvision.transforms.functional as F + + background = 255 + mapim = torch.ByteTensor(3, mapsize, mapsize).fill_(background) + + # normalize map coordinates: + mapc = mapcoord.add(-mapcoord.min()) + mapc.div_(mapc.max()) + + # loop over images: + for idx in range(len(dataset)): + + # compute grid location: + if snap_to_grid: + y = 1 + int(math.floor(mapc[idx][0] * (mapsize - imsize - 2))) + x = 1 + int(math.floor(mapc[idx][1] * (mapsize - imsize - 2))) + else: + y = 1 + int(math.floor(mapc[idx][0] * (math.floor(mapsize - imsize) - 2))) + x = 1 + int(math.floor(mapc[idx][1] * (math.floor(mapsize - imsize) - 2))) + + # check whether we can overwrite this location: + overwrite = not snap_to_grid + if not overwrite: + segment = mapim.narrow(1, y, imsize).narrow(2, x, imsize) + overwrite = segment.eq(background).all() + + # draw image: + if overwrite: + + # load, unnormalize, and resize image: + image = dataset[idx][0] + if unnormalize is not None: + image = unnormalize(image) + resized_im = F.to_tensor( + F.resize(F.to_pil_image(image), imsize, Image.BILINEAR) + ) + + # place image: + segment = mapim.narrow(1, y, imsize).narrow(2, x, imsize) + segment.copy_(resized_im.mul_(255.0).byte()) + + # return map: + return mapim diff --git a/classy_vision/heads/__init__.py b/classy_vision/heads/__init__.py new file mode 100644 index 0000000000..6047032a14 --- /dev/null +++ b/classy_vision/heads/__init__.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_head import ClassyHead + + +FILE_ROOT = Path(__file__).parent + + +HEAD_REGISTRY = {} +HEAD_CLASS_NAMES = set() + + +def register_head(name): + """Registers a ClassyHead subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyHead from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyHead subclass, like this: + + .. code-block:: python + + @register_head("my_head") + class MyHead(ClassyHead): + ... + + To instantiate a head from a configuration file, see + :func:`build_head`.""" + + def register_head_cls(cls): + if name in HEAD_REGISTRY: + raise ValueError("Cannot register duplicate head ({})".format(name)) + if not issubclass(cls, ClassyHead): + raise ValueError( + "Head ({}: {}) must extend ClassyHead".format(name, cls.__name__) + ) + if cls.__name__ in HEAD_CLASS_NAMES: + raise ValueError( + "Cannot register head with duplicate class name ({})".format( + cls.__name__ + ) + ) + HEAD_REGISTRY[name] = cls + HEAD_CLASS_NAMES.add(cls.__name__) + return cls + + return register_head_cls + + +def build_head(config): + """Builds a ClassyHead from a config. + + This assumes a 'name' key in the config which is used to determine what + head class to instantiate. For instance, a config `{"name": "my_head", + "foo": "bar"}` will find a class that was registered as "my_head" + (see :func:`register_head`) and call .from_config on it.""" + + assert "name" in config, "Expect name in config" + assert "unique_id" in config, "Expect a global unique id in config" + assert config["name"] in HEAD_REGISTRY, "unknown head" + name = config["name"] + head_config = copy.deepcopy(config) + del head_config["name"] + return HEAD_REGISTRY[name].from_config(head_config) + + +# automatically import any Python files in the heads/ directory +import_all_modules(FILE_ROOT, "classy_vision.heads") + +from .fully_connected_head import FullyConnectedHead # isort:skip +from .fully_convolutional_linear_head import FullyConvolutionalLinearHead # isort:skip +from .identity_head import IdentityHead # isort:skip + + +__all__ = [ + "ClassyHead", + "FullyConnectedHead", + "FullyConvolutionalLinearHead", + "IdentityHead", + "build_head", + "register_head", +] diff --git a/classy_vision/heads/classy_head.py b/classy_vision/heads/classy_head.py new file mode 100644 index 0000000000..380bb7f838 --- /dev/null +++ b/classy_vision/heads/classy_head.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict, Optional + +import torch.nn as nn + + +class ClassyHead(nn.Module): + """ + Base class for heads that can be attached to :class:`ClassyModel`. + + A head is a regular :class:`torch.nn.Module` that can be attached to a + pretrained model. This enables a form of transfer learning: utilizing a + model trained for one dataset to extract features that can be used for + other problems. A head must be attached to a :class:`models.ClassyBlock` + within a :class:`models.ClassyModel`. + """ + + def __init__( + self, unique_id: Optional[str] = None, num_classes: Optional[int] = None + ): + """ + Constructs a ClassyHead. + + Args: + unique_id: A unique identifier for the head. Multiple instances of + the same head might be attached to a model, and unique_id is used + to refer to them. + + num_classes: Number of classes for the head. + """ + super().__init__() + self.unique_id = unique_id or self.__class__.__name__ + self.num_classes = num_classes + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyHead": + """Instantiates a ClassyHead from a configuration. + + Args: + config: A configuration for the ClassyHead. + + Returns: + A ClassyHead instance. + """ + raise NotImplementedError + + def forward(self, x): + """ + Performs inference on the head. + + This is a regular PyTorch method, refer to :class:`torch.nn.Module` for + more details + """ + raise NotImplementedError diff --git a/classy_vision/heads/fully_connected_head.py b/classy_vision/heads/fully_connected_head.py new file mode 100644 index 0000000000..1377fd55e9 --- /dev/null +++ b/classy_vision/heads/fully_connected_head.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch.nn as nn +from classy_vision.generic.util import is_pos_int +from classy_vision.heads import ClassyHead, register_head + + +@register_head("fully_connected") +class FullyConnectedHead(ClassyHead): + """This head defines a 2d average pooling layer + (:class:`torch.nn.AdaptiveAvgPool2d`) followed by a fully connected + layer (:class:`torch.nn.Linear`). + """ + + def __init__(self, unique_id: str, num_classes: int, in_plane: int): + """Constructor for FullyConnectedHead + + Args: + unique_id: A unique identifier for the head. Multiple instances of + the same head might be attached to a model, and unique_id is used + to refer to them. + + num_classes: Number of classes for the head. If None, then the fully + connected layer is not applied. + + in_plane: Input size for the fully connected layer. + """ + super().__init__(unique_id, num_classes) + assert num_classes is None or is_pos_int(num_classes) + assert is_pos_int(in_plane) + self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + self.fc = None if num_classes is None else nn.Linear(in_plane, num_classes) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "FullyConnectedHead": + """Instantiates a FullyConnectedHead from a configuration. + + Args: + config: A configuration for a FullyConnectedHead. + See :func:`__init__` for parameters expected in the config. + + Returns: + A FullyConnectedHead instance. + """ + num_classes = config.get("num_classes", None) + in_plane = config["in_plane"] + return cls(config["unique_id"], num_classes, in_plane) + + def forward(self, x): + # perform average pooling: + out = self.avgpool(x) + + # final classifier: + out = out.reshape(out.size(0), -1) + if self.fc is not None: + out = self.fc(out) + return out diff --git a/classy_vision/heads/fully_convolutional_linear_head.py b/classy_vision/heads/fully_convolutional_linear_head.py new file mode 100644 index 0000000000..622d884815 --- /dev/null +++ b/classy_vision/heads/fully_convolutional_linear_head.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from collections.abc import Sequence +from typing import Any, Dict, List, Optional + +import torch.nn as nn +from classy_vision.generic.util import is_pos_int +from classy_vision.heads import ClassyHead, register_head + + +class FullyConvolutionalLinear(nn.Module): + def __init__(self, dim_in, num_classes, act_func="softmax"): + super(FullyConvolutionalLinear, self).__init__() + # Perform FC in a fully convolutional manner. The FC layer will be + # initialized with a different std comparing to convolutional layers. + self.projection = nn.Linear(dim_in, num_classes, bias=True) + + # Softmax for evaluation and testing. + if act_func == "softmax": + self.act = nn.Softmax(dim=4) + elif act_func == "sigmoid": + self.act = nn.Sigmoid() + else: + raise NotImplementedError( + "{} is not supported as an activation" "function.".format(act_func) + ) + + def forward(self, x): + # (N, C, T, H, W) -> (N, T, H, W, C). + x = x.permute((0, 2, 3, 4, 1)) + x = self.projection(x) + # Performs fully convlutional inference. + if not self.training: + x = self.act(x) + x = x.mean([1, 2, 3]) + x = x.view(x.shape[0], -1) + return x + + +@register_head("fully_convolutional_linear") +class FullyConvolutionalLinearHead(ClassyHead): + """ + This head defines a 3d average pooling layer (:class:`torch.nn.AvgPool3d`) + followed by a fully convolutional linear layer. This layer performs a + fully-connected projection during training, when the input size is 1x1x1. + It performs a convolutional projection during testing when the input size + is larger than 1x1x1. + """ + + def __init__( + self, + unique_id: str, + num_classes: int, + in_plane: int, + pool_size: List[int], + activation_func: str, + use_dropout: Optional[bool] = None, + ): + """ + Constructor for FullyConvolutionalLinearHead. + + Args: + unique_id: A unique identifier for the head. Multiple instances of + the same head might be attached to a model, and unique_id is used + to refer to them. + num_classes: Number of classes for the head. + in_plane: Input size for the fully connected layer. + pool_size: Kernel size for the 3d pooling layer. + activation_func: activation function to use. 'softmax': applies + softmax on the output. 'sigmoid': applies sigmoid on the output. + use_dropout: Whether to apply dropout after the pooling layer. + """ + super().__init__(unique_id, num_classes) + self.final_avgpool = nn.AvgPool3d(pool_size, stride=1) + if use_dropout: + self.dropout = nn.Dropout(p=0.5) + # we separate average pooling from the fully-convolutional linear projection + # because for multi-path models such as SlowFast model, the input can be + # more than 1 tesnor. In such case, we can define a new head to combine multiple + # tensors via concat or addition, do average pooling, but still reuse + # FullyConvolutionalLinear inside of it. + self.head_fcl = FullyConvolutionalLinear( + in_plane, num_classes, act_func=activation_func + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "FullyConvolutionalLinearHead": + """Instantiates a FullyConvolutionalLinearHead from a configuration. + + Args: + config: A configuration for a FullyConvolutionalLinearHead. + See :func:`__init__` for parameters expected in the config. + + Returns: + A FullyConvolutionalLinearHead instance. + """ + required_args = ["pool_size", "in_plane", "num_classes"] + for arg in required_args: + assert arg in config, "argument %s is required" % arg + + config.update({"activation_func": config.get("activation_func", "softmax")}) + config.update({"use_dropout": config.get("use_dropout", False)}) + + assert ( + isinstance(config["pool_size"], Sequence) and len(config["pool_size"]) == 3 + ) + for pool_size_dim in config["pool_size"]: + assert is_pos_int(pool_size_dim) + assert is_pos_int(config["in_plane"]) + assert is_pos_int(config["num_classes"]) + + num_classes = config.get("num_classes", None) + in_plane = config["in_plane"] + return cls( + config["unique_id"], + num_classes, + in_plane, + config["pool_size"], + config["activation_func"], + config["use_dropout"], + ) + + def forward(self, x): + out = self.final_avgpool(x) + if hasattr(self, "dropout"): + out = self.dropout(out) + out = self.head_fcl(out) + return out diff --git a/classy_vision/heads/identity_head.py b/classy_vision/heads/identity_head.py new file mode 100644 index 0000000000..c8dc6e40c0 --- /dev/null +++ b/classy_vision/heads/identity_head.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +from classy_vision.heads import ClassyHead, register_head + + +@register_head("identity") +class IdentityHead(ClassyHead): + """This head returns the input without changing it. This can + be attached to a model, if the output of the model is the + desired result. + """ + + def forward(self, x): + return x + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "IdentityHead": + """Instantiates a IdentityHead from a configuration. + + Args: + config: A configuration for a IdentityHead. + See :func:`__init__` for parameters expected in the config. + + Returns: + A IdentityHead instance. + """ + return cls(config["unique_id"]) diff --git a/classy_vision/hooks/__init__.py b/classy_vision/hooks/__init__.py new file mode 100644 index 0000000000..28eda768ea --- /dev/null +++ b/classy_vision/hooks/__init__.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + +from .checkpoint_hook import CheckpointHook +from .classy_hook import ClassyHook, ClassyHookFunctions +from .exponential_moving_average_model_hook import ExponentialMovingAverageModelHook +from .loss_lr_meter_logging_hook import LossLrMeterLoggingHook +from .model_complexity_hook import ModelComplexityHook +from .model_tensorboard_hook import ModelTensorboardHook +from .profiler_hook import ProfilerHook +from .progress_bar_hook import ProgressBarHook +from .tensorboard_plot_hook import TensorboardPlotHook +from .time_metrics_hook import TimeMetricsHook +from .visdom_hook import VisdomHook + + +__all__ = [ + "CheckpointHook", + "ClassyHook", + "ClassyHookFunctions", + "ExponentialMovingAverageModelHook", + "LossLrMeterLoggingHook", + "TensorboardPlotHook", + "ModelComplexityHook", + "ModelTensorboardHook", + "ProfilerHook", + "ProgressBarHook", + "TimeMetricsHook", + "VisdomHook", +] + +FILE_ROOT = Path(__file__).parent + +# automatically import any Python files in the hooks/ directory +import_all_modules(FILE_ROOT, "classy_vision.hooks") diff --git a/classy_vision/hooks/checkpoint_hook.py b/classy_vision/hooks/checkpoint_hook.py new file mode 100644 index 0000000000..e8f3a300d8 --- /dev/null +++ b/classy_vision/hooks/checkpoint_hook.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import os +import tempfile +from shutil import copy2, move +from typing import Any, Collection, Dict, Optional + +from classy_vision import tasks +from classy_vision.generic.distributed_util import is_master +from classy_vision.generic.util import get_checkpoint_dict, save_checkpoint +from classy_vision.hooks.classy_hook import ClassyHook + + +class CheckpointHook(ClassyHook): + """ + Hook to checkpoint a model's task. + + Saves the checkpoints in checkpoint_folder. + """ + + on_rendezvous = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_update = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__( + self, + checkpoint_folder: str, + input_args: Any, + phase_types: Optional[Collection[str]] = None, + checkpoint_period: int = 1, + ) -> None: + """The constructor method of CheckpointHook. + + Args: + checkpoint_folder: Folder to store checkpoints in + input_args: Any arguments to save about the runtime setup. For example, + it is useful to store the config that was used to instantiate the model. + phase_types: If ``phase_types`` is specified, only checkpoint on those phase + types. Each item in ``phase_types`` must be either "train" or "test". + checkpoint_period: Checkpoint at the end of every x phases (default 1) + + """ + super().__init__() + self.checkpoint_folder: str = checkpoint_folder + self.input_args: Any = input_args + if phase_types is None: + phase_types = ["train", "test"] + assert len(phase_types) > 0 and all( + phase_type in ["train", "test"] for phase_type in phase_types + ), "phase_types should contain one or more of ['train', 'test']" + assert ( + isinstance(checkpoint_period, int) and checkpoint_period > 0 + ), "checkpoint period must be positive" + + self.phase_types: Collection[str] = phase_types + self.checkpoint_period: int = checkpoint_period + self.phase_counter: int = 0 + + def _save_checkpoint(self, task, filename): + if getattr(task, "test_only", False): + return + assert os.path.exists( + self.checkpoint_folder + ), "Checkpoint folder '{}' deleted unexpectedly".format(self.checkpoint_folder) + + # save checkpoint: + logging.info("Saving checkpoint to '{}'...".format(self.checkpoint_folder)) + checkpoint_file = save_checkpoint( + self.checkpoint_folder, get_checkpoint_dict(task, self.input_args) + ) + + # make copy of checkpoint that won't be overwritten: + if checkpoint_file: + tmp_dir = tempfile.mkdtemp() + tmp_file = os.path.join(tmp_dir, filename) + copy2(checkpoint_file, tmp_file) + move(tmp_file, os.path.join(self.checkpoint_folder, filename)) + + def on_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + if getattr(task, "test_only", False): + return + if not os.path.exists(self.checkpoint_folder): + err_msg = "Checkpoint folder '{}' does not exist.".format( + self.checkpoint_folder + ) + raise FileNotFoundError(err_msg) + + def on_phase_end( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Checkpoint the task every checkpoint_period phases. + + We do not necessarily checkpoint the task at the end of every phase. + """ + if not is_master() or task.phase_type not in self.phase_types: + return + + self.phase_counter += 1 + if self.phase_counter % self.checkpoint_period != 0: + return + + checkpoint_name = "model_phase-{phase}_end.torch".format(phase=task.phase_idx) + self._save_checkpoint(task, checkpoint_name) diff --git a/classy_vision/hooks/classy_hook.py b/classy_vision/hooks/classy_hook.py new file mode 100644 index 0000000000..33b0ed2461 --- /dev/null +++ b/classy_vision/hooks/classy_hook.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from abc import ABC, abstractmethod +from enum import Enum, auto +from typing import Any, Dict + +from classy_vision import tasks + + +class ClassyHookFunctions(Enum): + """ + Enumeration of all the hook functions in the ClassyHook class. + """ + + on_rendezvous = auto() + on_start = auto() + on_phase_start = auto() + on_sample = auto() + on_forward = auto() + on_loss_and_meter = auto() + on_backward = auto() + on_update = auto() + on_phase_end = auto() + on_end = auto() + + +class ClassyHookState: + """Class to store state within instances of ClassyHook. + + Any serializable data can be stored in the instance's attributes. + """ + + def get_classy_state(self) -> Dict[str, Any]: + return self.__dict__ + + def set_classy_state(self, state_dict: Dict[str, Any]): + self.__dict__ = state_dict + + +class ClassyHook(ABC): + """Base class for hooks. + + Hooks allow to inject behavior at different places of the training loop, which + are listed below in the chronological order. + + on_start -> on_phase_start -> on_sample -> on_forward -> on_loss_and_meter -> + on_backward -> on_update -> on_phase_end -> on_end + + Deriving classes should call ``super().__init__()`` and store any state in + ``self.state``. Any state added to this property should be serializable. + E.g. - + + .. code-block:: python + + class MyHook(ClassyHook): + def __init__(self, a, b): + super().__init__() + self.state.a = [1,2,3] + self.state.b = "my_hook" + # the following line is not allowed + # self.state.my_lambda = lambda x: x^2 + + """ + + def __init__(self): + self.state = ClassyHookState() + + def _noop(self, task: "tasks.ClassyTask", local_variables: Dict[str, Any]) -> None: + """Derived classes can set their hook functions to this. + + This is useful if they want those hook functions to not do anything. + + """ + pass + + @classmethod + def name(cls) -> str: + """Returns the name of the class.""" + return cls.__name__ + + @abstractmethod + def on_rendezvous( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called when the trainers rendezvous.""" + pass + + @abstractmethod + def on_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called at the start of training.""" + pass + + @abstractmethod + def on_phase_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called at the start of each phase.""" + pass + + @abstractmethod + def on_sample( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called each time trainer obtained a sample from the dataset.""" + pass + + @abstractmethod + def on_forward( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called each time forward pass is done in the model.""" + pass + + @abstractmethod + def on_loss_and_meter( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called each time after a loss has been computed and meters are updated.""" + pass + + @abstractmethod + def on_backward( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called each time a backward step is performed on the loss.""" + pass + + @abstractmethod + def on_update( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called each time after parameters have been updated by the optimizer.""" + pass + + @abstractmethod + def on_phase_end( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Called at the end of each phase (epoch).""" + pass + + @abstractmethod + def on_end(self, task: "tasks.ClassyTask", local_variables: Dict[str, Any]) -> None: + """Called at the end of training.""" + pass + + def get_classy_state(self) -> Dict[str, Any]: + """Get the state of the ClassyHook. + + The returned state is used for checkpointing. + + Returns: + A state dictionary containing the state of the hook.\ + + """ + return self.state.get_classy_state() + + def set_classy_state(self, state_dict: Dict[str, Any]) -> None: + """Set the state of the ClassyHook. + + Args: + state_dict: The state dictionary. Must be the output of a call to + :func:`get_classy_state`. + + This is used to load the state of the hook from a checkpoint. + + """ + self.state.set_classy_state(state_dict) diff --git a/classy_vision/hooks/exponential_moving_average_model_hook.py b/classy_vision/hooks/exponential_moving_average_model_hook.py new file mode 100644 index 0000000000..762fb5c2e9 --- /dev/null +++ b/classy_vision/hooks/exponential_moving_average_model_hook.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import itertools +import logging +from typing import Any, Dict, Iterable, Tuple + +import torch +import torch.nn as nn +from classy_vision.hooks import ClassyHook +from classy_vision.tasks import ClassyTask + + +class ExponentialMovingAverageModelHook(ClassyHook): + """ + Hook which keeps a track of the exponential moving average (EMA) of the model's + parameters and applies the EMA params to the model during the test phases. + + Saving the state in cpu will save gpu memory, but will make training slower since + the model parameters will need to be moved to cpu before the averaging. + + Note: + This hooks stores two additional copies of the model's parameters, which will + increase memory usage significantly. + """ + + on_rendezvous = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__( + self, decay: float, consider_bn_buffers: bool = True, device: str = "cpu" + ) -> None: + """The constructor method of ExponentialMovingAverageModelHook. + + Args: + decay: EMA decay factor, should be in [0, 1]. A decay of 0 corresponds to + always using the latest value (no EMA) and a decay of 1 corresponds to + not updating weights after initialization. + consider_bn_buffers: Whether to apply EMA to batch norm buffers + device: Device to store the model state. + """ + super().__init__() + assert 0 <= decay <= 1, "Decay should be between 0 and 1" + assert device in ["cpu", "gpu"], "Device should be one of cpu or gpu" + self.decay: int = decay + self.consider_bn_buffers = consider_bn_buffers + self.device = "cuda" if device == "gpu" else "cpu" + self.state.model_state = {} + self.state.ema_model_state = {} + logging.info( + f"{self.__class__.__name__} initialized with a decay of " + f"{decay} on device {device}" + ) + + def get_model_state_iterator(self, model: nn.Module) -> Iterable[Tuple[str, Any]]: + """Get an iterator over the model state to apply EMA to.""" + iterable = model.named_parameters() + if self.consider_bn_buffers: + # also add batch norm buffers to the list of state params to iterate over + buffers_iterable = ( + (f"{module_name}_buffer_{name}", buffer) + for module_name, module in model.named_modules() + for name, buffer in module.named_buffers() + if isinstance( + module, + (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm), + ) + ) + iterable = itertools.chain(iterable, buffers_iterable) + return iterable + + def _save_current_model_state(self, model: nn.Module, model_state: Dict[str, Any]): + """Copy the model's state to the provided dict.""" + for name, param in self.get_model_state_iterator(model): + model_state[name] = param.detach().clone().to(device=self.device) + + def on_start(self, task: ClassyTask, local_variables: Dict[str, Any]) -> None: + if self.state.model_state: + # loaded state from checkpoint, do not re-initialize, only move the state + # to the right device + for name in self.state.model_state: + self.state.model_state[name] = self.state.model_state[name].to( + device=self.device + ) + self.state.ema_model_state[name] = self.state.ema_model_state[name].to( + device=self.device + ) + return + self._save_current_model_state(task.base_model, self.state.model_state) + self._save_current_model_state(task.base_model, self.state.ema_model_state) + + def on_phase_start(self, task: ClassyTask, local_variables: Dict[str, Any]) -> None: + # restore the right state depending on the phase type + self.set_model_state(task, use_ema=not task.train) + + def on_phase_end(self, task: ClassyTask, local_variables: Dict[str, Any]) -> None: + if task.train: + # save the current model state since this will be overwritten by the ema + # state in the test phase + self._save_current_model_state(task.base_model, self.state.model_state) + + def on_update(self, task: ClassyTask, local_variables: Dict[str, Any]) -> None: + with torch.no_grad(): + for name, param in self.get_model_state_iterator(task.base_model): + self.state.ema_model_state[ + name + ] = self.decay * self.state.ema_model_state[name] + ( + 1 - self.decay + ) * param.to( + device=self.device + ) + + def set_model_state(self, task: ClassyTask, use_ema: bool) -> None: + """ + Depending on use_ema, set the appropriate state for the model. + """ + model_state = self.state.ema_model_state if use_ema else self.state.model_state + with torch.no_grad(): + for name, param in self.get_model_state_iterator(task.base_model): + param.copy_(model_state[name]) diff --git a/classy_vision/hooks/loss_lr_meter_logging_hook.py b/classy_vision/hooks/loss_lr_meter_logging_hook.py new file mode 100644 index 0000000000..3455d03375 --- /dev/null +++ b/classy_vision/hooks/loss_lr_meter_logging_hook.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from typing import Any, Dict, Optional + +from classy_vision import tasks +from classy_vision.generic.distributed_util import get_rank +from classy_vision.hooks.classy_hook import ClassyHook + + +class LossLrMeterLoggingHook(ClassyHook): + """ + Logs the loss, optimizer LR, and meters. Logs at the end of a phase. + """ + + on_rendezvous = ClassyHook._noop + on_start = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_backward = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__(self, log_freq: Optional[int] = None) -> None: + """The constructor method of LossLrMeterLoggingHook. + + Args: + log_freq: if specified, also logs every ``log_freq`` batches. + + """ + super().__init__() + self.log_freq: Optional[int] = log_freq + + def on_loss_and_meter( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Log metrics every log_freq batches, if log_freq is not None. + """ + if self.log_freq is None: + return + batches = len(task.losses) + if batches and batches % self.log_freq == 0: + logging.info("Local unsynced metric values:") + self._log_loss_meters(task, local_variables) + + def on_phase_end( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Log the loss, optimizer LR, and meters for the phase. + """ + batches = len(task.losses) + if batches: + # Most trainers will sync meters on phase end, however we + # do not explicitly state this since it is possible for a + # trainer to implement an unsynced end of phase meter or + # for meters to not provide a sync function. + logging.info("End of phase metric values:") + self._log_loss_meters(task, local_variables) + if task.train: + self._log_lr(task, local_variables) + + def on_update( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Log the LR every log_freq batches, if log_freq is not None. + """ + if self.log_freq is None: + return + batches = len(task.losses) + if batches and batches % self.log_freq == 0: + self._log_lr(task, local_variables) + + def _log_lr( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Compute and log the optimizer LR. + """ + optimizer_lr = task.optimizer.lr + logging.info("Learning Rate: {}\n".format(optimizer_lr)) + + def _log_loss_meters( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Compute and log the loss and meters. + """ + + phase_type = task.phase_type + phase_type_idx = task.train_phase_idx if task.train else task.eval_phase_idx + batches = len(task.losses) + + # Loss for the phase + loss = sum(task.losses) / (batches * task.get_batchsize_per_replica()) + + log_strs = [ + "Rank: {}, {} phase: {}, processed batches: {}".format( + get_rank(), phase_type, phase_type_idx, batches + ), + "{} loss: {}".format(phase_type, loss), + "Meters:", + ] + for meter in task.meters: + log_strs.append("{}".format(meter)) + logging.info("\n".join(log_strs)) diff --git a/classy_vision/hooks/model_complexity_hook.py b/classy_vision/hooks/model_complexity_hook.py new file mode 100644 index 0000000000..e965fded7c --- /dev/null +++ b/classy_vision/hooks/model_complexity_hook.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from typing import Any, Dict + +from classy_vision import tasks +from classy_vision.generic.profiler import compute_flops, count_params +from classy_vision.hooks.classy_hook import ClassyHook + + +class ModelComplexityHook(ClassyHook): + """ + Logs the number of paramaters and forward pass FLOPs of the model. + """ + + on_rendezvous = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_update = ClassyHook._noop + on_phase_end = ClassyHook._noop + on_end = ClassyHook._noop + + def on_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Measure number of parameters and number of FLOPs.""" + try: + num_flops = compute_flops( + task.base_model, + input_shape=task.base_model.input_shape, + input_key=task.base_model.input_key + if hasattr(task.base_model, "input_key") + else None, + ) + if num_flops is None: + logging.info("FLOPs for forward pass: skipped.") + else: + logging.info( + "FLOPs for forward pass: %d MFLOPs" % (float(num_flops) / 1e6) + ) + except NotImplementedError: + logging.warning( + """Model contains unsupported modules: + Could not compute FLOPs for model forward pass. Exception:""", + exc_info=True, + ) + logging.info( + "Number of parameters in model: %d" % count_params(task.base_model) + ) diff --git a/classy_vision/hooks/model_tensorboard_hook.py b/classy_vision/hooks/model_tensorboard_hook.py new file mode 100644 index 0000000000..2d0cbc6ccf --- /dev/null +++ b/classy_vision/hooks/model_tensorboard_hook.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from typing import Any, Dict + +from classy_vision import tasks +from classy_vision.generic.distributed_util import is_master +from classy_vision.generic.visualize import plot_model +from classy_vision.hooks.classy_hook import ClassyHook + + +try: + from tensorboardX import SummaryWriter # noqa F401 + + tbx_available = True +except ImportError: + tbx_available = False + + +class ModelTensorboardHook(ClassyHook): + """ + Shows the model graph in `TensorBoard _. + """ + + on_rendezvous = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_update = ClassyHook._noop + on_phase_end = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__(self, tb_writer) -> None: + """The constructor method of ModelTensorboardHook. + + Args: + tb_writer: `Tensorboard SummaryWriter `_ instance + + """ + super().__init__() + if not tbx_available: + raise RuntimeError( + "tensorboardX not installed, cannot use ModelTensorboardHook" + ) + + self.tb_writer = tb_writer + + def on_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Plot the model on Tensorboard. + """ + # Show model in tensorboard: + logging.info("Showing model graph in TensorBoard...") + + if is_master(): + try: + plot_model( + task.base_model, + size=task.base_model.input_shape, + input_key=task.base_model.input_key + if hasattr(task.base_model, "input_key") + else None, + writer=self.tb_writer, + ) + except Exception: + logging.warn( + "Unable to plot model to tensorboard. Exception: ", exc_info=True + ) diff --git a/classy_vision/hooks/profiler_hook.py b/classy_vision/hooks/profiler_hook.py new file mode 100644 index 0000000000..2fbe507708 --- /dev/null +++ b/classy_vision/hooks/profiler_hook.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from typing import Any, Dict + +from classy_vision import tasks +from classy_vision.generic.profiler import profile, summarize_profiler_info +from classy_vision.hooks.classy_hook import ClassyHook + + +class ProfilerHook(ClassyHook): + """ + Hook to profile a model and to show model runtime information, such as + the time breakdown in milliseconds of forward/backward pass. + """ + + on_rendezvous = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_update = ClassyHook._noop + on_phase_end = ClassyHook._noop + on_end = ClassyHook._noop + + def on_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Profile the forward pass.""" + logging.info("Profiling forward pass...") + batchsize_per_replica = getattr( + task.dataloaders[task.phase_type].dataset, "batchsize_per_replica", 1 + ) + input_shape = task.base_model.input_shape + p = profile( + task.model, + batchsize_per_replica=batchsize_per_replica, + input_shape=input_shape, + input_key=getattr(task.base_model, "input_key", None), + ) + logging.info(summarize_profiler_info(p)) diff --git a/classy_vision/hooks/progress_bar_hook.py b/classy_vision/hooks/progress_bar_hook.py new file mode 100644 index 0000000000..9f64b587c5 --- /dev/null +++ b/classy_vision/hooks/progress_bar_hook.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict, Optional + +from classy_vision import tasks +from classy_vision.generic.distributed_util import is_master +from classy_vision.hooks.classy_hook import ClassyHook + + +try: + import progressbar + + progressbar_available = True +except ImportError: + progressbar_available = False + + +class ProgressBarHook(ClassyHook): + """ + Displays a progress bar to show progress in processing batches. + """ + + on_rendezvous = ClassyHook._noop + on_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__(self) -> None: + """The constructor method of ProgressBarHook.""" + super().__init__() + self.progress_bar: Optional[progressbar.ProgressBar] = None + self.bar_size: int = 0 + self.batches: int = 0 + + def on_phase_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Create and display a progress bar with 0 progress.""" + if not progressbar_available: + raise RuntimeError( + "progressbar module not installed, cannot use ProgressBarHook" + ) + + if is_master(): + self.bar_size = task.num_batches_per_phase + self.batches = 0 + self.progress_bar = progressbar.ProgressBar(self.bar_size) + self.progress_bar.start() + + def on_update( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Update the progress bar with the batch size.""" + if is_master() and self.progress_bar is not None: + self.batches += 1 + self.progress_bar.update(min(self.batches, self.bar_size)) + + def on_phase_end( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Clear the progress bar at the end of the phase.""" + if is_master() and self.progress_bar is not None: + self.progress_bar.finish() diff --git a/classy_vision/hooks/tensorboard_plot_hook.py b/classy_vision/hooks/tensorboard_plot_hook.py new file mode 100644 index 0000000000..70dfae887d --- /dev/null +++ b/classy_vision/hooks/tensorboard_plot_hook.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import time +from typing import Any, Dict, List, Optional + +from classy_vision import tasks +from classy_vision.generic.distributed_util import is_master +from classy_vision.hooks.classy_hook import ClassyHook + + +try: + from tensorboardX import SummaryWriter # noqa F401 + + tbx_available = True +except ImportError: + tbx_available = False + + +log = logging.getLogger() + + +class TensorboardPlotHook(ClassyHook): + """ + Hook for writing the losses, learning rates and meters to `tensorboard _. + + Global steps are counted in terms of the number of samples processed. + """ + + on_rendezvous = ClassyHook._noop + on_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_backward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__(self, tb_writer) -> None: + """The constructor method of TensorboardPlotHook. + + Args: + tb_writer: `Tensorboard SummaryWriter `_ instance + """ + super().__init__() + if not tbx_available: + raise RuntimeError( + "tensorboardX not installed, cannot use TensorboardPlotHook" + ) + + self.tb_writer = tb_writer + self.learning_rates: Optional[List[float]] = None + self.wall_times: Optional[List[float]] = None + self.num_steps_global: Optional[List[int]] = None + + def on_phase_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Initialize losses and learning_rates.""" + self.learning_rates = [] + self.wall_times = [] + self.num_steps_global = [] + + def on_update( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Store the observed learning rates.""" + if self.learning_rates is None: + logging.warning("learning_rates is not initialized") + return + + if not task.train: + # Only need to log the average loss during the test phase + return + + learning_rate_val = task.optimizer.lr + + self.learning_rates.append(learning_rate_val) + self.wall_times.append(time.time()) + self.num_steps_global.append(task.num_updates) + + def on_phase_end( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """Add the losses and learning rates to tensorboard.""" + if self.learning_rates is None: + logging.warning("learning_rates is not initialized") + return + + batches = len(task.losses) + if batches == 0 or not is_master(): + return + + phase_type = task.phase_type + phase_type_idx = task.train_phase_idx if task.train else task.eval_phase_idx + + logging.info(f"Plotting to Tensorboard for {phase_type} phase {phase_type_idx}") + + phase_type = task.phase_type + loss_key = f"{phase_type}_loss" + learning_rate_key = f"{phase_type}_learning_rate_updates" + + if task.train: + for loss, learning_rate, global_step, wall_time in zip( + task.losses, self.learning_rates, self.num_steps_global, self.wall_times + ): + loss /= task.get_batchsize_per_replica() + self.tb_writer.add_scalar( + loss_key, loss, global_step=global_step, walltime=wall_time + ) + self.tb_writer.add_scalar( + learning_rate_key, + learning_rate, + global_step=global_step, + walltime=wall_time, + ) + + loss_avg = sum(task.losses) / (batches * task.get_batchsize_per_replica()) + + loss_key = "avg_{phase_type}_loss".format(phase_type=task.phase_type) + self.tb_writer.add_scalar(loss_key, loss_avg, global_step=phase_type_idx) + + # plot meters which return a dict + for meter in task.meters: + if not isinstance(meter.value, dict): + log.warn(f"Skipping meter {meter.name} with value: {meter.value}") + continue + for name, value in meter.value.items(): + meter_key = f"{phase_type}_{meter.name}_{name}" + self.tb_writer.add_scalar(meter_key, value, global_step=phase_type_idx) + + logging.info(f"Done plotting to Tensorboard") diff --git a/classy_vision/hooks/time_metrics_hook.py b/classy_vision/hooks/time_metrics_hook.py new file mode 100644 index 0000000000..d922a7cb40 --- /dev/null +++ b/classy_vision/hooks/time_metrics_hook.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import time +from typing import Any, Dict, Optional + +from classy_vision import tasks +from classy_vision.generic.distributed_util import get_rank +from classy_vision.generic.perf_stats import PerfStats +from classy_vision.hooks.classy_hook import ClassyHook + + +class TimeMetricsHook(ClassyHook): + """ + Computes and prints performance metrics. Logs at the end of a phase. + """ + + on_rendezvous = ClassyHook._noop + on_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_backward = ClassyHook._noop + on_update = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__(self, log_freq: Optional[int] = None) -> None: + """ + Args: + log_freq: if specified, logs every log_freq batches also. + """ + super().__init__() + self.log_freq: Optional[int] = log_freq + self.start_time: Optional[float] = None + + def on_phase_start( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Initialize start time and reset perf stats + """ + self.start_time = time.time() + local_variables["perf_stats"] = PerfStats() + + def on_loss_and_meter( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Log metrics every log_freq batches, if log_freq is not None. + """ + if self.log_freq is None: + return + batches = len(task.losses) + if batches and batches % self.log_freq == 0: + self._log_performance_metrics(task, local_variables) + + def on_phase_end( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Log metrics at the end of a phase if log_freq is None. + """ + batches = len(task.losses) + if batches: + self._log_performance_metrics(task, local_variables) + + def _log_performance_metrics( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Compute and log performance metrics. + """ + phase_type = task.phase_type + batches = len(task.losses) + + if self.start_time is None: + logging.warning("start_time not initialized") + else: + # Average batch time calculation + total_batch_time = time.time() - self.start_time + average_batch_time = total_batch_time / batches + logging.info( + "Average %s batch time (ms) for %d batches: %d" + % (phase_type, batches, 1000.0 * average_batch_time) + ) + + # Train step time breakdown + if local_variables.get("perf_stats") is None: + logging.warning('"perf_stats" not set in local_variables') + elif task.train: + logging.info( + "Train step time breakdown (rank {}):\n{}".format( + get_rank(), local_variables["perf_stats"].report_str() + ) + ) diff --git a/classy_vision/hooks/visdom_hook.py b/classy_vision/hooks/visdom_hook.py new file mode 100644 index 0000000000..0d1d6ce9a0 --- /dev/null +++ b/classy_vision/hooks/visdom_hook.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import collections +import logging +from typing import Any, Dict + +from classy_vision import tasks +from classy_vision.generic.distributed_util import is_master +from classy_vision.generic.util import flatten_dict +from classy_vision.generic.visualize import plot_learning_curves +from classy_vision.hooks.classy_hook import ClassyHook + + +try: + from visdom import Visdom + + visdom_available = True +except ImportError: + visdom_available = False + + +class VisdomHook(ClassyHook): + """Plots metrics on to `Visdom `_. + + Visdom is a flexible tool for creating, organizing, and sharing visualizations + of live, rich data. It supports Python. + + """ + + on_rendezvous = ClassyHook._noop + on_start = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_update = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__( + self, server: str, port: str, env: str = "main", title_suffix: str = "" + ) -> None: + """ + Args: + server: host name of the visdom server + port: port of visdom server, such as 8097 + env: environment of visdom + title_suffix: suffix that will be appended to the title + """ + super().__init__() + if not visdom_available: + raise RuntimeError("Visdom is not installed, cannot use VisdomHook") + + self.server: str = server + self.port: str = port + self.env: str = env + self.title_suffix: str = title_suffix + + self.metrics: Dict = {} + self.visdom: Visdom = Visdom(self.server, self.port) + + def on_phase_end( + self, task: "tasks.ClassyTask", local_variables: Dict[str, Any] + ) -> None: + """ + Plot the metrics on visdom. + """ + phase_type = task.phase_type + metrics = self.metrics + batches = len(task.losses) + + if batches == 0: + return + + # Loss for the phase + loss = sum(task.losses) / (batches * task.get_batchsize_per_replica()) + loss_key = phase_type + "_loss" + if loss_key not in metrics: + metrics[loss_key] = [] + metrics[loss_key].append(loss) + + # Optimizer LR for the phase + optimizer_lr = task.optimizer.lr + lr_key = phase_type + "_learning_rate" + if lr_key not in metrics: + metrics[lr_key] = [] + metrics[lr_key].append(optimizer_lr) + + # Calculate meters + for meter in task.meters: + if isinstance(meter.value, collections.MutableMapping): + flattened_meters_dict = flatten_dict(meter.value, prefix=meter.name) + for k, v in flattened_meters_dict.items(): + metric_key = phase_type + "_" + k + if metric_key not in metrics: + metrics[metric_key] = [] + metrics[metric_key].append(v) + else: + metric_key = phase_type + "_" + meter.name + if metric_key not in metrics: + metrics[metric_key] = [] + metrics[metric_key].append(meter.value) + + # update learning curve visualizations: + phase_type = "train" if task.train else "test" + title = "%s-%s-%d" % ( + phase_type, + task.base_model.__class__.__name__, + task.base_model.model_depth, + ) + title += self.title_suffix + + if not task.train and is_master(): + logging.info("Plotting learning curves to visdom") + plot_learning_curves( + metrics, visdom_server=self.visdom, env=self.env, win=title, title=title + ) diff --git a/classy_vision/hub/__init__.py b/classy_vision/hub/__init__.py new file mode 100644 index 0000000000..73bf112b96 --- /dev/null +++ b/classy_vision/hub/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from .classy_hub_interface import ClassyHubInterface + + +__all__ = ["ClassyHubInterface"] diff --git a/classy_vision/hub/classy_hub_interface.py b/classy_vision/hub/classy_hub_interface.py new file mode 100644 index 0000000000..173a907859 --- /dev/null +++ b/classy_vision/hub/classy_hub_interface.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Callable, Dict, Iterator, List, Optional, Union + +import torch +import torch.nn as nn +from classy_vision.dataset import ClassyDataset +from classy_vision.dataset.image_path_dataset import ImagePathDataset +from classy_vision.dataset.transforms.util import build_field_transform_default_imagenet +from classy_vision.models import ClassyModel, ClassyModelWrapper +from classy_vision.tasks import ClassyTask + + +class ClassyHubInterface: + """PyTorch Hub interface for classy vision tasks and models. + + The task is optional, but a model is guaranteed to be present. Do + not use the constructor directly, instead Use from_task() or + from_model() to instantiate the class. + + See the examples folder for an example of how to use this class + + Attributes: + task: If present, task that can be used to train the torchhub model + model: torchub model + + """ + + def __init__( + self, task: Optional[ClassyTask] = None, model: Optional[ClassyModel] = None + ) -> None: + """Constructor for ClassyHubInterface. + + Only one of task or model can be specified at construction + time. If task is specified then task.model is used to populate + the model attribute. + + Do not use the constructor directly, instead use from_task() + or from_model() to instantiate the class. + + Args: + task: task that can be used to train torchhub model, + task.model is used to populate the model attribute + model: torchhub model + """ + self.task = task + if task is None: + assert model is not None, "Need to specify a model if task is None" + self.model = model + else: + assert model is None, "Cannot pass a model if task is not None" + self.model = task.model + + @classmethod + def from_task(cls, task: ClassyTask) -> "ClassyHubInterface": + """Instantiates the ClassyHubInterface from a task. + + This function returns a hub interface based on a ClassyTask. + + Args: + task: ClassyTask that contains hub model + + """ + return cls(task=task) + + @classmethod + def from_model(cls, model: Union[nn.Module, ClassyModel]) -> "ClassyHubInterface": + """Instantiates the ClassyHubInterface from a model. + + This function returns a hub interface based on a ClassyModel + + Args: + model: torchhub model + + """ + if not isinstance(model, ClassyModel): + model = ClassyModelWrapper(model) + return cls(model=model) + + def create_image_dataset( + self, + image_paths: Union[List[str], str], + targets: Optional[List[Any]] = None, + batchsize_per_replica: int = 32, + shuffle: bool = True, + transform: Optional[Callable] = None, + num_samples: Optional[int] = None, + phase_type: str = "train", + ) -> ClassyDataset: + """Create a ClassyDataset which reads images from image_paths. + + Args: + image_paths: Can be + - A single directory location, in which case the data is expected to be + arranged in a format similar to + `:class:torchvision.datasets.ImageFolder`. The targets will + be inferred from the directory structure. + - A list of paths, in which case the list will contain the paths + to all the images. In this situation, the targets can be specified + by using the targets argument. + targets: A list containing the target classes for each image + batchsize_per_replica: Minibatch size per replica (i.e. samples per GPU) + shuffle: If true, data is shuffled between epochs + transform: Transform to apply to sample. If left as None, the dataset's + phase_type is used to determine the transform to apply. The transform + for the phase_type is searched for in self.task, falling back to + imagenet transformations if it is not found there. + num_samples: If specified, limits the number of samples returned by + the dataset + phase_type: String specifying the phase_type, e.g. "train" or "test" + """ + if transform is None: + if self.task is not None and phase_type in self.task.datasets: + # use the transform from the dataset for the phase_type + dataset = self.task.datasets[phase_type] + transform = dataset.transform + assert transform is not None, "Cannot infer transform from the task" + else: + transform = build_field_transform_default_imagenet( + config=None, split=phase_type + ) + return ImagePathDataset( + batchsize_per_replica, + shuffle, + transform, + num_samples, + image_paths, + targets=targets, + split=phase_type, + ) + + @staticmethod + def get_data_iterator(dataset: ClassyDataset) -> Iterator[Any]: + """Returns an iterator that can be used to retrieve training / testing samples. + + Args: + dataset: Dataset to iterate over + """ + return iter(dataset.iterator()) + + def train(self) -> None: + """Sets the model to train mode and enables torch gradient calculation + + """ + torch.autograd.set_grad_enabled(True) + self.model.train() + + def eval(self) -> None: + """Sets the model to eval mode and disables torch gradient calculation + + """ + torch.autograd.set_grad_enabled(False) + self.model.eval() + + def predict(self, sample): + """Returns the model's prediction for a sample. + + Args: + sample: Must contain "input" key, model calculates prediction over input. + """ + output = self.model(sample["input"]) + # squeeze the output in case the batch size is 1 + return output.squeeze() + + def extract_features(self, sample): + """Calculates feature embeddings of sample. + + Args: + sample: Must contain "input" key, model calculates prediction over input. + """ + output = self.model.extract_features(sample["input"]) + # squeeze the output in case the batch size is 1 + return output.squeeze() diff --git a/classy_vision/hydra/args.yaml b/classy_vision/hydra/args.yaml new file mode 100644 index 0000000000..9ab4d8e78d --- /dev/null +++ b/classy_vision/hydra/args.yaml @@ -0,0 +1,21 @@ +debug: False +device: gpu +checkpoint_folder: "" +checkpoint_period: 1 +log_freq: 5 +num_workers: 4 +pretrained_checkpoint_folder: "" +profiler: False +skip_tensorboard: False +show_progress: False +test_only: False +visdom_port: 8097 +visdom_server: "" +video_backend: pyav +defaults: + - task: classification_task + - loss: cross_entropy + - dataset: synthetic_image + - model: resnet_50 + - optimizer: sgd + - param_scheduler: step diff --git a/classy_vision/hydra/config/resnet50_synthetic.yaml b/classy_vision/hydra/config/resnet50_synthetic.yaml new file mode 100644 index 0000000000..ce79b17c8a --- /dev/null +++ b/classy_vision/hydra/config/resnet50_synthetic.yaml @@ -0,0 +1,63 @@ +config: + name: classification_task + num_epochs: 2 + loss: + name: CrossEntropyLoss + dataset: + train: + name: synthetic_image + split: train + crop_size: 224 + class_ratio: 0.5 + num_samples: 2000 + seed: 0 + batchsize_per_replica: 32 + use_shuffle: true + transforms: + - name: apply_transform_to_key + transforms: + - name: ToTensor + - name: Normalize + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + key: input + + test: + name: synthetic_image + split: val + crop_size: 224 + class_ratio: 0.5 + num_samples: 2000 + seed: 1 + batchsize_per_replica: 32 + use_shuffle: false + transforms: + - name: apply_transform_to_key + transforms: + - name: ToTensor + - name: Normalize + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + key: input + + meters: + accuracy: + topk: [1, 5] + model: + name: resnet + num_blocks: [3, 4, 6, 3] + small_input: false + zero_init_bn_residuals: true + heads: + - name: fully_connected + unique_id: default_head + num_classes: 1000 + fork_block: block3-2 + in_plane: 2048 + optimizer: + name: sgd + lr: + name: step + values: [0.1, 0.01] + weight_decay: 1e-4 + momentum: 0.9 diff --git a/classy_vision/hydra/dataset/imagenet.yaml b/classy_vision/hydra/dataset/imagenet.yaml new file mode 100644 index 0000000000..ee0fb1771f --- /dev/null +++ b/classy_vision/hydra/dataset/imagenet.yaml @@ -0,0 +1,12 @@ +config: + dataset: + train: + name: imagenet + split: train + batchsize_per_replica: 32 + use_shuffle: true + test: + name: imagenet + split: val + batchsize_per_replica: 32 + use_shuffle: false diff --git a/classy_vision/hydra/dataset/synthetic_image.yaml b/classy_vision/hydra/dataset/synthetic_image.yaml new file mode 100644 index 0000000000..c730e05427 --- /dev/null +++ b/classy_vision/hydra/dataset/synthetic_image.yaml @@ -0,0 +1,36 @@ +config: + dataset: + train: + name: synthetic_image + split: train + crop_size: 224 + class_ratio: 0.5 + num_samples: 2000 + seed: 0 + batchsize_per_replica: 32 + use_shuffle: true + transforms: + - name: apply_transform_to_key + transforms: + - name: ToTensor + - name: Normalize + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + key: input + test: + name: synthetic_image + split: val + crop_size: 224 + class_ratio: 0.5 + num_samples: 2000 + seed: 1 + batchsize_per_replica: 32 + use_shuffle: false + transforms: + - name: apply_transform_to_key + transforms: + - name: ToTensor + - name: Normalize + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + key: input diff --git a/classy_vision/hydra/loss/cross_entropy.yaml b/classy_vision/hydra/loss/cross_entropy.yaml new file mode 100644 index 0000000000..5327045a78 --- /dev/null +++ b/classy_vision/hydra/loss/cross_entropy.yaml @@ -0,0 +1,3 @@ +config: + loss: + name: CrossEntropyLoss diff --git a/classy_vision/hydra/loss/label_smoothing_cross_entropy.yaml b/classy_vision/hydra/loss/label_smoothing_cross_entropy.yaml new file mode 100644 index 0000000000..85729d505f --- /dev/null +++ b/classy_vision/hydra/loss/label_smoothing_cross_entropy.yaml @@ -0,0 +1,4 @@ +config: + loss: + name: label_smoothing_cross_entropy + smoothing_param: 0.1 diff --git a/classy_vision/hydra/meters/accuracy.yaml b/classy_vision/hydra/meters/accuracy.yaml new file mode 100644 index 0000000000..34315653cd --- /dev/null +++ b/classy_vision/hydra/meters/accuracy.yaml @@ -0,0 +1,4 @@ +config: + meters: + accuracy: + topk: [1, 5] diff --git a/classy_vision/hydra/model/resnet_50.yaml b/classy_vision/hydra/model/resnet_50.yaml new file mode 100644 index 0000000000..4fc4255f3b --- /dev/null +++ b/classy_vision/hydra/model/resnet_50.yaml @@ -0,0 +1,12 @@ +config: + model: + name: resnet + num_blocks: [3, 4, 6, 3] + small_input: false + zero_init_bn_residuals: true + heads: + - name: fully_connected + unique_id: default_head + num_classes: 1000 + fork_block: block3-2 + in_plane: 2048 diff --git a/classy_vision/hydra/optimizer/sgd.yaml b/classy_vision/hydra/optimizer/sgd.yaml new file mode 100644 index 0000000000..cfa1dc51da --- /dev/null +++ b/classy_vision/hydra/optimizer/sgd.yaml @@ -0,0 +1,5 @@ +config: + optimizer: + name: sgd + weight_decay: 1e-4 + momentum: 0.9 diff --git a/classy_vision/hydra/param_scheduler/step.yaml b/classy_vision/hydra/param_scheduler/step.yaml new file mode 100644 index 0000000000..55a977a025 --- /dev/null +++ b/classy_vision/hydra/param_scheduler/step.yaml @@ -0,0 +1,5 @@ +config: + optimizer: + lr: + name: step + values: [0.1, 0.01] diff --git a/classy_vision/hydra/task/classification_task.yaml b/classy_vision/hydra/task/classification_task.yaml new file mode 100644 index 0000000000..e5da69c789 --- /dev/null +++ b/classy_vision/hydra/task/classification_task.yaml @@ -0,0 +1,3 @@ +config: + name: classification_task + num_epochs: 2 diff --git a/classy_vision/losses/__init__.py b/classy_vision/losses/__init__.py new file mode 100644 index 0000000000..82f7b40d82 --- /dev/null +++ b/classy_vision/losses/__init__.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +from pathlib import Path + +import torch +import torch.nn.modules.loss as torch_losses +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_loss import ClassyLoss + + +FILE_ROOT = Path(__file__).parent + + +LOSS_REGISTRY = {} +LOSS_CLASS_NAMES = set() + + +def build_loss(config): + """Builds a ClassyLoss from a config. + + This assumes a 'name' key in the config which is used to determine what + model class to instantiate. For instance, a config `{"name": "my_loss", + "foo": "bar"}` will find a class that was registered as "my_loss" + (see :func:`register_loss`) and call .from_config on it. + + In addition to losses registered with :func:`register_loss`, we also + support instantiating losses available in the `torch.nn.modules.loss `_ + module. Any keys in the config will get expanded to parameters of the loss + constructor. For instance, the following call will instantiate a + `torch.nn.modules.CrossEntropyLoss `_: + + .. code-block:: python + + build_loss({"name": "CrossEntropyLoss", "reduction": "sum"}) + """ + + assert "name" in config, f"name not provided for loss: {config}" + name = config["name"] + if name in LOSS_REGISTRY: + return LOSS_REGISTRY[name].from_config(config) + + # the name should be available in torch.nn.modules.loss + assert hasattr(torch_losses, name), ( + f"{name} isn't a registered loss" + ", nor is it available in torch.nn.modules.loss" + ) + args = copy.deepcopy(config) + del args["name"] + if "weight" in args: + # if we are passing weights, we need to change the weights from a list + # to a tensor + args["weight"] = torch.tensor(args["weight"], dtype=torch.float) + return getattr(torch_losses, name)(**args) + + +def register_loss(name): + """Registers a ClassyLoss subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyLoss from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyLoss subclass, like this: + + .. code-block:: python + + @register_loss("my_loss") + class MyLoss(ClassyLoss): + ... + + To instantiate a loss from a configuration file, see + :func:`build_loss`.""" + + def register_loss_cls(cls): + if name in LOSS_REGISTRY: + raise ValueError("Cannot register duplicate optimizer ({})".format(name)) + if not issubclass(cls, ClassyLoss): + raise ValueError( + "Loss ({}: {}) must extend ClassyLoss".format(name, cls.__name__) + ) + LOSS_REGISTRY[name] = cls + LOSS_CLASS_NAMES.add(cls.__name__) + return cls + + return register_loss_cls + + +# automatically import any Python files in the losses/ directory +import_all_modules(FILE_ROOT, "classy_vision.losses") + + +from .barron_loss import BarronLoss # isort:skip +from .label_smoothing_loss import LabelSmoothingCrossEntropyLoss # isort:skip +from .multi_output_sum_loss import MultiOutputSumLoss # isort:skip +from .soft_target_cross_entropy_loss import SoftTargetCrossEntropyLoss # isort:skip +from .sum_arbitrary_loss import SumArbitraryLoss # isort:skip + + +__all__ = [ + "BarronLoss", + "ClassyLoss", + "LabelSmoothingCrossEntropyLoss", + "MultiOutputSumLoss", + "SoftTargetCrossEntropyLoss", + "SumArbitraryLoss", + "build_loss", + "register_loss", +] diff --git a/classy_vision/losses/barron_loss.py b/classy_vision/losses/barron_loss.py new file mode 100644 index 0000000000..3a8f1f823a --- /dev/null +++ b/classy_vision/losses/barron_loss.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch + +from . import ClassyLoss, register_loss + + +@register_loss("barron") +class BarronLoss(ClassyLoss): + """ + This implements the `Barron loss `_. + """ + + def __init__(self, alpha, size_average, c): + super(BarronLoss, self).__init__() + + self.size_average = size_average + self.alpha = alpha + self.c = c + self.z = max(1.0, 2.0 - self.alpha) + + # define all three losses: + def _forward_zero(diff): + out = diff.div(self.c).pow(2.0).mul(0.5).add(1.0).log() + return out + + def _forward_inf(diff): + out = 1.0 - diff.div(self.c).pow(2.0).mul(-0.5).exp() + return out + + def _forward(diff): + out = diff.div(self.c).pow(2.0).div(self.z).add(1.0).pow(self.alpha / 2.0) + out.add_(-1.0).mul_(self.z / self.alpha) + return out + + # set the correct loss: + if self.alpha == 0.0: + self._forward = _forward_zero + elif self.alpha == -float("inf") or self.alpha == float("inf"): + self._forward = _forward_inf + else: + self._forward = _forward + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "BarronLoss": + """Instantiates a BarronLoss from a configuration. + + Args: + config: A configuration for a BarronLoss. + See :func:`__init__` for parameters expected in the config. + + Returns: + A BarronLoss instance. + """ + # Infinity is a valid alpha value but is frequently a string + config["alpha"] = float(config["alpha"]) + # assertions: + assert type(config["size_average"]) == bool + assert type(config["alpha"]) == float + assert type(config["c"]) == float and config["c"] > 0.0 + + return cls( + alpha=config["alpha"], size_average=config["size_average"], c=config["c"] + ) + + def forward(self, prediction, target): + diff = torch.add(prediction, -target) + loss = self._forward(diff) + loss = loss.sum(0, keepdim=True) + if self.size_average: + loss.div_(prediction.size(0)) + return loss diff --git a/classy_vision/losses/classy_loss.py b/classy_vision/losses/classy_loss.py new file mode 100644 index 0000000000..94f9a26b21 --- /dev/null +++ b/classy_vision/losses/classy_loss.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch.nn as nn + + +class ClassyLoss(nn.Module): + """ + Base class to calculate the loss during training. + + This implementation of :class:`torch.nn.Module` allows building + the loss object from a configuration file. + """ + + def __init__(self): + """ + Constructor for ClassyLoss. + """ + super(ClassyLoss, self).__init__() + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyLoss": + """Instantiates a ClassyLoss from a configuration. + + Args: + config: A configuration for a ClassyLoss. + + Returns: + A ClassyLoss instance. + """ + raise NotImplementedError() + + def forward(self, output, target): + """ + Compute the loss for the provided sample. + + Refer to :class:`torch.nn.Module` for more details. + """ + raise NotImplementedError diff --git a/classy_vision/losses/label_smoothing_loss.py b/classy_vision/losses/label_smoothing_loss.py new file mode 100644 index 0000000000..c6a6349d6b --- /dev/null +++ b/classy_vision/losses/label_smoothing_loss.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import numpy as np +from classy_vision.generic.util import convert_to_one_hot +from classy_vision.losses import ClassyLoss, register_loss +from classy_vision.losses.soft_target_cross_entropy_loss import ( + SoftTargetCrossEntropyLoss, +) + + +@register_loss("label_smoothing_cross_entropy") +class LabelSmoothingCrossEntropyLoss(ClassyLoss): + def __init__(self, ignore_index, reduction, smoothing_param): + """Intializer for the label smoothed cross entropy loss. + This decreases gap between output scores and encourages generalization. + Targets provided to forward can be one-hot vectors (NxC) or class indices(Nx1) + + Config params: + 'weight': weight of sample (not yet implemented), + 'ignore_index': sample should be ignored for loss (optional), + 'smoothing_param': value to be added to each target entry + """ + super().__init__() + self._ignore_index = ignore_index + self._reduction = reduction + self._smoothing_param = smoothing_param + self.loss_function = SoftTargetCrossEntropyLoss( + self._ignore_index, self._reduction, None + ) + self._eps = np.finfo(np.float32).eps + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "LabelSmoothingCrossEntropyLoss": + """Instantiates a LabelSmoothingCrossEntropyLoss from a configuration. + + Args: + config: A configuration for a LabelSmoothingCrossEntropyLoss. + See :func:`__init__` for parameters expected in the config. + + Returns: + A LabelSmoothingCrossEntropyLoss instance. + """ + + assert "weight" not in config, '"weight" not implemented' + assert ( + "smoothing_param" in config + ), "Label Smoothing needs a smoothing parameter" + return cls( + ignore_index=config.get("ignore_index", -100), + reduction=config.get("reduction", "mean"), + smoothing_param=config.get("smoothing_param"), + ) + + def compute_valid_targets(self, target, classes): + + """ + This function takes one-hot or index target vectors and computes valid one-hot + target vectors, based on ignore index value + """ + target_shape_list = list(target.size()) + + valid_mask = target != self._ignore_index + valid_targets = target.float() * valid_mask.float() + + # check if targets are inputted as class integers + if len(target_shape_list) == 1 or ( + len(target_shape_list) == 2 and target_shape_list[1] == 1 + ): + + valid_targets = convert_to_one_hot(valid_targets.view(-1, 1), classes) + valid_targets = valid_targets.float() * valid_mask.view(-1, 1).float() + + return valid_targets + + def smooth_targets(self, valid_targets, classes): + + """ + This function takes valid (No ignore values present) one-hot target vectors + and computes smoothed target vectors (normalized) according to the loss's + smoothing parameter + """ + + valid_targets /= self._eps + valid_targets.sum(dim=1, keepdim=True) + if classes > 0: + smoothed_targets = valid_targets + (self._smoothing_param / classes) + smoothed_targets /= self._eps + smoothed_targets.sum(dim=1, keepdim=True) + + return smoothed_targets + + def forward(self, output, target): + valid_targets = self.compute_valid_targets( + target=target, classes=output.shape[1] + ) + assert ( + valid_targets.shape == output.shape + ), "LabelSmoothingCrossEntropyLoss requires output and target to be same size" + smoothed_targets = self.smooth_targets( + valid_targets=valid_targets, classes=output.shape[1] + ) + return self.loss_function(output, smoothed_targets) diff --git a/classy_vision/losses/multi_output_sum_loss.py b/classy_vision/losses/multi_output_sum_loss.py new file mode 100644 index 0000000000..6d13f0c695 --- /dev/null +++ b/classy_vision/losses/multi_output_sum_loss.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch + +from . import ClassyLoss, build_loss, register_loss + + +@register_loss("multi_output_sum_loss") +class MultiOutputSumLoss(ClassyLoss): + """ + Applies the provided loss to the list of outputs (or single output) and sums + up the losses. + """ + + def __init__(self, loss) -> None: + super().__init__() + + self._loss = loss + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "MultiOutputSumLoss": + """Instantiates a MultiOutputSumLoss from a configuration. + + Args: + config: A configuration for a MultiOutpuSumLoss. + See :func:`__init__` for parameters expected in the config. + + Returns: + A MultiOutputSumLoss instance. + """ + assert ( + type(config["loss"]) == dict + ), "loss must be a dict containing a configuration for a registered loss" + return cls(loss=build_loss(config["loss"])) + + def forward(self, output, target): + if torch.is_tensor(output): + output = [output] + + loss = 0 + for pred in output: + loss += self._loss(pred, target) + + return loss diff --git a/classy_vision/losses/soft_target_cross_entropy_loss.py b/classy_vision/losses/soft_target_cross_entropy_loss.py new file mode 100644 index 0000000000..a8d67e7a8a --- /dev/null +++ b/classy_vision/losses/soft_target_cross_entropy_loss.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +from typing import Any, Dict + +import numpy as np +import torch +import torch.nn.functional as F +from classy_vision.losses import ClassyLoss, register_loss + + +@register_loss("soft_target_cross_entropy") +class SoftTargetCrossEntropyLoss(ClassyLoss): + def __init__(self, ignore_index, reduction, normalize_targets): + """Intializer for the soft target cross-entropy loss loss. + This allows the targets for the cross entropy loss to be multilabel + + Config params: + 'weight': weight of sample (not yet implemented), + 'ignore_index': sample should be ignored for loss (optional), + 'reduction': specifies reduction to apply to the output (optional), + """ + super(SoftTargetCrossEntropyLoss, self).__init__() + self._ignore_index = ignore_index + self._reduction = reduction + assert normalize_targets in [None, "count_based"] + self._normalize_targets = normalize_targets + if self._reduction != "mean": + raise NotImplementedError( + 'reduction type "{}" not implemented'.format(self._reduction) + ) + self._eps = np.finfo(np.float32).eps + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "SoftTargetCrossEntropyLoss": + """Instantiates a SoftTargetCrossEntropyLoss from a configuration. + + Args: + config: A configuration for a SoftTargetCrossEntropyLoss. + See :func:`__init__` for parameters expected in the config. + + Returns: + A SoftTargetCrossEntropyLoss instance. + """ + + if "weight" in config: + raise NotImplementedError('"weight" not implemented') + return cls( + ignore_index=config.get("ignore_index", -100), + reduction=config.get("reduction", "mean"), + normalize_targets=config.get("normalize_targets", "count_based"), + ) + + def forward(self, output, target): + """for N examples and C classes + - output: N x C these are raw outputs (without softmax/sigmoid) + - target: N x C corresponding targets + + Target elements set to ignore_index contribute 0 loss. + + Samples where all entries are ignore_index do not contribute to the loss + reduction. + """ + assert ( + output.shape == target.shape + ), "SoftTargetCrossEntropyLoss requires output and target to be same" + valid_mask = target != self._ignore_index + valid_targets = target.float() * valid_mask.float() + if self._normalize_targets == "count_based": + valid_targets /= self._eps + valid_targets.sum(dim=1, keepdim=True) + per_sample_per_target_loss = -valid_targets * F.log_softmax(output, -1) + # perform reduction + if self._reduction == "mean": + per_sample_loss = torch.sum(per_sample_per_target_loss, -1) + # normalize based on the number of samples with > 0 non-ignored targets + loss = per_sample_loss.sum() / torch.sum( + (torch.sum(valid_mask, -1) > 0) + ).clamp(min=1) + return loss diff --git a/classy_vision/losses/sum_arbitrary_loss.py b/classy_vision/losses/sum_arbitrary_loss.py new file mode 100644 index 0000000000..5dfddb664c --- /dev/null +++ b/classy_vision/losses/sum_arbitrary_loss.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict, List, Optional + +import torch +from torch import Tensor + +from . import ClassyLoss, build_loss, register_loss + + +@register_loss("sum_arbitrary") +class SumArbitraryLoss(ClassyLoss): + """ + Sums a collection of (weighted) torch.nn losses. + + NOTE: this applies all the losses to the same output and does not support + taking a list of outputs as input. + """ + + def __init__(self, losses: List[ClassyLoss], weights: Optional[Tensor] = None) -> None: + super().__init__() + if weights is None: + weights = torch.ones((len(losses))) + + self.losses = losses + self.weights = weights + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "SumArbitraryLoss": + """Instantiates a SumArbitraryLoss from a configuration. + + Args: + config: A configuration for a SumArbitraryLoss. + See :func:`__init__` for parameters expected in the config. + + Returns: + A SumArbitraryLoss instance. + """ + assert ( + type(config["losses"]) == list and len(config["losses"]) > 0 + ), "losses must be a list of registered losses with length > 0" + assert type(config["weights"]) == list and len(config["weights"]) == len( + config["losses"] + ), "weights must be None or a list and have same length as losses" + + loss_modules = [] + for loss_config in config["losses"]: + loss_modules.append(build_loss(loss_config)) + + assert all( + isinstance(loss_module, ClassyLoss) for loss_module in loss_modules + ), "All losses must be registered, valid ClassyLosses" + + return cls(losses=loss_modules, weights=config.get("weights", None)) + + def forward(self, prediction, target): + for idx, loss in enumerate(self.losses): + current_loss = loss(prediction, target) + if idx == 0: + total_loss = current_loss + else: + total_loss = total_loss.add(self.weights[idx], current_loss) + return total_loss diff --git a/classy_vision/meters/__init__.py b/classy_vision/meters/__init__.py new file mode 100644 index 0000000000..3a1e010937 --- /dev/null +++ b/classy_vision/meters/__init__.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_meter import ClassyMeter + + +FILE_ROOT = Path(__file__).parent + + +METER_REGISTRY = {} + + +def build_meter(config): + """Builds a :class:`ClassyMeter` from a config. + + This assumes a 'name' key in the config which is used to determine what + meter class to instantiate. For instance, a config `{"name": "my_meter", + "foo": "bar"}` will find a class that was registered as "my_meter" (see + :func:`register_meter`) and call .from_config on it.""" + return METER_REGISTRY[config["name"]].from_config(config) + + +def build_meters(config): + configs = [{"name": name, **args} for name, args in config.items()] + return [build_meter(config) for config in configs] + + +def register_meter(name): + """Registers a :class:`ClassyMeter` subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyMeter from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyMeter subclass, like this: + + .. code-block:: python + + @register_meter('accuracy') + class AccuracyMeter(ClassyMeter): + ... + + To instantiate a meter from a configuration file, see + :func:`build_meter`.""" + + def register_meter_cls(cls): + if name in METER_REGISTRY: + raise ValueError("Cannot register duplicate meter ({})".format(name)) + if not issubclass(cls, ClassyMeter): + raise ValueError( + "Meter ({}: {}) must extend \ + ClassyMeter".format( + name, cls.__name__ + ) + ) + METER_REGISTRY[name] = cls + return cls + + return register_meter_cls + + +# automatically import any Python files in the meters/ directory +import_all_modules(FILE_ROOT, "classy_vision.meters") + +from .accuracy_meter import AccuracyMeter # isort:skip +from .precision_meter import PrecisionAtKMeter # isort:skip +from .recall_meter import RecallAtKMeter # isort:skip +from .video_accuracy_meter import VideoAccuracyMeter # isort:skip + +__all__ = [ + "AccuracyMeter", + "ClassyMeter", + "PrecisionAtKMeter", + "RecallAtKMeter", + "VideoAccuracyMeter", + "build_meter", + "build_meters", + "register_meter", +] diff --git a/classy_vision/meters/accuracy_meter.py b/classy_vision/meters/accuracy_meter.py new file mode 100644 index 0000000000..5615c9ec61 --- /dev/null +++ b/classy_vision/meters/accuracy_meter.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch +from classy_vision.generic.distributed_util import all_reduce_sum +from classy_vision.generic.util import is_pos_int +from classy_vision.meters import ClassyMeter + +from . import register_meter + + +@register_meter("accuracy") +class AccuracyMeter(ClassyMeter): + """Meter to calculate top-k accuracy for single label + image classification task. + """ + + def __init__(self, topk): + """ + args: + topk: list of int `k` values. + """ + assert isinstance(topk, list), "topk must be a list" + assert len(topk) > 0, "topk list should have at least one element" + assert [is_pos_int(x) for x in topk], "each value in topk must be >= 1" + + self._topk = topk + + # _total_* variables store running, in-sync totals for the + # metrics. These should not be communicated / summed. + self._total_correct_predictions_k = None + self._total_sample_count = None + + # _curr_* variables store counts since the last sync. Only + # these should be summed across workers and they are reset + # after each communication + self._curr_correct_predictions_k = None + self._curr_sample_count = None + + # Initialize all values properly + self.reset() + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "AccuracyMeter": + """Instantiates a AccuracyMeter from a configuration. + + Args: + config: A configuration for a AccuracyMeter. + See :func:`__init__` for parameters expected in the config. + + Returns: + A AccuracyMeter instance. + """ + return cls(topk=config["topk"]) + + @property + def name(self): + return "accuracy" + + def sync_state(self): + # Communications + self._curr_correct_predictions_k = all_reduce_sum( + self._curr_correct_predictions_k + ) + self._curr_sample_count = all_reduce_sum(self._curr_sample_count) + + # Store results + self._total_correct_predictions_k += self._curr_correct_predictions_k + self._total_sample_count += self._curr_sample_count + + # Reset values until next sync + self._curr_correct_predictions_k.zero_() + self._curr_sample_count.zero_() + + @property + def value(self): + # Return value based on the local state of meter which + # includes the local sample count since last sync and the total global sample + # count obtained at the last sync + correct_predictions = { + k: self._curr_correct_predictions_k[i] + + self._total_correct_predictions_k[i] + for i, k in enumerate(self._topk) + } + sample_count = self._total_sample_count + self._curr_sample_count + return { + "top_{}".format(k): (correct_predictions[k] / sample_count).item() + if sample_count + else 0.0 + for k in self._topk + } + + def get_classy_state(self): + """Contains the states of the meter. + """ + return { + "name": self.name, + "top_k": self._topk, + "total_correct_predictions": self._total_correct_predictions_k.clone(), + "total_sample_count": self._total_sample_count.clone(), + "curr_sample_count": self._curr_sample_count.clone(), + "curr_correct_predictions_k": self._curr_correct_predictions_k.clone(), + } + + def set_classy_state(self, state): + assert ( + self.name == state["name"] + ), "State name {state_name} does not match meter name {obj_name}".format( + state_name=state["name"], obj_name=self.name + ) + assert ( + self._topk == state["top_k"] + ), "top-k of state {state_k} does not match object's top-k {obj_k}".format( + state_k=state["top_k"], obj_k=self._topk + ) + + # Restore the state -- correct_predictions and sample_count. + self.reset() + self._total_correct_predictions_k = state["total_correct_predictions"].clone() + self._total_sample_count = state["total_sample_count"].clone() + self._curr_correct_predictions_k = state["curr_correct_predictions_k"].clone() + self._curr_sample_count = state["curr_sample_count"].clone() + + def __repr__(self): + return repr({"name": self.name, "value": self.value}) + + def update(self, model_output, target, **kwargs): + """ + args: + model_output: tensor of shape (B, C) where each value is + either logit or class probability. + target: tensor of shape (B). + Note: For binary classification, C=2. + """ + # Due to dummy samples, in some corner cases, the whole batch could + # be dummy samples, in that case we want to not update meters on that + # process + if model_output.shape[0] == 0: + return + _, pred = model_output.topk(max(self._topk), dim=1, largest=True, sorted=True) + + correct_predictions = pred.eq(target.unsqueeze(1).expand_as(pred)) + for i, k in enumerate(self._topk): + self._curr_correct_predictions_k[i] += ( + correct_predictions[:, :k].float().sum().item() + ) + self._curr_sample_count += model_output.shape[0] + + def reset(self): + self._total_correct_predictions_k = torch.zeros(len(self._topk)) + self._total_sample_count = torch.zeros(1) + self._curr_correct_predictions_k = torch.zeros(len(self._topk)) + self._curr_sample_count = torch.zeros(1) + + def validate(self, model_output_shape, target_shape): + assert ( + len(model_output_shape) == 2 + ), "model_output_shape must be (B, C) \ + Found shape {}".format( + model_output_shape + ) + assert ( + len(target_shape) == 1 + ), "target_shape must be (B) \ + Found shape {}".format( + target_shape + ) + assert ( + max(self._topk) < model_output_shape[1] + ), "k in top_k, for \ + accuracy_meter cannot be larger than num_classes: \ + {}".format( + model_output_shape[1] + ) diff --git a/classy_vision/meters/classy_meter.py b/classy_vision/meters/classy_meter.py new file mode 100644 index 0000000000..324a566083 --- /dev/null +++ b/classy_vision/meters/classy_meter.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict, Tuple + +import torch + + +class ClassyMeter: + """ + Base class to measure various metrics during training and testing phases. + + This can include meters like Accuracy, Precision and Recall, etc. + """ + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyMeter": + """Instantiates a ClassyMeter using a configuration. + + Args: + config: A configuration for a ClassyMeter. + + Returns: + A ClassyMeter instance. + """ + raise NotImplementedError + + @property + def name(self) -> str: + """The name of the meter.""" + raise NotImplementedError + + @property + def value(self) -> Any: + """ + Value of meter based on local state, can be any python object. + + Note: + If there are multiple training processes then this + represents the local state of the meter. If :func:`sync_state` is + implemented, then value will return the global state since the + last sync PLUS any local unsynced updates that have occurred + in the local process. + """ + raise NotImplementedError + + def sync_state(self) -> None: + """ + Syncs state with all other meters in distributed training. + + If not provided by child class this does nothing by default + and meter only provides the local process stats. If + implemented then the meter provides the global stats at last + sync + any local updates since the last sync. + + Warning: + Calls to sync_state could involve communications via + :mod:`torch.distributed` which can result in a loss of performance or + deadlocks if not coordinated among threads. + """ + pass + + def reset(self): + """ + Resets any internal meter state. + + Should normally be called at the end of a phase. + """ + raise NotImplementedError + + def update( + self, model_output: torch.Tensor, target: torch.Tensor, **kwargs + ) -> None: + """ + Updates any internal state of meter. + + Should be called after each batch processing of each phase. + + Args: + model_output: Output of a :class:`ClassyModel`. + target: Target provided by a dataloader from :class:`ClassyDataset`. + """ + raise NotImplementedError + + def validate(self, model_output_shape: Tuple, target_shape: Tuple) -> None: + """ + Validate the meter. + + Checks if the meter can be calculated on the given ``model_output_shape`` + and ``target_shape``. + """ + raise NotImplementedError + + def get_classy_state(self) -> Dict[str, Any]: + """Get the state of the ClassyMeter. + + The returned state is used for checkpointing. + + Returns: + A state dictionary containing the state of the meter. + """ + raise NotImplementedError + + def set_classy_state(self, state: Dict[str, Any]) -> None: + """Set the state of the ClassyMeter. + + Args: + state_dict: The state dictionary. Must be the output of a call to + :func:`get_classy_state`. + + This is used to load the state of the meter from a checkpoint. + """ + raise NotImplementedError diff --git a/classy_vision/meters/precision_meter.py b/classy_vision/meters/precision_meter.py new file mode 100644 index 0000000000..e7b2ca3cf7 --- /dev/null +++ b/classy_vision/meters/precision_meter.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch +from classy_vision.generic.distributed_util import all_reduce_sum +from classy_vision.generic.util import convert_to_one_hot, is_pos_int +from classy_vision.meters import ClassyMeter + +from . import register_meter + + +@register_meter("precision_at_k") +class PrecisionAtKMeter(ClassyMeter): + """ + Meter to calculate top-k precision for single-label or multi-label + image classification task. Note, ties are resolved randomly. + """ + + def __init__(self, topk, target_is_one_hot=True, num_classes=-1): + """ + args: + topk: list of int `k` values. + target_is_one_hot: boolean, if class labels are one-hot encoded. + num_classes: int, number of classes. + """ + assert isinstance(topk, list), "topk must be a list" + assert len(topk) > 0, "topk list should have at least one element" + assert [is_pos_int(x) for x in topk], "each value in topk must be >= 1" + if not target_is_one_hot: + assert ( + type(num_classes) == int and num_classes > 0 + ), "num_classes must be positive integer" + + self._topk = topk + self._target_is_one_hot = target_is_one_hot + self._num_classes = num_classes + + # _total_* variables store running, in-sync totals for the + # metrics. These should not be communicated / summed. + self._total_correct_predictions_k = None + self._total_sample_count = None + + # _curr_* variables store counts since the last sync. Only + # these should be summed across workers and they are reset + # after each communication + self._curr_correct_predictions_k = None + self._curr_sample_count = None + + # Initialize all values properly + self.reset() + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "PrecisionAtKMeter": + """Instantiates a PrecisionAtKMeter from a configuration. + + Args: + config: A configuration for a PrecisionAtKMeter. + See :func:`__init__` for parameters expected in the config. + + Returns: + A PrecisionAtKMeter instance. + """ + return cls( + topk=config["topk"], + target_is_one_hot=config.get("target_is_one_hot", True), + num_classes=config.get("num_classes", -1), + ) + + @property + def name(self): + return "precision_at_k" + + def sync_state(self): + # Communications + self._curr_correct_predictions_k = all_reduce_sum( + self._curr_correct_predictions_k + ) + self._curr_sample_count = all_reduce_sum(self._curr_sample_count) + + # Store results + self._total_correct_predictions_k += self._curr_correct_predictions_k + self._total_sample_count += self._curr_sample_count + + # Reset values until next sync + self._curr_correct_predictions_k.zero_() + self._curr_sample_count.zero_() + + @property + def value(self): + # Return value based on the local state of meter which + # includes the local sample count since last sync and the total global sample + # count obtained at the last sync + correct_predictions = { + k: self._curr_correct_predictions_k[i] + + self._total_correct_predictions_k[i] + for i, k in enumerate(self._topk) + } + sample_count = self._total_sample_count + self._curr_sample_count + return { + "top_{}".format(k): (correct_predictions[k] / (k * sample_count)).item() + if sample_count + else 0.0 + for k in self._topk + } + + def get_classy_state(self): + """Contains the states of the meter. + """ + return { + "name": self.name, + "top_k": self._topk, + "total_correct_predictions": self._total_correct_predictions_k.clone(), + "total_sample_count": self._total_sample_count.clone(), + "curr_sample_count": self._curr_sample_count.clone(), + "curr_correct_predictions_k": self._curr_correct_predictions_k.clone(), + } + + def set_classy_state(self, state): + assert ( + self.name == state["name"] + ), "State name {state_name} does not match meter name {obj_name}".format( + state_name=state["name"], obj_name=self.name + ) + assert ( + self._topk == state["top_k"] + ), "top-k of state {state_k} does not match object's top-k {obj_k}".format( + state_k=state["top_k"], obj_k=self._topk + ) + + # Restore the state -- correct_predictions and sample_count. + self.reset() + self._total_correct_predictions_k = state["total_correct_predictions"].clone() + self._total_sample_count = state["total_sample_count"].clone() + self._curr_correct_predictions_k = state["curr_correct_predictions_k"].clone() + self._curr_sample_count = state["curr_sample_count"].clone() + + def __repr__(self): + return repr({"name": self.name, "value": self.value}) + + def update(self, model_output, target, **kwargs): + """ + args: + model_output: tensor of shape (B, C) where each value is + either logit or class probability. + target: tensor of shape (B, C), one-hot encoded + or integer encoded. + Note: For binary classification, C=2. + For integer encoded target, C=1. + """ + + if self._target_is_one_hot is False: + assert target.shape[1] == 1, "Integer encoded target must be single labeled" + target = convert_to_one_hot(target, self._num_classes) + + assert ( + torch.min(target.eq(0) + target.eq(1)) == 1 + ), "Target must be one-hot encoded vector" + + # Due to dummy samples, in some corner cases, the whole batch could + # be dummy samples, in that case we want to not update meters on that + # process + if model_output.shape[0] == 0: + return + _, pred_classes = model_output.topk( + max(self._topk), dim=1, largest=True, sorted=True + ) + pred_mask_tensor = torch.zeros(target.size()) + for i, k in enumerate(self._topk): + pred_mask_tensor.zero_() + self._curr_correct_predictions_k[i] += torch.sum( + # torch.min is used to simulate AND between binary + # tensors. If tensors are not binary, this will fail. + torch.min( + pred_mask_tensor.scatter_(1, pred_classes[:, :k], 1.0), + target.float(), + ) + ).item() + self._curr_sample_count += model_output.shape[0] + + def reset(self): + self._total_correct_predictions_k = torch.zeros(len(self._topk)) + self._total_sample_count = torch.zeros(1) + self._curr_correct_predictions_k = torch.zeros(len(self._topk)) + self._curr_sample_count = torch.zeros(1) + + def validate(self, model_output_shape, target_shape): + assert ( + len(model_output_shape) == 2 + ), "model_output_shape must be (B, C) \ + Found shape {}".format( + model_output_shape + ) + assert ( + len(target_shape) == 2 + ), "target_shape must be (B, C) \ + Found shape {}".format( + target_shape + ) + assert ( + max(self._topk) < model_output_shape[1] + ), "k in top_k, for \ + precision_meter cannot be larger than num_classes: \ + {}".format( + model_output_shape[1] + ) diff --git a/classy_vision/meters/recall_meter.py b/classy_vision/meters/recall_meter.py new file mode 100644 index 0000000000..35ebc82559 --- /dev/null +++ b/classy_vision/meters/recall_meter.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch +from classy_vision.generic.distributed_util import all_reduce_sum +from classy_vision.generic.util import convert_to_one_hot, is_pos_int +from classy_vision.meters import ClassyMeter + +from . import register_meter + + +@register_meter("recall_at_k") +class RecallAtKMeter(ClassyMeter): + """Meter to calculate top-k recall for single-label or multi-label + image classification task. + """ + + def __init__(self, topk, target_is_one_hot=True, num_classes=None): + """ + args: + topk: list of int `k` values. + target_is_one_hot: boolean, if class labels are one-hot encoded. + num_classes: int, number of classes. + """ + assert isinstance(topk, list), "topk must be a list" + assert len(topk) > 0, "topk list should have at least one element" + assert [is_pos_int(x) for x in topk], "each value in topk must be >= 1" + if not target_is_one_hot: + assert ( + type(num_classes) == int and num_classes > 0 + ), "num_classes must be positive integer" + + self._topk = topk + self._target_is_one_hot = target_is_one_hot + self._num_classes = num_classes + + # _total_* variables store running, in-sync totals for the + # metrics. These should not be communicated / summed. + self._total_correct_predictions_k = None + self._total_correct_targets = None + + # _curr_* variables store counts since the last sync. Only + # these should be summed across workers and they are reset + # after each communication + self._curr_correct_predictions_k = None + self._curr_correct_targets = None + + # Initialize all values properly + self.reset() + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "RecallAtKMeter": + """Instantiates a RecallAtKMeter from a configuration. + + Args: + config: A configuration for a RecallAtKMeter. + See :func:`__init__` for parameters expected in the config. + + Returns: + A RecallAtKMeter instance. + """ + return cls( + topk=config["topk"], + target_is_one_hot=config.get("target_is_one_hot", True), + num_classes=config.get("num_classes", None), + ) + + @property + def name(self): + return "recall_at_k" + + def sync_state(self): + # Communications + self._curr_correct_predictions_k = all_reduce_sum( + self._curr_correct_predictions_k + ) + self._curr_correct_targets = all_reduce_sum(self._curr_correct_targets) + + # Store results + self._total_correct_predictions_k += self._curr_correct_predictions_k + self._total_correct_targets += self._curr_correct_targets + + # Reset values until next sync + self._curr_correct_predictions_k.zero_() + self._curr_correct_targets.zero_() + + @property + def value(self): + # Return value based on the local state of meter which + # includes the local sample count since last sync and the total global sample + # count obtained at the last sync + correct_predictions = { + k: self._curr_correct_predictions_k[i] + + self._total_correct_predictions_k[i] + for i, k in enumerate(self._topk) + } + correct_targets = self._total_correct_targets + self._curr_correct_targets + return { + "top_{}".format(k): ((correct_predictions[k] / correct_targets).item()) + if correct_targets + else 0.0 + for k in self._topk + } + + def get_classy_state(self): + """Contains the states of the meter. + """ + return { + "name": self.name, + "top_k": self._topk, + "total_correct_predictions": self._total_correct_predictions_k.clone(), + "total_correct_targets": self._total_correct_targets.clone(), + "curr_correct_targets": self._curr_correct_targets.clone(), + "curr_correct_predictions_k": self._curr_correct_predictions_k.clone(), + } + + def set_classy_state(self, state): + assert ( + self.name == state["name"] + ), "State name {state_name} does not match meter name {obj_name}".format( + state_name=state["name"], obj_name=self.name + ) + assert ( + self._topk == state["top_k"] + ), "top-k of state {state_k} does not match object's top-k {obj_k}".format( + state_k=state["top_k"], obj_k=self._topk + ) + + # Restore the state -- correct_predictions and correct_targets. + self.reset() + self._total_correct_predictions_k = state["total_correct_predictions"].clone() + self._total_correct_targets = state["total_correct_targets"].clone() + self._curr_correct_predictions_k = state["curr_correct_predictions_k"].clone() + self._curr_correct_targets = state["curr_correct_targets"].clone() + + def __repr__(self): + return repr({"name": self.name, "value": self.value}) + + def update(self, model_output, target, **kwargs): + """ + args: + model_output: tensor of shape (B, C) where each value is + either logit or class probability. + target: tensor of shape (B, C), one-hot encoded + or integer encoded. + + Note: + + For binary classification, C=2. For integer encoded target, C=1. + """ + + if self._target_is_one_hot is False: + assert target.shape[1] == 1, "Integer encoded target must be single labeled" + target = convert_to_one_hot(target, self._num_classes) + + assert ( + torch.min(target.eq(0) + target.eq(1)) == 1 + ), "Target must be one-hot encoded vector" + # Due to dummy samples, in some corner cases, the whole batch could + # be dummy samples, in that case we want to not update meters on that + # process + if model_output.shape[0] == 0: + return + _, pred_classes = model_output.topk( + max(self._topk), dim=1, largest=True, sorted=True + ) + pred_mask_tensor = torch.zeros(target.size()) + for i, k in enumerate(self._topk): + pred_mask_tensor.zero_() + self._curr_correct_predictions_k[i] += torch.sum( + # torch.min is used to simulate AND between binary + # tensors. If tensors are not binary, this will fail. + torch.min( + pred_mask_tensor.scatter_(1, pred_classes[:, :k], 1.0), + target.float(), + ) + ).item() + self._curr_correct_targets += target.sum().item() + + def reset(self): + self._total_correct_predictions_k = torch.zeros(len(self._topk)) + self._total_correct_targets = torch.zeros(1) + self._curr_correct_predictions_k = torch.zeros(len(self._topk)) + self._curr_correct_targets = torch.zeros(1) + + def validate(self, model_output_shape, target_shape): + assert ( + len(model_output_shape) == 2 + ), "model_output_shape must be (B, C) \ + Found shape {}".format( + model_output_shape + ) + assert ( + len(target_shape) == 2 + ), "target_shape must be (B, C) \ + Found shape {}".format( + target_shape + ) + assert ( + max(self._topk) < model_output_shape[1] + ), "k in top_k, for \ + recall_meter cannot be larger than num_classes: \ + {}".format( + model_output_shape[1] + ) diff --git a/classy_vision/meters/video_accuracy_meter.py b/classy_vision/meters/video_accuracy_meter.py new file mode 100644 index 0000000000..4852e32897 --- /dev/null +++ b/classy_vision/meters/video_accuracy_meter.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch +from classy_vision.generic.util import is_pos_int +from classy_vision.meters import ClassyMeter +from classy_vision.meters.accuracy_meter import AccuracyMeter + +from . import register_meter + + +@register_meter("video_accuracy") +class VideoAccuracyMeter(ClassyMeter): + """Meter to calculate top-k video-level accuracy for single label + video classification task. Video-level accuarcy is computed by averaging + clip-level predictions and compare the reslt with video-level groundtruth + label. + """ + + def __init__(self, topk, clips_per_video_train, clips_per_video_test): + """ + args: + topk: list of int `k` values. + clips_per_video_train: No. of clips sampled per video at train time + clips_per_video_test: No. of clips sampled per video at test time + """ + assert isinstance(topk, list), "topk must be a list" + assert len(topk) > 0, "topk list should have at least one element" + assert [is_pos_int(x) for x in topk], "each value in topk must be >= 1" + + self._clips_per_video_train = clips_per_video_train + self._clips_per_video_test = clips_per_video_test + self._accuracy_meter = AccuracyMeter(topk) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "VideoAccuracyMeter": + """Instantiates a VideoAccuracyMeter from a configuration. + + Args: + config: A configuration for a VideoAccuracyMeter. + See :func:`__init__` for parameters expected in the config. + + Returns: + A VideoAccuracyMeter instance. + """ + return cls( + topk=config["topk"], + clips_per_video_train=config.get("clips_per_video_train", 1), + clips_per_video_test=config["clips_per_video_test"], + ) + + @property + def name(self): + return "video_accuracy" + + @property + def value(self): + return self._accuracy_meter.value + + def sync_state(self): + self._accuracy_meter.sync_state() + + def get_classy_state(self): + """Contains the states of the meter. + """ + state = {} + state["accuracy_state"] = self._accuracy_meter.get_classy_state() + state["name"] = "video_accuracy" + state["clips_per_video_train"] = self._clips_per_video_train + state["clips_per_video_test"] = self._clips_per_video_test + return state + + def set_classy_state(self, state): + assert ( + "video_accuracy" == state["name"] + ), "State name {state_name} does not match meter name {obj_name}".format( + state_name=state["name"], obj_name=self.name + ) + assert ( + self._clips_per_video_train == state["clips_per_video_train"] + ), "incompatible clips_per_video_train for video accuracy" + assert ( + self._clips_per_video_test == state["clips_per_video_test"] + ), "incompatible clips_per_video_test for video accuracy" + # Restore the state -- correct_predictions and sample_count. + self.reset() + self._accuracy_meter.set_classy_state(state["accuracy_state"]) + + def __repr__(self): + return repr({"name": self.name, "value": self._accuracy_meter.value}) + + def update(self, model_output, target, is_train, **kwargs): + """ + args: + model_output: tensor of shape (B * clips_per_video, C) where each value is + either logit or class probability. + target: tensor of shape (B * clips_per_video). + is_train if True, it is training stage when meter is updated + + Note: For binary classification, C=2. + """ + num_clips = len(model_output) + if num_clips == 0: + # It is possible that a minibatch entirely contains dummy samples + # when dataset is sharded. In such case, the effective target and output + # can be empty, and we immediately return + return + + clips_per_video = ( + self._clips_per_video_train if is_train else self._clips_per_video_test + ) + assert num_clips % clips_per_video == 0, ( + "For video model testing, batch size must be a multplier of No. of " + "clips per video" + ) + num_videos = num_clips // clips_per_video + for i in range(num_videos): + clip_labels = target[i * clips_per_video : (i + 1) * clips_per_video] + assert ( + len(torch.unique(clip_labels)) == 1 + ), "all clips from the same video should have same label" + + video_target = target[::clips_per_video] + video_model_output = torch.mean( + torch.reshape(model_output, (num_videos, clips_per_video, -1)), 1 + ) + self._accuracy_meter.update(video_model_output, video_target) + + def reset(self): + self._accuracy_meter.reset() + + def validate(self, model_output_shape, target_shape): + self._accuracy_meter.validate(model_output_shape, target_shape) diff --git a/classy_vision/models/__init__.py b/classy_vision/models/__init__.py new file mode 100644 index 0000000000..085100b248 --- /dev/null +++ b/classy_vision/models/__init__.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +from collections import defaultdict +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules +from classy_vision.heads import build_head + +from .classy_model import ClassyModel + + +FILE_ROOT = Path(__file__).parent + + +MODEL_REGISTRY = {} +MODEL_CLASS_NAMES = set() + + +def register_model(name): + """Registers a ClassyModel subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyModel from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyModel subclass, like this: + + @register_model('resnet') + class ResidualNet(ClassyModel): + ... + + To instantiate a model from a configuration file, see + :func:`build_model`.""" + + def register_model_cls(cls): + if name in MODEL_REGISTRY: + raise ValueError("Cannot register duplicate model ({})".format(name)) + if not issubclass(cls, ClassyModel): + raise ValueError( + "Model ({}: {}) must extend ClassyModel".format(name, cls.__name__) + ) + if cls.__name__ in MODEL_CLASS_NAMES: + raise ValueError( + "Cannot register model with duplicate class name ({})".format( + cls.__name__ + ) + ) + MODEL_REGISTRY[name] = cls + MODEL_CLASS_NAMES.add(cls.__name__) + return cls + + return register_model_cls + + +def build_model(config): + """Builds a ClassyModel from a config. + + This assumes a 'name' key in the config which is used to determine what + model class to instantiate. For instance, a config `{"name": "my_model", + "foo": "bar"}` will find a class that was registered as "my_model" + (see :func:`register_model`) and call .from_config on it.""" + + assert config["name"] in MODEL_REGISTRY, "unknown model" + model = MODEL_REGISTRY[config["name"]].from_config(config) + if "heads" in config: + heads = defaultdict(dict) + for head_config in config["heads"]: + assert "fork_block" in head_config, "Expect fork_block in config" + fork_block = head_config["fork_block"] + updated_config = copy.deepcopy(head_config) + del updated_config["fork_block"] + + head = build_head(updated_config) + heads[fork_block][head.unique_id] = head + model.set_heads(heads) + return model + + +# automatically import any Python files in the models/ directory +import_all_modules(FILE_ROOT, "classy_vision.models") + +from .classy_block import ClassyBlock # isort:skip +from .classy_model import ClassyModelEvaluationMode # isort:skip +from .classy_model_wrapper import ClassyModelWrapper # isort:skip +from .densenet import DenseNet # isort:skip +from .mlp import MLP # isort:skip +from .resnet import ResNet # isort:skip +from .resnext import ResNeXt # isort:skip +from .resnext3d import ResNeXt3D # isort:skip + + +__all__ = [ + "build_model", + "register_model", + "ClassyBlock", + "ClassyModel", + "ClassyModelEvaluationMode", + "ClassyModelWrapper", + "DenseNet", + "MLP", + "ResNet", + "ResNeXt", + "ResNeXt3D", +] diff --git a/classy_vision/models/classy_block.py b/classy_vision/models/classy_block.py new file mode 100644 index 0000000000..f353728e86 --- /dev/null +++ b/classy_vision/models/classy_block.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch +import torch.nn as nn + + +class ClassyBlock(nn.Module): + """ + This is a thin wrapper for head execution, which records the output of + wrapped module for executing the heads forked from this module. + """ + + def __init__(self, name, module): + super().__init__() + self.name = name + self.output = torch.zeros(0) + self._module = module + self._should_cache_output = False + + def set_cache_output(self, should_cache_output: bool = True): + """ + Whether to cache the output of wrapped module for head execution. + """ + self._should_cache_output = should_cache_output + + def forward(self, input): + output = self._module(input) + if self._should_cache_output: + self.output = output + return output diff --git a/classy_vision/models/classy_model.py b/classy_vision/models/classy_model.py new file mode 100644 index 0000000000..61824e6504 --- /dev/null +++ b/classy_vision/models/classy_model.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +from enum import Enum +from typing import Any, Dict + +import torch +import torch.nn as nn +from classy_vision.heads.classy_head import ClassyHead + +from .classy_block import ClassyBlock + + +class ClassyModelEvaluationMode(Enum): + DEFAULT = 0 + VIDEO_CLIP_AVERAGING = 1 + + +class ClassyModel(nn.Module): + """Base class for models in classy vision. + + A model refers either to a specific architecture (e.g. ResNet50) or a + family of architectures (e.g. ResNet). Models can take arguments in the + constructor in order to configure different behavior (e.g. + hyperparameters). Classy Models must implement :method:`from_config` in + order to allow instantiation from a configuration file. Like regular + PyTorch models, Classy Models must also implement :method:`forward`, where + the bulk of the inference logic lives. + + Classy Models also have some advanced functionality for production + fine-tuning systems. For example, we allow users to train a trunk + model and then attach heads to the model via the attachable + blocks. Making your model support the trunk-heads paradigm is + completely optional. + + """ + + def __init__(self): + """Constructor for ClassyModel.""" + super().__init__() + + self._attachable_blocks = {} + self._heads = nn.ModuleDict() + self._head_outputs = {} + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyModel": + """Instantiates a ClassyModel from a configuration. + + Args: + config: A configuration for the ClassyModel. + + Returns: + A ClassyModel instance. + """ + raise NotImplementedError + + @classmethod + def from_checkpoint(cls, checkpoint): + from . import build_model + + model = build_model(checkpoint["input_args"]["config"]["model"]) + model.set_classy_state(checkpoint["classy_state_dict"]["base_model"]) + return model + + def get_classy_state(self, deep_copy=False): + """Get the state of the ClassyModel. + + The returned state is used for checkpointing. + + Args: + deep_copy: If True, creates a deep copy of the state Dict. Otherwise, the + returned Dict's state will be tied to the object's. + + Returns: + A state dictionary containing the state of the model. + """ + # If the model doesn't have head for fine-tuning, all of model's state + # live in the trunk + attached_heads = self.get_heads() + # clear heads to get trunk only states. There shouldn't be any component + # states depend on heads + self._clear_heads() + trunk_state_dict = super().state_dict() + self.set_heads(attached_heads) + + head_state_dict = {} + for block, heads in attached_heads.items(): + head_state_dict[block] = { + head_name: head.state_dict() for head_name, head in heads.items() + } + model_state_dict = { + "model": {"trunk": trunk_state_dict, "heads": head_state_dict} + } + if deep_copy: + model_state_dict = copy.deepcopy(model_state_dict) + return model_state_dict + + def load_head_states(self, state): + """Load only the state (weights) of the heads. + + For a trunk-heads model, this function allows the user to + only update the head state of the model. Useful for attaching + fine-tuned heads to a pre-trained trunk. + + Args: + state (Dict): Contains the classy model state under key "model" + + """ + for block_name, head_states in state["model"]["heads"].items(): + for head_name, head_state in head_states.items(): + self._heads[block_name][head_name].load_state_dict(head_state) + + def set_classy_state(self, state): + """Set the state of the ClassyModel. + + Args: + state_dict: The state dictionary. Must be the output of a call to + :method:`get_classy_state`. + + This is used to load the state of the model from a checkpoint. + """ + self.load_head_states(state) + + current_state = self.state_dict() + current_state.update(state["model"]["trunk"]) + super().load_state_dict(current_state) + + def forward(self, x): + """ + Perform computation of blocks in the order define in get_blocks. + """ + raise NotImplementedError + + def extract_features(self, x): + """ + Extract features from the model. + + Derived classes can implement this method to extract the features before + applying the final fc layer. + """ + return self.forward(x) + + def build_attachable_block(self, name, module): + """ + Add a wrapper to the module to allow to attach heads to the module. + """ + if name in self._attachable_blocks: + raise ValueError("Found duplicated block name {}".format(name)) + block = ClassyBlock(name, module) + self._attachable_blocks[name] = block + return block + + @property + def attachable_block_names(self): + """ + Return names of all attachable blocks. + """ + return self._attachable_blocks.keys() + + def _clear_heads(self): + # clear all existing heads + self._heads.clear() + self._head_outputs.clear() + + def set_heads(self, heads: Dict[str, Dict[str, ClassyHead]]): + """Attach all the heads to corresponding blocks. + + A head is expected to be a ClassyHead object. For more + details, see :class:`ClassyHead`. + + Args: + heads (Dict): a mapping between attachable block name + and a dictionary of heads attached to that block. For + example, if you have two different teams that want to + attach two different heads for downstream classifiers to + the 15th block, then they would use: + heads = {"block15": + {"team1": classifier_head1, "team2": classifier_head2} + } + """ + self._clear_heads() + + head_ids = set() + for block_name, block_heads in heads.items(): + if block_name not in self._attachable_blocks: + raise ValueError( + "block {} does not exist or can not be attached".format(block_name) + ) + self._attachable_blocks[block_name].set_cache_output() + for head in block_heads.values(): + if head.unique_id in head_ids: + raise ValueError("head id {} already exists".format(head.unique_id)) + head_ids.add(head.unique_id) + self._heads[block_name] = nn.ModuleDict(block_heads) + + def get_heads(self): + """Returns the heads on the model + + Function returns the heads a dictionary of block names to + nn.modules attached to that block. + + """ + return {block_name: dict(heads) for block_name, heads in self._heads.items()} + + @property + def head_outputs(self): + """Return outputs of all heads in the format of Dict[head_id, output] + + Head outputs are cached during a forward pass. + """ + return self._head_outputs.copy() + + def get_block_outputs(self) -> Dict[str, torch.Tensor]: + outputs = {} + for name, block in self._attachable_blocks.items(): + outputs[name] = block.output + return outputs + + def execute_heads(self) -> Dict[str, torch.Tensor]: + block_outs = self.get_block_outputs() + outputs = {} + for block_name, heads in self._heads.items(): + for head in heads.values(): + outputs[head.unique_id] = head(block_outs[block_name]) + self._head_outputs = outputs + return outputs + + def get_optimizer_params(self, bn_weight_decay=False): + """Returns param groups for optimizer. + + Function to return dict of params with "keys" from + {"regularized_params", "unregularized_params"} + to "values" a list of torch Params. + + "weight_decay" provided as part of optimizer is only used + for "regularized_params". For "unregularized_params", weight_decay is set + to 0.0 + + This implementation sets BatchNorm's all trainable params to be + unregularized_params if bn_weight_decay is False. + + Override this function for any custom behavior. + + Args: + bn_weight_decay (bool): Apply weight decay to bn params if true + """ + unregularized_params = [] + regularized_params = [] + for module in self.modules(): + # If module has children (i.e. internal node of constructed DAG) then + # only add direct parameters() to the list of params, else go over + # children node to find if they are BatchNorm or have "bias". + if list(module.children()) != []: + for params in module.parameters(recurse=False): + if params.requires_grad: + regularized_params.append(params) + elif not bn_weight_decay and isinstance( + module, + (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm), + ): + for params in module.parameters(): + if params.requires_grad: + unregularized_params.append(params) + else: + for params in module.parameters(): + if params.requires_grad: + regularized_params.append(params) + return { + "regularized_params": regularized_params, + "unregularized_params": unregularized_params, + } + + @property + def input_shape(self): + """If implemented, returns expected input tensor shape + """ + raise NotImplementedError + + @property + def output_shape(self): + """If implemented, returns expected output tensor shape + """ + raise NotImplementedError + + @property + def model_depth(self): + """If implemented, returns number of layers in model + """ + raise NotImplementedError + + @property + def evaluation_mode(self): + """Used by video models for averaging over contiguous clips. + + TODO: Remove this once we have a video task, this logic should + live in a video specific task + + """ + return ClassyModelEvaluationMode.DEFAULT diff --git a/classy_vision/models/classy_model_wrapper.py b/classy_vision/models/classy_model_wrapper.py new file mode 100644 index 0000000000..3012a4bea3 --- /dev/null +++ b/classy_vision/models/classy_model_wrapper.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict, Optional, Tuple + +import torch.nn as nn + +from .classy_model import ClassyModel + + +class ClassyModelWrapper(ClassyModel): + """ + Class which wraps an nn.Module within a ClassyModel. + + The only required argument is the model, the additional args are needed + to get some additional capabilities from Classy Vision to work. + """ + + def __init__( + self, + model: nn.Module, + input_shape: Optional[Tuple] = None, + output_shape: Optional[Tuple] = None, + model_depth: Optional[int] = None, + ): + super().__init__() + self.model = model + self._input_shape = input_shape + self._output_shape = output_shape + self._model_depth = model_depth + + def forward(self, x): + return self.model(x) + + def extract_features(self, x): + if hasattr(self.model, "extract_features"): + return self.model.extract_features(x) + return super().extract_features(x) + + @property + def input_shape(self): + if self._input_shape is not None: + return self._input_shape + return super().input_shape + + @property + def output_shape(self): + if self._output_shape is not None: + return self._output_shape + return super().output_shape + + @property + def model_depth(self): + if self._model_depth is not None: + return self._model_depth + return super().model_depth diff --git a/classy_vision/models/densenet.py b/classy_vision/models/densenet.py new file mode 100644 index 0000000000..0efdeea622 --- /dev/null +++ b/classy_vision/models/densenet.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# TODO: Some batch-normalization operations are superfluous and can be removed. + +# dependencies: +import math +from typing import Any, Dict + +import torch +import torch.nn as nn +from classy_vision.generic.util import is_pos_int + +from . import register_model +from .classy_model import ClassyModel + + +# global setting for in-place ReLU: +INPLACE = True + + +class _DenseLayer(nn.Sequential): + """ + Single layer of a DenseNet. + """ + + def __init__(self, in_planes, growth_rate=32, expansion=4): + + # assertions: + assert is_pos_int(in_planes) + assert is_pos_int(growth_rate) + assert is_pos_int(expansion) + + # add all layers to layer + super(_DenseLayer, self).__init__() + intermediate = expansion * growth_rate + self.add_module("norm-1", nn.BatchNorm2d(in_planes)) + self.add_module("relu-1", nn.ReLU(inplace=INPLACE)) + self.add_module( + "conv-1", + nn.Conv2d(in_planes, intermediate, kernel_size=1, stride=1, bias=False), + ) + self.add_module("norm-2", nn.BatchNorm2d(intermediate)) + self.add_module("relu-2", nn.ReLU(inplace=INPLACE)) + self.add_module( + "conv-2", + nn.Conv2d( + intermediate, + growth_rate, + kernel_size=3, + stride=1, + padding=1, + bias=False, + ), + ) + + def forward(self, x): + new_features = super(_DenseLayer, self).forward(x) + return torch.cat([x, new_features], 1) + + +class _DenseBlock(nn.Sequential): + """ + Block of densely connected layers at same resolution. + """ + + def __init__(self, num_layers, in_planes, growth_rate=32, expansion=4): + + # assertions: + assert is_pos_int(in_planes) + assert is_pos_int(growth_rate) + assert is_pos_int(expansion) + + # create block of dense layers at same resolution: + super(_DenseBlock, self).__init__() + for idx in range(num_layers): + layer = _DenseLayer( + in_planes + idx * growth_rate, + growth_rate=growth_rate, + expansion=expansion, + ) + self.add_module("denselayer-%d" % (idx + 1), layer) + + +class _Transition(nn.Sequential): + """ + Transition layer to reduce spatial resolution. + """ + + def __init__(self, in_planes, out_planes, reduction=2): + + # assertions: + assert is_pos_int(in_planes) + assert is_pos_int(out_planes) + assert is_pos_int(reduction) + + # create layers for pooling: + super(_Transition, self).__init__() + self.add_module("pool-norm", nn.BatchNorm2d(in_planes)) + self.add_module("pool-relu", nn.ReLU(inplace=INPLACE)) + self.add_module( + "pool-conv", + nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, bias=False), + ) + self.add_module( + "pool-pool", nn.AvgPool2d(kernel_size=reduction, stride=reduction) + ) + + +@register_model("densenet") +class DenseNet(ClassyModel): + def __init__( + self, + num_blocks, + num_classes, + init_planes, + growth_rate, + expansion, + small_input, + final_bn_relu, + ): + """ + Implementation of a standard densely connected network (DenseNet). + + Set `small_input` to `True` for 32x32 sized image inputs. + + Set `final_bn_relu` to `False` to exclude the final batchnorm and ReLU + layers. These settings are useful when + training Siamese networks. + """ + super().__init__() + + # assertions: + assert type(num_blocks) == list + assert all(is_pos_int(b) for b in num_blocks) + assert num_classes is None or is_pos_int(num_classes) + assert is_pos_int(init_planes) + assert is_pos_int(growth_rate) + assert is_pos_int(expansion) + assert type(small_input) == bool + + # initial convolutional block: + self._num_classes = num_classes + self.num_blocks = num_blocks + self.small_input = small_input + if self.small_input: + self.initial_block = nn.Sequential( + nn.Conv2d( + 3, init_planes, kernel_size=3, stride=1, padding=1, bias=False + ), + nn.BatchNorm2d(init_planes), + nn.ReLU(inplace=INPLACE), + ) + else: + self.initial_block = nn.Sequential( + nn.Conv2d( + 3, init_planes, kernel_size=7, stride=2, padding=3, bias=False + ), + nn.BatchNorm2d(init_planes), + nn.ReLU(inplace=INPLACE), + nn.MaxPool2d(kernel_size=3, stride=2, padding=1), + ) + # loop over spatial resolutions: + num_planes = init_planes + self.features = nn.Sequential() + for idx, num_layers in enumerate(num_blocks): + + # add dense block: + block = _DenseBlock( + num_layers, num_planes, growth_rate=growth_rate, expansion=expansion + ) + self.features.add_module("denseblock-%d" % (idx + 1), block) + num_planes = num_planes + num_layers * growth_rate + + # add transition layer: + if idx != len(num_blocks) - 1: + trans = _Transition(num_planes, num_planes // 2) + self.features.add_module("transition-%d" % (idx + 1), trans) + num_planes = num_planes // 2 + + # final batch normalization: + if final_bn_relu: + self.features.add_module("norm-final", nn.BatchNorm2d(num_planes)) + self.features.add_module("relu-final", nn.ReLU(inplace=INPLACE)) + + # final classifier: + self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + self.fc = None if num_classes is None else nn.Linear(num_planes, num_classes) + self.num_planes = num_planes + + # initialize weights of convolutional and batchnorm layers: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2.0 / n)) + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.Linear): + m.bias.data.zero_() + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "DenseNet": + """Instantiates a DenseNet from a configuration. + + Args: + config: A configuration for a DenseNet. + See :func:`__init__` for parameters expected in the config. + + Returns: + A DenseNet instance. + """ + assert "num_blocks" in config + config = { + "num_blocks": config["num_blocks"], + "num_classes": config.get("num_classes"), + "init_planes": config.get("init_planes", 64), + "growth_rate": config.get("growth_rate", 32), + "expansion": config.get("expansion", 4), + "small_input": config.get("small_input", False), + "final_bn_relu": config.get("final_bn_relu", True), + } + return cls(**config) + + # forward pass in DenseNet: + def forward(self, x): + + # initial convolutional block: + out = self.initial_block(x) + + # evaluate all dense blocks: + out = self.features(out) + + # perform average pooling: + out = self.avgpool(out) + + # final classifier: + out = out.view(out.size(0), -1) + if self.fc is not None: + out = self.fc(out) + return out + + def get_optimizer_params(self): + # use weight decay on BatchNorm for DenseNets + return super().get_optimizer_params(bn_weight_decay=True) + + @property + def input_shape(self): + if self.small_input: + return (3, 32, 32) + else: + return (3, 224, 224) + + @property + def output_shape(self): + return (1, self._num_classes) + + @property + def model_depth(self): + return sum(self.num_blocks) diff --git a/classy_vision/models/mlp.py b/classy_vision/models/mlp.py new file mode 100644 index 0000000000..56ecca5945 --- /dev/null +++ b/classy_vision/models/mlp.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +"""MLP model.""" + +from typing import Any, Dict + +import torch.nn as nn + +from . import register_model +from .classy_model import ClassyModel + + +@register_model("mlp") +class MLP(ClassyModel): + """MLP model using ReLU. Useful for testing on CPUs.""" + + def __init__( + self, + input_dim, + output_dim, + hidden_dims, + dropout, + first_dropout, + use_batchnorm, + first_batchnorm, + ): + super().__init__() + + layers = [] + # If first_batchnorm is set, must be using batchnorm + assert not first_batchnorm or use_batchnorm + + self._num_inputs = input_dim + self._num_classes = output_dim + self._model_depth = len(hidden_dims) + 1 + + if dropout > 0 and first_dropout: + layers.append(nn.Dropout(p=dropout)) + + if use_batchnorm and first_batchnorm: + layers.append(nn.BatchNorm1d(input_dim)) + + for dim in hidden_dims: + layers.append(nn.Linear(input_dim, dim)) + if use_batchnorm: + layers.append(nn.BatchNorm1d(dim)) + if dropout > 0: + layers.append(nn.Dropout(p=dropout)) + layers.append(nn.ReLU(inplace=True)) + input_dim = dim + + layers.append(nn.Linear(input_dim, output_dim)) + self.mlp = nn.Sequential(*layers) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "MLP": + """Instantiates a MLP from a configuration. + + Args: + config: A configuration for a MLP. + See :func:`__init__` for parameters expected in the config. + + Returns: + A MLP instance. + """ + assert (key in config for key in ["input_dim", "output_dim", "hidden_dims"]) + + output_dim = config["output_dim"] + return cls( + input_dim=config["input_dim"], + output_dim=output_dim, + hidden_dims=config["hidden_dims"], + dropout=config.get("dropout", 0), + first_dropout=config.get("first_dropout", False), + use_batchnorm=config.get("use_batchnorm", False), + first_batchnorm=config.get("first_batchnorm", False), + ) + + def forward(self, x): + batchsize_per_replica = x.shape[0] + out = x.view(batchsize_per_replica, -1) + out = self.mlp(out) + return out + + @property + def input_shape(self): + return (self._num_inputs,) + + @property + def output_shape(self): + return (1, self._num_classes) + + @property + def model_depth(self): + return self._model_depth diff --git a/classy_vision/models/resnet.py b/classy_vision/models/resnet.py new file mode 100644 index 0000000000..4c08b9ebf9 --- /dev/null +++ b/classy_vision/models/resnet.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +""" +Implementation of ResNet (https://arxiv.org/pdf/1512.03385.pdf) as a special +case of ResNeXt (https://arxiv.org/pdf/1611.05431.pdf) +""" + +from . import register_model +from .resnext import ResNeXt + + +# global setting for in-place ReLU: +INPLACE = True + + +@register_model("resnet") +class ResNet(ResNeXt): + """ + ResNet is a special case of :class:`ResNeXt`. + """ + + def __init__(self, **kwargs): + """ + See :func:`ResNeXt.__init__` + """ + assert ( + kwargs["base_width_and_cardinality"] is None + ), "base_width_and_cardinality should be None for ResNet" + super().__init__(**kwargs) diff --git a/classy_vision/models/resnext.py b/classy_vision/models/resnext.py new file mode 100644 index 0000000000..192be4c253 --- /dev/null +++ b/classy_vision/models/resnext.py @@ -0,0 +1,409 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +""" +Implementation of ResNeXt (https://arxiv.org/pdf/1611.05431.pdf) +""" + +import math +from typing import Any, Dict + +import torch.nn as nn +from classy_vision.generic.util import is_pos_int + +from . import register_model +from .classy_model import ClassyModel + + +# global setting for in-place ReLU: +INPLACE = True + + +def conv3x3(in_planes, out_planes, stride=1, groups=1): + """helper function for constructing 3x3 grouped convolution""" + return nn.Conv2d( + in_planes, + out_planes, + kernel_size=3, + stride=stride, + padding=1, + groups=groups, + bias=False, + ) + + +def conv1x1(in_planes, out_planes, stride=1): + """helper function for constructing 1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class GenericLayer(nn.Module): + """ + Parent class for 2-layer (BasicLayer) and 3-layer (BottleneckLayer) + bottleneck layer class + """ + + def __init__( + self, + convolutional_block, + in_planes, + out_planes, + stride=1, + mid_planes_and_cardinality=None, + reduction=4, + final_bn_relu=True, + ): + + # assertions on inputs: + assert is_pos_int(in_planes) and is_pos_int(out_planes) + assert is_pos_int(stride) and is_pos_int(reduction) + + # set object fields: + super(GenericLayer, self).__init__() + self.convolutional_block = convolutional_block + self.final_bn_relu = final_bn_relu + + # final batchnorm and relu layer: + if final_bn_relu: + self.bn = nn.BatchNorm2d(out_planes) + self.relu = nn.ReLU(inplace=INPLACE) + + # define down-sampling layer (if direct residual impossible): + self.downsample = None + if stride != 1 or in_planes != out_planes: + self.downsample = nn.Sequential( + conv1x1(in_planes, out_planes, stride=stride), + nn.BatchNorm2d(out_planes), + ) + + def forward(self, x): + + # if required, perform downsampling along shortcut connection: + if self.downsample is None: + residual = x + else: + residual = self.downsample(x) + + # forward pass through convolutional block: + out = self.convolutional_block(x) + + if self.final_bn_relu: + out = self.bn(out) + # add residual connection, perform rely + batchnorm, and return result: + out += residual + if self.final_bn_relu: + out = self.relu(out) + return out + + +class BasicLayer(GenericLayer): + """ + ResNeXt bottleneck layer with `in_planes` input planes and `out_planes` + output planes. + """ + + def __init__( + self, + in_planes, + out_planes, + stride=1, + mid_planes_and_cardinality=None, + reduction=4, + final_bn_relu=True, + ): + + # assertions on inputs: + assert is_pos_int(in_planes) and is_pos_int(out_planes) + assert is_pos_int(stride) and is_pos_int(reduction) + + # define convolutional block: + convolutional_block = nn.Sequential( + conv3x3(in_planes, out_planes, stride=stride), + nn.BatchNorm2d(out_planes), + nn.ReLU(inplace=INPLACE), + conv3x3(out_planes, out_planes), + ) + + # call constructor of generic layer: + super(BasicLayer, self).__init__( + convolutional_block, + in_planes, + out_planes, + stride=stride, + reduction=reduction, + final_bn_relu=final_bn_relu, + ) + + +class BottleneckLayer(GenericLayer): + """ + ResNeXt bottleneck layer with `in_planes` input planes, `out_planes` + output planes, and a bottleneck `reduction`. + """ + + def __init__( + self, + in_planes, + out_planes, + stride=1, + mid_planes_and_cardinality=None, + reduction=4, + final_bn_relu=True, + ): + + # assertions on inputs: + assert is_pos_int(in_planes) and is_pos_int(out_planes) + assert is_pos_int(stride) and is_pos_int(reduction) + + # define convolutional layers: + bottleneck_planes = int(math.ceil(out_planes / reduction)) + cardinality = 1 + if mid_planes_and_cardinality is not None: + mid_planes, cardinality = mid_planes_and_cardinality + bottleneck_planes = mid_planes * cardinality + + convolutional_block = nn.Sequential( + conv1x1(in_planes, bottleneck_planes), + nn.BatchNorm2d(bottleneck_planes), + nn.ReLU(inplace=INPLACE), + conv3x3( + bottleneck_planes, bottleneck_planes, stride=stride, groups=cardinality + ), + nn.BatchNorm2d(bottleneck_planes), + nn.ReLU(inplace=INPLACE), + conv1x1(bottleneck_planes, out_planes), + ) + + # call constructor of generic layer: + super(BottleneckLayer, self).__init__( + convolutional_block, + in_planes, + out_planes, + stride=stride, + reduction=reduction, + final_bn_relu=final_bn_relu, + ) + + +class SmallInputInitialBlock(nn.Module): + """ + ResNeXt initial block for small input with `in_planes` input planes + """ + + def __init__(self, init_planes): + super().__init__() + self._module = nn.Sequential( + conv3x3(3, init_planes, stride=1), + nn.BatchNorm2d(init_planes), + nn.ReLU(inplace=INPLACE), + ) + + def forward(self, x): + return self._module(x) + + +class InitialBlock(nn.Module): + """ + ResNeXt initial block with `in_planes` input planes + """ + + def __init__(self, init_planes): + super().__init__() + self._module = nn.Sequential( + nn.Conv2d(3, init_planes, kernel_size=7, stride=2, padding=3, bias=False), + nn.BatchNorm2d(init_planes), + nn.ReLU(inplace=INPLACE), + nn.MaxPool2d(kernel_size=3, stride=2, padding=1), + ) + + def forward(self, x): + return self._module(x) + + +@register_model("resnext") +class ResNeXt(ClassyModel): + def __init__( + self, + num_blocks, + init_planes, + reduction, + small_input, + zero_init_bn_residuals, + base_width_and_cardinality, + basic_layer, + final_bn_relu, + ): + """ + Implementation of `ResNeXt `_. + + Set ``small_input`` to `True` for 32x32 sized image inputs. + + Set ``final_bn_relu`` to `False` to exclude the final batchnorm and + ReLU layers. These settings are useful when training Siamese networks. + """ + super().__init__() + + # assertions on inputs: + assert type(num_blocks) == list + assert all(is_pos_int(n) for n in num_blocks) + assert is_pos_int(init_planes) and is_pos_int(reduction) + assert type(small_input) == bool + assert ( + type(zero_init_bn_residuals) == bool + ), "zero_init_bn_residuals must be a boolean, set to true if gamma of last\ + BN of residual block should be initialized to 0.0, false for 1.0" + assert base_width_and_cardinality is None or ( + isinstance(base_width_and_cardinality, (tuple, list)) + and len(base_width_and_cardinality) == 2 + and is_pos_int(base_width_and_cardinality[0]) + and is_pos_int(base_width_and_cardinality[1]) + ) + + # initial convolutional block: + self.num_blocks = num_blocks + self.small_input = small_input + self._make_initial_block(small_input, init_planes, basic_layer) + + # compute number of planes at each spatial resolution: + out_planes = [init_planes * 2 ** i * reduction for i in range(len(num_blocks))] + in_planes = [init_planes] + out_planes[:-1] + + # create subnetworks for each spatial resolution: + blocks = [] + for idx in range(len(out_planes)): + mid_planes_and_cardinality = None + if base_width_and_cardinality is not None: + w, c = base_width_and_cardinality + mid_planes_and_cardinality = (w * 2 ** idx, c) + new_block = self._make_resolution_block( + in_planes[idx], + out_planes[idx], + idx, + num_blocks[idx], # num layers + stride=1 if idx == 0 else 2, + mid_planes_and_cardinality=mid_planes_and_cardinality, + reduction=reduction, + final_bn_relu=final_bn_relu or (idx != (len(out_planes) - 1)), + ) + blocks.append(nn.Sequential(*new_block)) + self.blocks = nn.Sequential(*blocks) + + self.out_planes = out_planes[-1] + self._num_classes = out_planes + + # initialize weights: + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu") + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + # Init BatchNorm gamma to 0.0 for last BN layer, it gets 0.2-0.3% higher + # final val top1 for larger batch sizes. + if zero_init_bn_residuals: + for m in self.modules(): + if isinstance(m, GenericLayer): + if hasattr(m, "bn"): + nn.init.constant_(m.bn.weight, 0) + + def _make_initial_block(self, small_input, init_planes, basic_layer): + if small_input: + self.initial_block = SmallInputInitialBlock(init_planes) + self.layer_type = BasicLayer + else: + self.initial_block = InitialBlock(init_planes) + self.layer_type = BasicLayer if basic_layer else BottleneckLayer + + # helper function that creates ResNet blocks at single spatial resolution: + def _make_resolution_block( + self, + in_planes, + out_planes, + resolution_idx, + num_blocks, + stride=1, + mid_planes_and_cardinality=None, + reduction=4, + final_bn_relu=True, + ): + + # add the desired number of residual blocks: + blocks = [] + for idx in range(num_blocks): + blocks.append( + self.build_attachable_block( + "block{}-{}".format(resolution_idx, idx), + self.layer_type( + in_planes if idx == 0 else out_planes, + out_planes, + stride=stride if idx == 0 else 1, # only first block has stride + mid_planes_and_cardinality=mid_planes_and_cardinality, + reduction=reduction, + final_bn_relu=final_bn_relu or (idx != (num_blocks - 1)), + ), + ) + ) + return blocks + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ResNeXt": + """Instantiates a ResNeXt from a configuration. + + Args: + config: A configuration for a ResNeXt. + See :func:`__init__` for parameters expected in the config. + + Returns: + A ResNeXt instance. + """ + assert "num_blocks" in config + config = { + "num_blocks": config["num_blocks"], + "init_planes": config.get("init_planes", 64), + "reduction": config.get("reduction", 4), + "base_width_and_cardinality": config.get("base_width_and_cardinality"), + "small_input": config.get("small_input", False), + "basic_layer": config.get("basic_layer", False), + "final_bn_relu": config.get("final_bn_relu", True), + "zero_init_bn_residuals": config.get("zero_init_bn_residuals", False), + } + return cls(**config) + + # forward pass in residual network: + def forward(self, x): + # initial convolutional block: + out = self.initial_block(x) + + # evaluate all residual blocks: + # TODO: (kaizh) T43794289 exit early if there is no block that has heads + self.blocks(out) + + # By default the classification layer is implemented as one head on top + # of the last block. The head is automatically computed right after the + # last block. + head_outputs = self.execute_heads() + if len(head_outputs) == 0: + raise Exception("Expecting at least one head that generates output") + elif len(head_outputs) == 1: + return list(head_outputs.values())[0] + else: + return head_outputs + + @property + def input_shape(self): + if self.small_input: + return (3, 32, 32) + else: + return (3, 224, 224) + + @property + def output_shape(self): + return (1, self._num_classes) + + @property + def model_depth(self): + return sum(self.num_blocks) diff --git a/classy_vision/models/resnext3d.py b/classy_vision/models/resnext3d.py new file mode 100644 index 0000000000..f1edfdf8d2 --- /dev/null +++ b/classy_vision/models/resnext3d.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch +import torch.nn as nn +from classy_vision.generic.util import is_pos_int, is_pos_int_list + +from . import register_model +from .classy_model import ClassyModel, ClassyModelEvaluationMode +from .resnext3d_stage import ResStage +from .resnext3d_stem import ResNeXt3DStem + + +model_stems = { + "resnext3d_stem": ResNeXt3DStem, + # For more types of model stem, add them below +} + + +class ResNeXt3DBase(ClassyModel): + def __init__( + self, + input_key, + input_planes, + clip_crop_size, + frames_per_clip, + num_blocks, + stem_name, + stem_planes, + stem_temporal_kernel, + stem_spatial_kernel, + stem_maxpool, + ): + """ + ResNeXt3DBase implements everything in ResNeXt3D model except the + construction of 4 stages. See more details in ResNeXt3D. + """ + super(ResNeXt3DBase, self).__init__() + + self._input_key = input_key + self.input_planes = input_planes + self.clip_crop_size = clip_crop_size + self.frames_per_clip = frames_per_clip + self.num_blocks = num_blocks + + assert stem_name in model_stems, "unknown stem: %s" % stem_name + self.stem = model_stems[stem_name]( + stem_temporal_kernel, + stem_spatial_kernel, + input_planes, + stem_planes, + stem_maxpool, + ) + + @classmethod + def _parse_config(cls, config): + ret_config = {} + required_args = [ + "input_planes", + "clip_crop_size", + "skip_transformation_type", + "residual_transformation_type", + "frames_per_clip", + "num_blocks", + ] + for arg in required_args: + assert arg in config, "resnext3d model requires argument %s" % arg + ret_config[arg] = config[arg] + + # Default setting for model stem + # stem_planes: No. of output channles of conv op in stem + # stem_temporal_kernel: temporal size of conv op in stem + # stem_spatial_kernel: spatial size of conv op in stem + # stem_maxpool: by default, spatial maxpool op is disabled in stem + ret_config.update( + { + "input_key": config.get("input_key", None), + "stem_name": config.get("stem_name", "resnext3d_stem"), + "stem_planes": config.get("stem_planes", 64), + "stem_temporal_kernel": config.get("stem_temporal_kernel", 3), + "stem_spatial_kernel": config.get("stem_spatial_kernel", 7), + "stem_maxpool": config.get("stem_maxpool", False), + } + ) + # Default setting for model stages 2, 3, 4 and 5 + # stage_planes: No. of output channel of 1st conv op in stage 2 + # stage_temporal_kernel_basis: Basis of temporal kernel sizes for each of + # the stage. + # temporal_conv_1x1: if True, do temporal convolution in the fist + # 1x1 Conv3d. Otherwise, do it in the second 3x3 Conv3d (default settting) + # stage_temporal_stride: temporal stride for each stage + # stage_spatial_stride: spatial stride for each stage + # num_groups: No. of groups in 2nd (group) conv in the residual transformation + # width_per_group: No. of channels per group in 2nd (group) conv in the + # residual transformation + ret_config.update( + { + "stage_planes": config.get("stage_planes", 256), + "stage_temporal_kernel_basis": config.get( + "stage_temporal_kernel_basis", [[3], [3], [3], [3]] + ), + "temporal_conv_1x1": config.get( + "temporal_conv_1x1", [False, False, False, False] + ), + "stage_temporal_stride": config.get( + "stage_temporal_stride", [1, 2, 2, 2] + ), + "stage_spatial_stride": config.get( + "stage_spatial_stride", [1, 2, 2, 2] + ), + "num_groups": config.get("num_groups", 1), + "width_per_group": config.get("width_per_group", 64), + } + ) + # Default setting for model parameter initialization + ret_config.update( + { + "zero_init_residual_transform": config.get( + "zero_init_residual_transform", False + ) + } + ) + assert is_pos_int_list(ret_config["num_blocks"]) + assert is_pos_int(ret_config["stem_planes"]) + assert is_pos_int(ret_config["stem_temporal_kernel"]) + assert is_pos_int(ret_config["stem_spatial_kernel"]) + assert type(ret_config["stem_maxpool"]) == bool + assert is_pos_int(ret_config["stage_planes"]) + assert type(ret_config["stage_temporal_kernel_basis"]) == list + assert all( + is_pos_int_list(l) for l in ret_config["stage_temporal_kernel_basis"] + ) + assert type(ret_config["temporal_conv_1x1"]) == list + assert is_pos_int_list(ret_config["stage_temporal_stride"]) + assert is_pos_int_list(ret_config["stage_spatial_stride"]) + assert is_pos_int(ret_config["num_groups"]) + assert is_pos_int(ret_config["width_per_group"]) + return ret_config + + def _init_parameter(self, zero_init_residual_transform): + for m in self.modules(): + if isinstance(m, nn.Conv3d): + if ( + hasattr(m, "final_transform_op") + and m.final_transform_op + and zero_init_residual_transform + ): + nn.init.constant_(m.weight, 0) + else: + nn.init.kaiming_normal_( + m.weight, mode="fan_out", nonlinearity="relu" + ) + if m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm3d) and m.affine: + if ( + hasattr(m, "final_transform_op") + and m.final_transform_op + and zero_init_residual_transform + ): + batchnorm_weight = 0.0 + else: + batchnorm_weight = 1.0 + nn.init.constant_(m.weight, batchnorm_weight) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, mean=0.0, std=0.01) + nn.init.constant_(m.bias, 0) + + def set_classy_state(self, state): + # We need to support both regular checkpoint loading and 2D conv weight + # inflation into 3D conv weight in this function. + self.load_head_states(state) + current_state = self.state_dict() + for name, weight_src in state["model"]["trunk"].items(): + assert name in current_state, ( + "weight %s is not found in ResNeXt3D model" % name + ) + weight_tgt = current_state[name] + assert ( + weight_src.dim() == weight_tgt.dim() + ), "weight of source- and target 3D convolution should have same dimension" + if ( + weight_src.dim() == 5 + and weight_src.shape[2] == 1 + and weight_tgt.shape[2] > 1 + ): + # Find a source weight tensor where temporal dimension is 1. If the + # temporal dimension of the current weight tensor with the same name + # is larger than 1, we inflate the source weight tensor before + # loading it. Such parameter inflation was first introduced in + # the paper (https://arxiv.org/abs/1705.07750). It can achieve a + # better initialization compared to random initialization. + assert ( + weight_src.shape[-2:] == weight_tgt.shape[-2:] + and weight_src.shape[:2] == weight_tgt.shape[:2] + ), "weight shapes of source- and target 3D convolution mismatch" + weight_src_inflated = ( + weight_src.repeat(1, 1, weight_tgt.shape[2], 1, 1) + / weight_tgt.shape[2] + ) + weight_src = weight_src_inflated + else: + assert all( + weight_src.size(d) == weight_tgt.size(d) + for d in range(weight_src.dim()) + ), ( + "the shapes of source and target weight mismatch: %s Vs %s" + % (str(weight_src.size()), str(weight_tgt.size())) + ) + + current_state[name] = weight_src.clone() + super().load_state_dict(current_state) + + def forward(self, x): + """ + Args: + x (dict or torch.Tensor): video input. + When its type is dict, the dataset is a video dataset, and its + content is like {"video": torch.tensor, "audio": torch.tensor}. + When its type is torch.Tensor, the dataset is an image dataset. + """ + assert isinstance(x, dict) or isinstance( + x, torch.Tensor + ), "x must be either a dictionary or a torch.Tensor" + if isinstance(x, dict): + assert self._input_key is not None and self._input_key in x, ( + "input key (%s) not in the input" % self._input_key + ) + x = x[self._input_key] + else: + assert ( + self._input_key is None + ), "when input of forward pass is a tensor, input key should not be set" + assert x.dim() == 4 or x.dim() == 5, "tensor x must be 4D/5D tensor" + if x.dim() == 4: + # x is a 4D tensor of size N x C x H x W and is prepared from an + # image dataset. We insert a temporal axis make it 5D of size + # N x C x T x H x W + x = torch.unsqueeze(x, 2) + + out = self.stem([x]) + out = self.stages(out) + + head_outputs = self.execute_heads() + if len(head_outputs) == 0: + raise Exception("Expecting at least one head that generates output") + elif len(head_outputs) == 1: + return list(head_outputs.values())[0] + else: + return head_outputs + + @property + def input_shape(self): + """ + Shape of video model input can vary in the following cases + - At training stage, input are video frame croppings of fixed size. + - At test stage, input are original video frames to support Fully Convolutional + evaluation and its size can vary video by video + """ + # Input shape is used by tensorboard hook. We put the input shape at + # training stage for profiling and visualization purpose. + return ( + self.input_planes, + self.frames_per_clip, + self.clip_crop_size, + self.clip_crop_size, + ) + + @property + def output_shape(self): + return (1, None) + + @property + def model_depth(self): + return sum(self.num_blocks) + + @property + def evaluation_mode(self): + return ClassyModelEvaluationMode.VIDEO_CLIP_AVERAGING + + @property + def input_key(self): + return self._input_key + + +@register_model("resnext3d") +class ResNeXt3D(ResNeXt3DBase): + """ + Implementation of: + 1. Conventional `post-activated 3D ResNe(X)t `_. + + 2. `Pre-activated 3D ResNe(X)t `_. + The model consists of one stem, a number of stages, and one or multiple + heads that are attached to different blocks in the stage. + """ + def __init__( + self, + input_key, + input_planes, + clip_crop_size, + skip_transformation_type, + residual_transformation_type, + frames_per_clip, + num_blocks, + stem_name, + stem_planes, + stem_temporal_kernel, + stem_spatial_kernel, + stem_maxpool, + stage_planes, + stage_temporal_kernel_basis, + temporal_conv_1x1, + stage_temporal_stride, + stage_spatial_stride, + num_groups, + width_per_group, + zero_init_residual_transform, + ): + """ + Args: + input_key (str): a key that can index into model input that is + of dict type. + input_planes (int): the channel dimension of the input. Normally 3 is used + for rgb input. + clip_crop_size (int): spatial cropping size of video clip at train time. + skip_transformation_type (str): the type of skip transformation. + residual_transformation_type (str): the type of residual transformation. + frames_per_clip (int): Number of frames in a video clip. + num_blocks (list): list of the number of blocks in stages. + stem_name (str): name of model stem. + stem_planes (int): the output dimension of the convolution in the model + stem. + stem_temporal_kernel (int): the temporal kernel size of the convolution + in the model stem. + stem_spatial_kernel (int): the spatial kernel size of the convolution + in the model stem. + stem_maxpool (bool): If true, perform max pooling. + stage_planes (int): the output channel dimension of the 1st residual stage + stage_temporal_kernel_basis (list): Basis of temporal kernel sizes for + each of the stage. + temporal_conv_1x1 (bool): Only useful for BottleneckTransformation. + In a pathaway, if True, do temporal convolution in the first 1x1 + Conv3d. Otherwise, do it in the second 3x3 Conv3d. + stage_temporal_stride (int): the temporal stride of the residual + transformation. + stage_spatial_stride (int): the spatial stride of the the residual + transformation. + num_groups (int): number of groups for the convolution. + num_groups = 1 is for standard ResNet like networks, and + num_groups > 1 is for ResNeXt like networks. + width_per_group (int): Number of channels per group in 2nd (group) + conv in the residual transformation in the first stage + zero_init_residual_transform (bool): if true, the weight of last + operation, which could be either BatchNorm3D in post-activated + transformation or Conv3D in pre-activated transformation, in the + residual transformation is initialized to zero + """ + super(ResNeXt3D, self).__init__( + input_key, + input_planes, + clip_crop_size, + frames_per_clip, + num_blocks, + stem_name, + stem_planes, + stem_temporal_kernel, + stem_spatial_kernel, + stem_maxpool, + ) + + num_stages = len(num_blocks) + out_planes = [stage_planes * 2 ** i for i in range(num_stages)] + in_planes = [stem_planes] + out_planes[:-1] + inner_planes = [ + num_groups * width_per_group * 2 ** i for i in range(num_stages) + ] + + stages = [] + for s in range(num_stages): + stage = ResStage( + s + 1, # stem is viewed as stage 0, and following stages start from 1 + [in_planes[s]], + [out_planes[s]], + [inner_planes[s]], + [stage_temporal_kernel_basis[s]], + [temporal_conv_1x1[s]], + [stage_temporal_stride[s]], + [stage_spatial_stride[s]], + [num_blocks[s]], + [num_groups], + skip_transformation_type, + residual_transformation_type, + block_callback=self.build_attachable_block, + disable_pre_activation=(s == 0), + final_stage=(s == (num_stages - 1)), + ) + stages.append(stage) + + self.stages = nn.Sequential(*stages) + self._init_parameter(zero_init_residual_transform) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ResNeXt3D": + """Instantiates a ResNeXt3D from a configuration. + + Args: + config: A configuration for a ResNeXt3D. + See :func:`__init__` for parameters expected in the config. + + Returns: + A ResNeXt3D instance. + """ + ret_config = ResNeXt3D._parse_config(config) + return cls(**ret_config) diff --git a/classy_vision/models/resnext3d_block.py b/classy_vision/models/resnext3d_block.py new file mode 100644 index 0000000000..2147507e76 --- /dev/null +++ b/classy_vision/models/resnext3d_block.py @@ -0,0 +1,465 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch.nn as nn + + +class BasicTransformation(nn.Module): + """ + Basic transformation: 3x3x3 group conv, 3x3x3 group conv + """ + + def __init__( + self, + dim_in, + dim_out, + temporal_stride, + spatial_stride, + groups, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + **kwargs + ): + """ + Args: + dim_in (int): the channel dimensions of the input. + dim_out (int): the channel dimension of the output. + temporal_stride (int): the temporal stride of the bottleneck. + spatial_stride (int): the spatial_stride of the bottleneck. + groups (int): number of groups for the convolution. + inplace_relu (bool): calculate the relu on the original input + without allocating new memory. + bn_eps (float): epsilon for batch norm. + bn_mmt (float): momentum for batch norm. Noted that BN momentum in + PyTorch = 1 - BN momentum in Caffe2. + """ + super(BasicTransformation, self).__init__() + + # 3x3x3 group conv, BN, ReLU. + branch2a = nn.Conv3d( + dim_in, + dim_out, + [3, 3, 3], # kernel + stride=[temporal_stride, spatial_stride, spatial_stride], + padding=[1, 1, 1], + groups=groups, + bias=False, + ) + branch2a_bn = nn.BatchNorm3d(dim_out, eps=bn_eps, momentum=bn_mmt) + branch2a_relu = nn.ReLU(inplace=inplace_relu) + # 3x3x3 group conv, BN, ReLU. + branch2b = nn.Conv3d( + dim_out, + dim_out, + [3, 3, 3], # kernel + stride=[1, 1, 1], + padding=[1, 1, 1], + groups=groups, + bias=False, + ) + branch2b_bn = nn.BatchNorm3d(dim_out, eps=bn_eps, momentum=bn_mmt) + branch2b_bn.final_transform_op = True + + self.basic_transform = nn.Sequential( + branch2a, branch2a_bn, branch2a_relu, branch2b, branch2b_bn + ) + + def forward(self, x): + return self.basic_transform(x) + + +class PostactivatedBottleneckTransformation(nn.Module): + """ + Bottleneck transformation: Tx1x1, 1x3x3, 1x1x1, where T is the size of + temporal kernel. + """ + + def __init__( + self, + dim_in, + dim_out, + temporal_stride, + spatial_stride, + num_groups, + dim_inner, + temporal_kernel_size=3, + temporal_conv_1x1=True, + spatial_stride_1x1=False, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + **kwargs + ): + """ + Args: + dim_in (int): the channel dimensions of the input. + dim_out (int): the channel dimension of the output. + temporal_kernel_size (int): the temporal kernel sizes of the middle + convolution in the bottleneck. + temporal_conv_1x1 (bool): if True, do temporal convolution in the fist + 1x1 Conv3d. Otherwise, do it in the second 3x3 Conv3d + temporal_stride (int): the temporal stride of the bottleneck. + spatial_stride (int): the spatial_stride of the bottleneck. + num_groups (int): number of groups for the convolution. + dim_inner (int): the inner dimension of the block. + is for standard ResNet like networks, and num_groups>1 is for + ResNeXt like networks. + spatial_stride_1x1 (bool): if True, apply spatial_stride to 1x1 conv. + inplace_relu (bool): calculate the relu on the original input + without allocating new memory. + bn_eps (float): epsilon for batch norm. + bn_mmt (float): momentum for batch norm. Noted that BN momentum in + PyTorch = 1 - BN momentum in Caffe2. + """ + super(PostactivatedBottleneckTransformation, self).__init__() + (temporal_kernel_size_1x1, temporal_kernel_size_3x3) = ( + (temporal_kernel_size, 1) + if temporal_conv_1x1 + else (1, temporal_kernel_size) + ) + # MSRA -> stride=2 is on 1x1; TH/C2 -> stride=2 is on 3x3. + (str1x1, str3x3) = ( + (spatial_stride, 1) if spatial_stride_1x1 else (1, spatial_stride) + ) + # Tx1x1 conv, BN, ReLU. + self.branch2a = nn.Conv3d( + dim_in, + dim_inner, + kernel_size=[temporal_kernel_size_1x1, 1, 1], + stride=[1, str1x1, str1x1], + padding=[temporal_kernel_size_1x1 // 2, 0, 0], + bias=False, + ) + self.branch2a_bn = nn.BatchNorm3d(dim_inner, eps=bn_eps, momentum=bn_mmt) + self.branch2a_relu = nn.ReLU(inplace=inplace_relu) + # Tx3x3 group conv, BN, ReLU. + self.branch2b = nn.Conv3d( + dim_inner, + dim_inner, + [temporal_kernel_size_3x3, 3, 3], + stride=[temporal_stride, str3x3, str3x3], + padding=[temporal_kernel_size_3x3 // 2, 1, 1], + groups=num_groups, + bias=False, + ) + self.branch2b_bn = nn.BatchNorm3d(dim_inner, eps=bn_eps, momentum=bn_mmt) + self.branch2b_relu = nn.ReLU(inplace=inplace_relu) + # 1x1x1 conv, BN. + self.branch2c = nn.Conv3d( + dim_inner, + dim_out, + kernel_size=[1, 1, 1], + stride=[1, 1, 1], + padding=[0, 0, 0], + bias=False, + ) + self.branch2c_bn = nn.BatchNorm3d(dim_out, eps=bn_eps, momentum=bn_mmt) + self.branch2c_bn.final_transform_op = True + + def forward(self, x): + # Explicitly forward every layer. + # Branch2a. + x = self.branch2a(x) + x = self.branch2a_bn(x) + x = self.branch2a_relu(x) + + # Branch2b. + x = self.branch2b(x) + x = self.branch2b_bn(x) + x = self.branch2b_relu(x) + + # Branch2c + x = self.branch2c(x) + x = self.branch2c_bn(x) + return x + + +class PreactivatedBottleneckTransformation(nn.Module): + """ + Bottleneck transformation with pre-activation, which includes BatchNorm3D + and ReLu. Conv3D kernsl are Tx1x1, 1x3x3, 1x1x1, where T is the size of + temporal kernel (https://arxiv.org/abs/1603.05027). + """ + + def __init__( + self, + dim_in, + dim_out, + temporal_stride, + spatial_stride, + num_groups, + dim_inner, + temporal_kernel_size=3, + temporal_conv_1x1=True, + spatial_stride_1x1=False, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + disable_pre_activation=False, + **kwargs + ): + """ + Args: + dim_in (int): the channel dimensions of the input. + dim_out (int): the channel dimension of the output. + temporal_kernel_size (int): the temporal kernel sizes of the middle + convolution in the bottleneck. + temporal_conv_1x1 (bool): if True, do temporal convolution in the fist + 1x1 Conv3d. Otherwise, do it in the second 3x3 Conv3d + temporal_stride (int): the temporal stride of the bottleneck. + spatial_stride (int): the spatial_stride of the bottleneck. + num_groups (int): number of groups for the convolution. + dim_inner (int): the inner dimension of the block. + is for standard ResNet like networks, and num_groups>1 is for + ResNeXt like networks. + spatial_stride_1x1 (bool): if True, apply spatial_stride to 1x1 conv. + inplace_relu (bool): calculate the relu on the original input + without allocating new memory. + bn_eps (float): epsilon for batch norm. + bn_mmt (float): momentum for batch norm. Noted that BN momentum in + PyTorch = 1 - BN momentum in Caffe2. + disable_pre_activation (bool): If true, disable pre activation, + including BatchNorm3D and ReLU. + """ + super(PreactivatedBottleneckTransformation, self).__init__() + (temporal_kernel_size_1x1, temporal_kernel_size_3x3) = ( + (temporal_kernel_size, 1) + if temporal_conv_1x1 + else (1, temporal_kernel_size) + ) + (str1x1, str3x3) = ( + (spatial_stride, 1) if spatial_stride_1x1 else (1, spatial_stride) + ) + + self.disable_pre_activation = disable_pre_activation + if not disable_pre_activation: + self.branch2a_bn = nn.BatchNorm3d(dim_in, eps=bn_eps, momentum=bn_mmt) + self.branch2a_relu = nn.ReLU(inplace=inplace_relu) + + self.branch2a = nn.Conv3d( + dim_in, + dim_inner, + kernel_size=[temporal_kernel_size_1x1, 1, 1], + stride=[1, str1x1, str1x1], + padding=[temporal_kernel_size_1x1 // 2, 0, 0], + bias=False, + ) + # Tx3x3 group conv, BN, ReLU. + self.branch2b_bn = nn.BatchNorm3d(dim_inner, eps=bn_eps, momentum=bn_mmt) + self.branch2b_relu = nn.ReLU(inplace=inplace_relu) + self.branch2b = nn.Conv3d( + dim_inner, + dim_inner, + [temporal_kernel_size_3x3, 3, 3], + stride=[temporal_stride, str3x3, str3x3], + padding=[temporal_kernel_size_3x3 // 2, 1, 1], + groups=num_groups, + bias=False, + ) + # 1x1x1 conv, BN. + self.branch2c_bn = nn.BatchNorm3d(dim_inner, eps=bn_eps, momentum=bn_mmt) + self.branch2c_relu = nn.ReLU(inplace=inplace_relu) + self.branch2c = nn.Conv3d( + dim_inner, + dim_out, + kernel_size=[1, 1, 1], + stride=[1, 1, 1], + padding=[0, 0, 0], + bias=False, + ) + self.branch2c.final_transform_op = True + + def forward(self, x): + # Branch2a + if not self.disable_pre_activation: + x = self.branch2a_bn(x) + x = self.branch2a_relu(x) + x = self.branch2a(x) + # Branch2b + x = self.branch2b_bn(x) + x = self.branch2b_relu(x) + x = self.branch2b(x) + # Branch2c + x = self.branch2c_bn(x) + x = self.branch2c_relu(x) + x = self.branch2c(x) + return x + + +residual_transformations = { + "basic_transformation": BasicTransformation, + "postactivated_bottleneck_transformation": PostactivatedBottleneckTransformation, + "preactivated_bottleneck_transformation": PreactivatedBottleneckTransformation, + # For more types of residual transformations, add them below +} + + +class PostactivatedShortcutTransformation(nn.Module): + """ + Skip connection used in ResNet3D model. + """ + + def __init__( + self, + dim_in, + dim_out, + temporal_stride, + spatial_stride, + bn_eps=1e-5, + bn_mmt=0.1, + **kwargs + ): + super(PostactivatedShortcutTransformation, self).__init__() + # Use skip connection with projection if dim or spatial/temporal res change. + assert (dim_in != dim_out) or (spatial_stride != 1) or (temporal_stride != 1) + self.branch1 = nn.Conv3d( + dim_in, + dim_out, + kernel_size=1, + stride=[temporal_stride, spatial_stride, spatial_stride], + padding=0, + bias=False, + ) + self.branch1_bn = nn.BatchNorm3d(dim_out, eps=bn_eps, momentum=bn_mmt) + + def forward(self, x): + return self.branch1_bn(self.branch1(x)) + + +class PreactivatedShortcutTransformation(nn.Module): + """ + Skip connection with pre-activation, which includes BatchNorm3D and ReLU, + in ResNet3D model (https://arxiv.org/abs/1603.05027). + """ + + def __init__( + self, + dim_in, + dim_out, + temporal_stride, + spatial_stride, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + disable_pre_activation=False, + **kwargs + ): + super(PreactivatedShortcutTransformation, self).__init__() + # Use skip connection with projection if dim or spatial/temporal res change. + assert (dim_in != dim_out) or (spatial_stride != 1) or (temporal_stride != 1) + if not disable_pre_activation: + self.branch1_bn = nn.BatchNorm3d(dim_in, eps=bn_eps, momentum=bn_mmt) + self.branch1_relu = nn.ReLU(inplace=inplace_relu) + self.branch1 = nn.Conv3d( + dim_in, + dim_out, + kernel_size=1, + stride=[temporal_stride, spatial_stride, spatial_stride], + padding=0, + bias=False, + ) + + def forward(self, x): + if hasattr(self, "branch1_bn") and hasattr(self, "branch1_relu"): + x = self.branch1_relu(self.branch1_bn(x)) + x = self.branch1(x) + return x + + +skip_transformations = { + "postactivated_shortcut": PostactivatedShortcutTransformation, + "preactivated_shortcut": PreactivatedShortcutTransformation, + # For more types of skip transformations, add them below +} + + +class ResBlock(nn.Module): + """ + Residual block with skip connection. + """ + + def __init__( + self, + dim_in, + dim_out, + dim_inner, + temporal_kernel_size, + temporal_conv_1x1, + temporal_stride, + spatial_stride, + skip_transformation_type, + residual_transformation_type, + num_groups=1, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + disable_pre_activation=False, + ): + """ + ResBlock class constructs redisual blocks. More details can be found in: + "Deep residual learning for image recognition." + https://arxiv.org/abs/1512.03385 + Args: + dim_in (int): the channel dimensions of the input. + dim_out (int): the channel dimension of the output. + dim_inner (int): the inner dimension of the block. + temporal_kernel_size (int): the temporal kernel sizes of the middle + convolution in the bottleneck. + temporal_conv_1x1 (bool): Only useful for PostactivatedBottleneckTransformation. + if True, do temporal convolution in the fist 1x1 Conv3d. + Otherwise, do it in the second 3x3 Conv3d + temporal_stride (int): the temporal stride of the bottleneck. + spatial_stride (int): the spatial_stride of the bottleneck. + stride (int): the stride of the bottleneck. + skip_transformation_type (str): the type of skip transformation + residual_transformation_type (str): the type of residual transformation + num_groups (int): number of groups for the convolution. num_groups=1 + is for standard ResNet like networks, and num_groups>1 is for + ResNeXt like networks. + disable_pre_activation (bool): If true, disable the preactivation, + which includes BatchNorm3D and ReLU. + """ + super(ResBlock, self).__init__() + + assert skip_transformation_type in skip_transformations, ( + "unknown skip transformation: %s" % skip_transformation_type + ) + + if (dim_in != dim_out) or (spatial_stride != 1) or (temporal_stride != 1): + self.skip = skip_transformations[skip_transformation_type]( + dim_in, + dim_out, + temporal_stride, + spatial_stride, + bn_eps=bn_eps, + bn_mmt=bn_mmt, + disable_pre_activation=disable_pre_activation, + ) + + assert residual_transformation_type in residual_transformations, ( + "unknown residual transformation: %s" % residual_transformation_type + ) + self.residual = residual_transformations[residual_transformation_type]( + dim_in, + dim_out, + temporal_stride, + spatial_stride, + num_groups, + dim_inner, + temporal_kernel_size=temporal_kernel_size, + temporal_conv_1x1=temporal_conv_1x1, + disable_pre_activation=disable_pre_activation, + ) + self.relu = nn.ReLU(inplace_relu) + + def forward(self, x): + if hasattr(self, "skip"): + x = self.skip(x) + self.residual(x) + else: + x = x + self.residual(x) + x = self.relu(x) + return x diff --git a/classy_vision/models/resnext3d_stage.py b/classy_vision/models/resnext3d_stage.py new file mode 100644 index 0000000000..1597c04219 --- /dev/null +++ b/classy_vision/models/resnext3d_stage.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from collections import OrderedDict + +import torch.nn as nn + +from .resnext3d_block import ResBlock + + +class ResStageBase(nn.Module): + def __init__( + self, + stage_idx, + dim_in, + dim_out, + dim_inner, + temporal_kernel_basis, + temporal_conv_1x1, + temporal_stride, + spatial_stride, + num_blocks, + num_groups, + ): + super(ResStageBase, self).__init__() + + assert ( + len( + { + len(dim_in), + len(dim_out), + len(temporal_kernel_basis), + len(temporal_conv_1x1), + len(temporal_stride), + len(spatial_stride), + len(num_blocks), + len(dim_inner), + len(num_groups), + } + ) + == 1 + ) + + self.stage_idx = stage_idx + self.num_blocks = num_blocks + self.num_pathways = len(self.num_blocks) + + self.temporal_kernel_sizes = [ + (temporal_kernel_basis[i] * num_blocks[i])[: num_blocks[i]] + for i in range(len(temporal_kernel_basis)) + ] + + def _block_name(self, pathway_idx, stage_idx, block_idx): + return "pathway{}-stage{}-block{}".format(pathway_idx, stage_idx, block_idx) + + def _pathway_name(self, pathway_idx): + return "pathway{}".format(pathway_idx) + + def forward(self, inputs): + output = [] + for p in range(self.num_pathways): + x = inputs[p] + pathway_module = getattr(self, self._pathway_name(p)) + output.append(pathway_module(x)) + return output + + +class ResStage(ResStageBase): + """ + Stage of 3D ResNet. It expects to have one or more tensors as input for + single pathway (C2D, I3D, SlowOnly), and multi-pathway (SlowFast) cases. + More details can be found here: + "Slowfast networks for video recognition." + https://arxiv.org/pdf/1812.03982.pdf + """ + + def __init__( + self, + stage_idx, + dim_in, + dim_out, + dim_inner, + temporal_kernel_basis, + temporal_conv_1x1, + temporal_stride, + spatial_stride, + num_blocks, + num_groups, + skip_transformation_type, + residual_transformation_type, + block_callback=None, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + disable_pre_activation=False, + final_stage=False, + ): + """ + The `__init__` method of any subclass should also contain these arguments. + ResStage builds p streams, where p can be greater or equal to one. + Args: + stage_idx (int): integer index of stage. + dim_in (list): list of p the channel dimensions of the input. + Different channel dimensions control the input dimension of + different pathways. + dim_out (list): list of p the channel dimensions of the output. + Different channel dimensions control the input dimension of + different pathways. + dim_inner (list): list of the p inner channel dimensions of the + input. + Different channel dimensions control the input dimension of + different pathways. + temporal_kernel_basis (list): Basis of temporal kernel sizes for each of + the stage. + temporal_conv_1x1 (list): Only useful for BottleneckBlock. + In a pathaway, if True, do temporal convolution in the fist 1x1 Conv3d. + Otherwise, do it in the second 3x3 Conv3d + temporal_stride (list): the temporal stride of the bottleneck. + spatial_stride (list): the spatial_stride of the bottleneck. + num_blocks (list): list of p numbers of blocks for each of the + pathway. + num_groups (list): list of number of p groups for the convolution. + num_groups=1 is for standard ResNet like networks, and + num_groups>1 is for ResNeXt like networks. + skip_transformation_type (str): the type of skip transformation + residual_transformation_type (str): the type of residual transformation + block_callback (function object): a callback function to be called with + residual block and its name as input arguments + disable_pre_activation (bool): If true, disable the preactivation, + which includes BatchNorm3D and ReLU. + final_stage (bool): If true, this is the last stage in the model. + """ + super(ResStage, self).__init__( + stage_idx, + dim_in, + dim_out, + dim_inner, + temporal_kernel_basis, + temporal_conv_1x1, + temporal_stride, + spatial_stride, + num_blocks, + num_groups, + ) + + for p in range(self.num_pathways): + blocks = [] + for i in range(self.num_blocks[p]): + # Retrieve the transformation function. + # Construct the block. + block_disable_pre_activation = ( + True if disable_pre_activation and i == 0 else False + ) + res_block = ResBlock( + dim_in[p] if i == 0 else dim_out[p], + dim_out[p], + dim_inner[p], + self.temporal_kernel_sizes[p][i], + temporal_conv_1x1[p], + temporal_stride[p] if i == 0 else 1, + spatial_stride[p] if i == 0 else 1, + skip_transformation_type, + residual_transformation_type, + num_groups=num_groups[p], + inplace_relu=inplace_relu, + bn_eps=bn_eps, + bn_mmt=bn_mmt, + disable_pre_activation=block_disable_pre_activation, + ) + block_name = self._block_name(p, stage_idx, i) + if block_callback: + res_block = block_callback(block_name, res_block) + blocks.append((block_name, res_block)) + + if final_stage and ( + residual_transformation_type == "preactivated_bottleneck_transformation" + ): + # For pre-activation residual transformation, we conduct + # activation in the final stage before continuing forward pass + # through the head + activate_bn = nn.BatchNorm3d(dim_out[p]) + activate_relu = nn.ReLU(inplace=True) + activate_bn_name = "-".join([block_name, "bn"]) + activate_relu_name = "-".join([block_name, "relu"]) + if block_callback: + activate_relu = block_callback(activate_relu_name, activate_relu) + blocks.append((activate_bn_name, activate_bn)) + blocks.append((activate_relu_name, activate_relu)) + + self.add_module(self._pathway_name(p), nn.Sequential(OrderedDict(blocks))) diff --git a/classy_vision/models/resnext3d_stem.py b/classy_vision/models/resnext3d_stem.py new file mode 100644 index 0000000000..880fa8bb48 --- /dev/null +++ b/classy_vision/models/resnext3d_stem.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch.nn as nn + + +class ResNeXt3DStemSinglePathway(nn.Module): + """ + ResNe(X)t 3D basic stem module. Assume a single pathway. + Performs spatiotemporal Convolution, BN, and Relu following by a + spatiotemporal pooling. + """ + + def __init__( + self, + dim_in, + dim_out, + kernel, + stride, + padding, + maxpool=True, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + ): + """ + The `__init__` method of any subclass should also contain these arguments. + + Args: + dim_in (int): the channel dimension of the input. Normally 3 is used + for rgb input + dim_out (int): the output dimension of the convolution in the stem + layer. + kernel (list): the kernel size of the convolution in the stem layer. + temporal kernel size, height kernel size, width kernel size in + order. + stride (list): the stride size of the convolution in the stem layer. + temporal kernel stride, height kernel size, width kernel size in + order. + padding (int): the padding size of the convolution in the stem + layer, temporal padding size, height padding size, width + padding size in order. + maxpool (bool): If true, perform max pooling. + inplace_relu (bool): calculate the relu on the original input + without allocating new memory. + bn_eps (float): epsilon for batch norm. + bn_mmt (float): momentum for batch norm. Noted that BN momentum in + PyTorch = 1 - BN momentum in Caffe2. + """ + super(ResNeXt3DStemSinglePathway, self).__init__() + self.kernel = kernel + self.stride = stride + self.padding = padding + self.inplace_relu = inplace_relu + self.bn_eps = bn_eps + self.bn_mmt = bn_mmt + self.maxpool = maxpool + + # Construct the stem layer. + self._construct_stem(dim_in, dim_out) + + def _construct_stem(self, dim_in, dim_out): + self.conv = nn.Conv3d( + dim_in, + dim_out, + self.kernel, + stride=self.stride, + padding=self.padding, + bias=False, + ) + self.bn = nn.BatchNorm3d(dim_out, eps=self.bn_eps, momentum=self.bn_mmt) + self.relu = nn.ReLU(self.inplace_relu) + if self.maxpool: + self.pool_layer = nn.MaxPool3d( + kernel_size=[1, 3, 3], stride=[1, 2, 2], padding=[0, 1, 1] + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.relu(x) + if self.maxpool: + x = self.pool_layer(x) + return x + + +class ResNeXt3DStemMultiPathway(nn.Module): + """ + Video 3D stem module. Provides stem operations of Conv, BN, ReLU, MaxPool + on input data tensor for one or multiple pathways. + """ + + def __init__( + self, + dim_in, + dim_out, + kernel, + stride, + padding, + inplace_relu=True, + bn_eps=1e-5, + bn_mmt=0.1, + maxpool=(True,), + ): + """ + The `__init__` method of any subclass should also contain these + arguments. List size of 1 for single pathway models (C2D, I3D, SlowOnly + and etc), list size of 2 for two pathway models (SlowFast). + + Args: + dim_in (list): the list of channel dimensions of the inputs. + dim_out (list): the output dimension of the convolution in the stem + layer. + kernel (list): the kernels' size of the convolutions in the stem + layers. Temporal kernel size, height kernel size, width kernel + size in order. + stride (list): the stride sizes of the convolutions in the stem + layer. Temporal kernel stride, height kernel size, width kernel + size in order. + padding (list): the paddings' sizes of the convolutions in the stem + layer. Temporal padding size, height padding size, width padding + size in order. + inplace_relu (bool): calculate the relu on the original input + without allocating new memory. + bn_eps (float): epsilon for batch norm. + bn_mmt (float): momentum for batch norm. Noted that BN momentum in + PyTorch = 1 - BN momentum in Caffe2. + maxpool (iterable): At training time, when crop size is 224 x 224, do max + pooling. When crop size is 112 x 112, skip max pooling. + Default value is a (True,) + """ + super(ResNeXt3DStemMultiPathway, self).__init__() + + assert ( + len({len(dim_in), len(dim_out), len(kernel), len(stride), len(padding)}) + == 1 + ), "Input pathway dimensions are not consistent." + self.num_pathways = len(dim_in) + self.kernel = kernel + self.stride = stride + self.padding = padding + self.inplace_relu = inplace_relu + self.bn_eps = bn_eps + self.bn_mmt = bn_mmt + self.maxpool = maxpool + + # Construct the stem layer. + self._construct_stem(dim_in, dim_out) + + def _construct_stem(self, dim_in, dim_out): + assert type(dim_in) == list + assert all(dim > 0 for dim in dim_in) + assert type(dim_out) == list + assert all(dim > 0 for dim in dim_out) + + self.blocks = {} + for p in range(len(dim_in)): + stem = ResNeXt3DStemSinglePathway( + dim_in[p], + dim_out[p], + self.kernel[p], + self.stride[p], + self.padding[p], + inplace_relu=self.inplace_relu, + bn_eps=self.bn_eps, + bn_mmt=self.bn_mmt, + maxpool=self.maxpool[p], + ) + stem_name = self._stem_name(p) + self.add_module(stem_name, stem) + self.blocks[stem_name] = stem + + def _stem_name(self, path_idx): + return "stem-path{}".format(path_idx) + + def forward(self, x): + assert ( + len(x) == self.num_pathways + ), "Input tensor does not contain {} pathway".format(self.num_pathways) + for p in range(len(x)): + stem_name = self._stem_name(p) + x[p] = self.blocks[stem_name](x[p]) + return x + + +class ResNeXt3DStem(nn.Module): + def __init__( + self, temporal_kernel, spatial_kernel, input_planes, stem_planes, maxpool + ): + super(ResNeXt3DStem, self).__init__() + self.stem = ResNeXt3DStemMultiPathway( + [input_planes], + [stem_planes], + [[temporal_kernel, spatial_kernel, spatial_kernel]], + [[1, 2, 2]], # stride + [ + [temporal_kernel // 2, spatial_kernel // 2, spatial_kernel // 2] + ], # padding + maxpool=[maxpool], + ) + + def forward(self, x): + return self.stem(x) diff --git a/classy_vision/optim/__init__.py b/classy_vision/optim/__init__.py new file mode 100644 index 0000000000..d061e30a32 --- /dev/null +++ b/classy_vision/optim/__init__.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_optimizer import ClassyOptimizer + + +FILE_ROOT = Path(__file__).parent + + +OPTIMIZER_REGISTRY = {} +OPTIMIZER_CLASS_NAMES = set() + + +def build_optimizer(config): + """Builds a ClassyOptimizer from a config. + + This assumes a 'name' key in the config which is used to determine what + optimizer class to instantiate. For instance, a config `{"name": "my_optimizer", + "foo": "bar"}` will find a class that was registered as "my_optimizer" + (see :func:`register_optimizer`) and call .from_config on it.""" + return OPTIMIZER_REGISTRY[config["name"]].from_config(config) + + +def register_optimizer(name): + """Registers a ClassyOptimizer subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyOptimizer from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyOptimizer subclass, like this: + + .. code-block:: python + + @register_optimizer('my_optimizer') + class MyOptimizer(ClassyOptimizer): + ... + + To instantiate an optimizer from a configuration file, see + :func:`build_optimizer`.""" + + def register_optimizer_cls(cls): + if name in OPTIMIZER_REGISTRY: + raise ValueError("Cannot register duplicate optimizer ({})".format(name)) + if not issubclass(cls, ClassyOptimizer): + raise ValueError( + "Optimizer ({}: {}) must extend ClassyVisionOptimizer".format( + name, cls.__name__ + ) + ) + if cls.__name__ in OPTIMIZER_CLASS_NAMES: + raise ValueError( + "Cannot register optimizer with duplicate class name({})".format( + cls.__name__ + ) + ) + OPTIMIZER_REGISTRY[name] = cls + OPTIMIZER_CLASS_NAMES.add(cls.__name__) + return cls + + return register_optimizer_cls + + +# automatically import any Python files in the optim/ directory +import_all_modules(FILE_ROOT, "classy_vision.optim") + +from .rmsprop import RMSProp # isort:skip +from .sgd import SGD # isort:skip + +__all__ = ["ClassyOptimizer", "RMSProp", "SGD"] diff --git a/classy_vision/optim/classy_optimizer.py b/classy_vision/optim/classy_optimizer.py new file mode 100644 index 0000000000..6ef3aca8ac --- /dev/null +++ b/classy_vision/optim/classy_optimizer.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Callable, Dict, Optional + +import torch +from classy_vision.models import ClassyModel + +from .param_scheduler.classy_vision_param_scheduler import ( + ClassyParamScheduler, + UpdateInterval, +) + + +class ClassyOptimizer: + """ + Base class for classy optimizers. + + This wraps a :class:`torch.optim.Optimizer` instance, handles learning + rate scheduling by using a :class:`param_scheduler.ClassyParamScheduler` + and supports specifying regularized and unregularized param groups. + Specifying unregularized params is especially useful to avoid applying + weight decay on batch norm. See + :func:`classy_vision.models.ClassyModel.get_optimizer_params` for more + information. + + Deriving classes can extend functionality be overriding the appropriate functions. + """ + + def __init__(self, lr_scheduler: ClassyParamScheduler): + """ + Constructor for ClassyOptimizer. + + Args: + lr_scheduler: The learning rate scheduler to use. + """ + self.lr_scheduler = lr_scheduler + self.lr = self.lr_scheduler(0) + self.optimizer = None + self.optimizer_params = None + + def _validate_and_get_optimizer_params(self, model: ClassyModel) -> Dict[str, Any]: + """ + Validate and return the optimizer params. + + The optimizer params are fetched from + :fun:`models.ClassyModel.get_optimizer_params`. + + Args: + model: The model to get the params from. + + Returns: + A dict containing "regularized_params" and "unregularized_params". + Weight decay will only be applied to "regularized_params". + """ + if isinstance(model, torch.nn.parallel.DistributedDataParallel): + optimizer_params = model.module.get_optimizer_params() + else: + optimizer_params = model.get_optimizer_params() + + assert isinstance(optimizer_params, dict) and set(optimizer_params.keys()) == { + "regularized_params", + "unregularized_params", + }, "get_optimizer_params() of {0} should return dict with exact two keys\ + 'regularized_params', 'unregularized_params'".format( + type(model).__name__ + ) + + trainable_params = [ + params for params in model.parameters() if params.requires_grad + ] + assert len(trainable_params) == len( + optimizer_params["regularized_params"] + ) + len(optimizer_params["unregularized_params"]), ( + "get_optimizer_params() of {0} should return params that cover all" + "trainable params of model".format(type(model).__name__) + ) + + return optimizer_params + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyOptimizer": + """Instantiates a ClassyOptimizer from a configuration. + + Args: + config: A configuration for the ClassyOptimizer. + + Returns: + A ClassyOptimizer instance. + """ + raise NotImplementedError + + @property + def parameters(self) -> Dict[str, Any]: + """ + Get the parameters of the optimizer which need to be overridden. All optimizer + param groups will use these parameters. + + Returns: + A kwarg dictionary that will be used to override optimizer args. + """ + return {"lr": self.lr} + + def init_pytorch_optimizer(self, model: ClassyModel) -> None: + """ + Initialize the underlying :class:`torch.optim.Optimizer` instance. + + Using the provided model, create param groups for the optimizer with a + weight decay override for params which should be left unregularized. + + Note: + Deriving classes should initialize the underlying Pytorch optimizer + in this call. The simplest way to do this after a call to + + ``super().init_pytorch_optimizer()`` + + Warning: + This should called only after the model has been moved to the correct + device. + """ + self.optimizer_params = self._validate_and_get_optimizer_params(model) + + param_groups_override = [] + self.contains_unregularized_params = False + if len(self.optimizer_params["unregularized_params"]) != 0: + param_groups_override.append( + { + "params": self.optimizer_params["unregularized_params"], + "weight_decay": 0.0, + } + ) + self.contains_unregularized_params = True + + if len(self.optimizer_params["regularized_params"]) != 0: + param_groups_override.append( + {"params": self.optimizer_params["regularized_params"]} + ) + self.param_groups_override = param_groups_override + + def get_classy_state(self) -> Dict[str, Any]: + """Get the state of the ClassyOptimizer. + + The returned state is used for checkpointing. + + Returns: + A state dictionary containing the state of the optimizer. + """ + return {"optim": self.optimizer.state_dict(), "parameters": self.parameters} + + def set_classy_state(self, state: Dict[str, Any]) -> None: + """Set the state of the ClassyOptimizer. + + Args: + state_dict: The state dictionary. Must be the output of a call to + :func:`get_classy_state`. + + This is used to load the state of the optimizer from a checkpoint. + """ + self.optimizer.load_state_dict(state["optim"]) + for param_name, param_value in state["parameters"].items(): + setattr(self, param_name, param_value) + + def backward(self, loss: torch.Tensor) -> None: + """ + Computer gradients with respect to the loss. + + Calls :func:`zero_grad` and then computes the gradient using + `torch.Tensor.backward `_. See :mod:`torch.autograd` for + more information. + """ + # TODO (aadcock): Add gradient accumulation logic + self.zero_grad() + loss.backward() + + def update_schedule_on_epoch(self, where: float) -> None: + """ + Update the param schedule at the end of an epoch. + + This should be called by the task at the end of every epoch to update the + schedule of epoch based param schedulers (See + :class:`param_scheduler.ClassyParamScheduler` for more information). + + Args: + where: where we are in terms of training progress (output of + :func:`tasks.ClassyTask.where`) + """ + assert self.lr_scheduler.update_interval in [ + UpdateInterval.EPOCH, + UpdateInterval.STEP, + ] + + if self.lr_scheduler.update_interval == UpdateInterval.EPOCH: + self._update_schedule(where) + + def update_schedule_on_step(self, where: float) -> None: + """ + Update the param schedule at the end of a train step. + + This should be called by the task at the end of every train step ( + :func:`tasks.ClassyTask.train_step`) to update the schedule of step + based param schedulers (See :class:`param_scheduler.ClassyParamScheduler` + for more information). + + Args: + where: where we are in terms of training progress (output of + :method:`ClassyTask.where`) + """ + assert self.lr_scheduler.update_interval in [ + UpdateInterval.EPOCH, + UpdateInterval.STEP, + ] + + if self.lr_scheduler.update_interval == UpdateInterval.STEP: + self._update_schedule(where) + + def _update_schedule(self, where: float) -> None: + """ + Args: + where: where we are in terms of training progress (output of + :func:`tasks.ClassyTask.where`) + """ + self.lr = self.lr_scheduler(where) + for group in self.optimizer.param_groups: + group.update(self.parameters) + + # Here there's an assumption that pytorch optimizer maintain the order of + # param_groups and batch_norm param_group is 0th param_group as initially + # set in the __init__ call. + # It seems like pytorch optim doesn't have way to get params by 'id': + # See thread https://github.com/pytorch/pytorch/issues/1489 + if self.contains_unregularized_params: + self.optimizer.param_groups[0].update(weight_decay=0.0) + + def step(self, closure: Optional[Callable] = None): + """ + Performs a single optimization step. + + See `torch.optim.Optimizer.step `_for more information. + + Args: + closure: A closure that re-evaluates the model and returns the loss + """ + self.optimizer.step(closure) + + def zero_grad(self): + """ + Clears the gradients of all optimized parameters. + + See `torch.optim.Optimizer.zero_grad `_ for more information. + """ + self.optimizer.zero_grad() diff --git a/classy_vision/optim/param_scheduler/__init__.py b/classy_vision/optim/param_scheduler/__init__.py new file mode 100644 index 0000000000..2797955444 --- /dev/null +++ b/classy_vision/optim/param_scheduler/__init__.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_vision_param_scheduler import ( # noqa F401 + ClassyParamScheduler, + UpdateInterval, +) + + +FILE_ROOT = Path(__file__).parent + + +PARAM_SCHEDULER_REGISTRY = {} + + +def build_param_scheduler(config): + """Builds a :class:`ClassyParamScheduler` from a config. + + This assumes a 'name' key in the config which is used to determine what + param scheduler class to instantiate. For instance, a config `{"name": + "my_scheduler", "foo": "bar"}` will find a class that was registered as + "my_scheduler" (see :func:`register_param_scheduler`) and call .from_config + on it.""" + return PARAM_SCHEDULER_REGISTRY[config["name"]].from_config(config) + + +def register_param_scheduler(name): + """Registers a :class:`ClassyParamScheduler` subclass. + + This decorator allows Classy Vision to instantiate a subclass of + ClassyParamScheduler from a configuration file, even if the class itself is not + part of the Classy Vision framework. To use it, apply this decorator to a + ClassyParamScheduler subclass, like this: + + .. code-block:: python + + @register_param_scheduler('my_scheduler') + class MyParamScheduler(ClassyParamScheduler): + ... + + To instantiate a param scheduler from a configuration file, see + :func:`build_param_scheduler`.""" + + def register_param_scheduler_cls(cls): + if name in PARAM_SCHEDULER_REGISTRY: + raise ValueError( + "Cannot register duplicate param scheduler ({})".format(name) + ) + if not issubclass(cls, ClassyParamScheduler): + raise ValueError( + "Param Scheduler ({}: {}) must extend ClassyParamScheduler".format( + name, cls.__name__ + ) + ) + PARAM_SCHEDULER_REGISTRY[name] = cls + return cls + + return register_param_scheduler_cls + + +# automatically import any Python files in the optim/param_scheduler/ directory +import_all_modules(FILE_ROOT, "classy_vision.optim.param_scheduler") + +from .composite_scheduler import CompositeParamScheduler # isort:skip +from .constant_scheduler import ConstantParamScheduler # isort:skip +from .cosine_scheduler import CosineParamScheduler # isort:skip +from .linear_scheduler import LinearParamScheduler # isort:skip +from .multi_step_scheduler import MultiStepParamScheduler # isort:skip +from .polynomial_decay_scheduler import PolynomialDecayParamScheduler # isort:skip +from .step_scheduler import StepParamScheduler # isort:skip +from .step_with_fixed_gamma_scheduler import ( # isort:skip + StepWithFixedGammaParamScheduler, +) + +__all__ = [ + "ClassyParamScheduler", + "CompositeParamScheduler", + "ConstantParamScheduler", + "CosineParamScheduler", + "LinearParamScheduler", + "MultiStepParamScheduler", + "PolynomialDecayParamScheduler", + "StepParamScheduler", + "StepWithFixedGammaParamScheduler", + "build_param_scheduler", + "register_param_scheduler" +] diff --git a/classy_vision/optim/param_scheduler/classy_vision_param_scheduler.py b/classy_vision/optim/param_scheduler/classy_vision_param_scheduler.py new file mode 100644 index 0000000000..e64070708d --- /dev/null +++ b/classy_vision/optim/param_scheduler/classy_vision_param_scheduler.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from enum import Enum +from typing import Any, Dict + + +class UpdateInterval(Enum): + """ + Enum for specifying update frequency for scheduler. + + Attributes: + EPOCH (str): Update param before each epoch + STEP (str): Update param before each optimizer step + """ + + EPOCH = "epoch" + STEP = "step" + + +class ClassyParamScheduler(object): + """ + Base class for Classy parameter schedulers. + + Attributes: + update_interval: Specifies how often to update each parameter + (before each epoch or each batch) + """ + + # To be used for comparisons with where + WHERE_EPSILON = 1e-6 + + def __init__(self, update_interval: UpdateInterval = UpdateInterval.EPOCH): + """ + Constructor for ClassyParamScheduler + + Args: + update_interval: Specifies the frequency of the param updates + """ + self.update_interval = update_interval + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyParamScheduler": + """Instantiates a ClassyParamScheduler from a configuration. + + Args: + config: A configuration for the ClassyParamScheduler. + + Returns: + A ClassyParamScheduler instance. + """ + raise NotImplementedError + + def __call__(self, where: float): + """ + Get the param for a given point at training. + + For Classy Vision we update params (such as learning rate) based on + the percent progress of training completed. This allows a + scheduler to be agnostic to the exact specifications of a + particular run (e.g. 120 epochs vs 90 epochs). + + Args: + where: A float in [0;1) that represents how far training has progressed + + """ + raise NotImplementedError("Param schedulers must override __call__") diff --git a/classy_vision/optim/param_scheduler/composite_scheduler.py b/classy_vision/optim/param_scheduler/composite_scheduler.py new file mode 100644 index 0000000000..ef06730fd7 --- /dev/null +++ b/classy_vision/optim/param_scheduler/composite_scheduler.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from enum import Enum, auto +from typing import Any, Dict, Sequence + +from . import ( + ClassyParamScheduler, + UpdateInterval, + build_param_scheduler, + register_param_scheduler, +) + + +@register_param_scheduler("composite") +class CompositeParamScheduler(ClassyParamScheduler): + """ + Composite parameter scheduler composed of intermediate schedulers. + Takes a list of schedulers and a list of lengths corresponding to + percentage of training each scheduler should run for. Schedulers + are run in order. All values in lengths should sum to 1.0. + + Each scheduler also has a corresponding interval scale. If interval + scale is 'fixed', the intermidiate scheduler will be run without any rescaling + of the time. If interval scale is 'rescaled', intermediate scheduler is + run such that each scheduler will start and end at the same values as it + would if it were the only scheduler. Default is 'fixed' for all schedulers. + + Example: + + .. code-block:: python + + update_interval = "step" + schedulers = [ + {"name": "constant", "value": 0.42}, + {"name": "cosine_decay", "start_lr": 0.42, "end_lr": 0.0001} + ] + interval_scaling = ['rescaled', 'rescaled'], + lengths = [0.3, 0.7] + + The parameter value will be 0.42 for the first [0%, 30%) of steps, + and then will cosine decay from 0.42 to 0.0001 for [30%, 100%) of + training. + """ + + class IntervalScaling(Enum): + RESCALED = auto() + FIXED = auto() + + def __init__( + self, + schedulers: Sequence[ClassyParamScheduler], + lengths: Sequence[float], + update_interval: UpdateInterval, + interval_scaling: Sequence[IntervalScaling], + ): + super().__init__() + self.update_interval = update_interval + self._lengths = lengths + self._schedulers = schedulers + self._interval_scaling = interval_scaling + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "CompositeParamScheduler": + """Instantiates a CompositeParamScheduler from a configuration. + + Args: + config: A configuration for a CompositeParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A CompositeParamScheduler instance. + """ + assert ( + "schedulers" in config and "lengths" in config + ), "Composite scheduler needs both a list of schedulers and lengths" + assert len(config["schedulers"]) == len( + config["lengths"] + ), "Schedulers and lengths must be same length" + assert ( + len(config["schedulers"]) > 0 + ), "There must be at least one scheduler in the composite scheduler" + assert ( + abs(sum(config["lengths"]) - 1.0) < 1e-3 + ), "The sum of all values in lengths must be 1" + if sum(config["lengths"]) != 1.0: + config["lengths"][-1] = 1.0 - sum(config["lengths"][:-1]) + update_interval = UpdateInterval.STEP + if "update_interval" in config: + assert config["update_interval"] in { + "step", + "epoch", + }, "Choices for update interval are 'step' or 'epoch'" + update_interval = UpdateInterval[config["update_interval"].upper()] + interval_scaling = [] + if "interval_scaling" in config: + assert len(config["schedulers"]) == len( + config["interval_scaling"] + ), "Schedulers and interval scaling must be the same length" + for interval_scale in config["interval_scaling"]: + assert interval_scale in { + "fixed", + "rescaled", + }, "Choices for interval scaline are 'fixed' or 'rescaled'" + interval_scaling.append(cls.IntervalScaling[interval_scale.upper()]) + else: + interval_scaling = [cls.IntervalScaling.RESCALED] * len( + config["schedulers"] + ) + if "num_epochs" in config: # Propogate value to intermediate schedulers + config["schedulers"] = [ + dict(schedule, **{"num_epochs": config["num_epochs"]}) + for schedule in config["schedulers"] + ] + return cls( + schedulers=[ + build_param_scheduler(scheduler) for scheduler in config["schedulers"] + ], + lengths=config["lengths"], + update_interval=update_interval, + interval_scaling=interval_scaling, + ) + + def __call__(self, where: float): + # Find scheduler corresponding to where + i = 0 + running_total = self._lengths[i] + while (where + self.WHERE_EPSILON) > running_total and i < len( + self._schedulers + ) - 1: + i += 1 + running_total += self._lengths[i] + scheduler = self._schedulers[i] + scheduler_where = where + interval_scale = self._interval_scaling[i] + if interval_scale == self.IntervalScaling.RESCALED: + # Calculate corresponding where % for scheduler + scheduler_start = running_total - self._lengths[i] + scheduler_where = (where - scheduler_start) / self._lengths[i] + return scheduler(scheduler_where) diff --git a/classy_vision/optim/param_scheduler/constant_scheduler.py b/classy_vision/optim/param_scheduler/constant_scheduler.py new file mode 100644 index 0000000000..6f973bc81b --- /dev/null +++ b/classy_vision/optim/param_scheduler/constant_scheduler.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +from . import ClassyParamScheduler, register_param_scheduler + + +@register_param_scheduler("constant") +class ConstantParamScheduler(ClassyParamScheduler): + """ + Returns a constant value for a optimizer param. + """ + + def __init__(self, value: float): + super().__init__() + self._value = value + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ConstantParamScheduler": + """Instantiates a ConstantParamScheduler from a configuration. + + Args: + config: A configuration for a ConstantParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A ConstantParamScheduler instance. + """ + assert "value" in config + return cls(value=config["value"]) + + def __call__(self, where: float): + if where >= 1.0: + raise RuntimeError(f"Invalid where parameter for scheduler: {where}") + + return self._value diff --git a/classy_vision/optim/param_scheduler/cosine_scheduler.py b/classy_vision/optim/param_scheduler/cosine_scheduler.py new file mode 100644 index 0000000000..25cff0cb9e --- /dev/null +++ b/classy_vision/optim/param_scheduler/cosine_scheduler.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import math +from typing import Any, Dict + +from . import ClassyParamScheduler, register_param_scheduler + + +@register_param_scheduler("cosine") +class CosineParamScheduler(ClassyParamScheduler): + """ + Changes the param value after every epoch based on a `cosine schedule `_. + Can be used for either cosine decay or cosine warmup schedules based on + start and end values. + + Example: + + .. code-block:: python + + start_lr: 0.1 + end_lr: 0.0001 + """ + + def __init__(self, start_lr: float, end_lr: float): + super().__init__() + self._start_lr = start_lr + self._end_lr = end_lr + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "CosineParamScheduler": + """Instantiates a CosineParamScheduler from a configuration. + + Args: + config: A configuration for a CosineParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A CosineParamScheduler instance. + """ + assert ( + "start_lr" in config and "end_lr" in config + ), "Cosine scheduler requires a start_lr and a end_lr" + + return cls(start_lr=config["start_lr"], end_lr=config["end_lr"]) + + def __call__(self, where: float): + return self._end_lr + 0.5 * (self._start_lr - self._end_lr) * ( + 1 + math.cos(math.pi * where) + ) diff --git a/classy_vision/optim/param_scheduler/linear_scheduler.py b/classy_vision/optim/param_scheduler/linear_scheduler.py new file mode 100644 index 0000000000..3fc6ab1bfc --- /dev/null +++ b/classy_vision/optim/param_scheduler/linear_scheduler.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +from . import ClassyParamScheduler, register_param_scheduler + + +@register_param_scheduler("linear") +class LinearParamScheduler(ClassyParamScheduler): + """ + Linearly interpolates parameter between ``start_lr`` and ``end_lr``. + Can be used for either warmup or decay based on start and end values. + + Example: + + .. code-block:: python + + start_lr: 0.0001 + end_lr: 0.01 + Corresponds to a linear increasing schedule with values in [0.0001, 0.01) + """ + + def __init__(self, start_lr: float, end_lr: float): + super().__init__() + self._start_lr = start_lr + self._end_lr = end_lr + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "LinearParamScheduler": + """Instantiates a LinearParamScheduler from a configuration. + + Args: + config: A configuration for a LinearParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A LinearParamScheduler instance. + """ + assert ( + "start_lr" in config and "end_lr" in config + ), "Linear scheduler requires a start and a end" + return cls(start_lr=config["start_lr"], end_lr=config["end_lr"]) + + def __call__(self, where: float): + # interpolate between start and end values + return self._end_lr * where + self._start_lr * (1 - where) diff --git a/classy_vision/optim/param_scheduler/multi_step_scheduler.py b/classy_vision/optim/param_scheduler/multi_step_scheduler.py new file mode 100644 index 0000000000..a1c22de84c --- /dev/null +++ b/classy_vision/optim/param_scheduler/multi_step_scheduler.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import bisect +import math +from typing import Any, Dict, List, NamedTuple, Optional, Union + +from classy_vision.generic.util import is_pos_int + +from . import ClassyParamScheduler, UpdateInterval, register_param_scheduler + + +@register_param_scheduler("multistep") +class MultiStepParamScheduler(ClassyParamScheduler): + """ + Takes a predefined schedule for a param value, and a list of epochs + which stand for the upper boundary (excluded) of each range. + + Example: + + .. code-block:: python + + values: [0.1, 0.01, 0.001, 0.0001] + milestones = [30, 60, 80] + num_epochs = 120 + + Then the param value will be 0.1 for epochs 0-29, 0.01 for + epochs 30-59, 0.001 for epochs 60-79, 0.0001 for epochs after epoch 80. + Note that the length of values must be equal to the length of milestones + plus one. + """ + + def __init__( + self, + values, + num_epochs: int, + update_interval: UpdateInterval, + milestones: Optional[List[int]] = None, + ): + super().__init__(update_interval) + self._param_schedule = values + self._num_epochs = num_epochs + self._milestones = milestones + + if milestones is None: + # Default equispaced drop_epochs behavior + self._milestones = [] + step_width = math.ceil(self._num_epochs / float(len(self._param_schedule))) + for idx in range(len(self._param_schedule) - 1): + self._milestones.append(step_width * (idx + 1)) + + start_epoch = 0 + for milestone in self._milestones: + # Do not exceed the total number of epochs + assert milestone < self._num_epochs, ( + "Epoch milestone must be smaller than total number of epochs: num_epochs=%d, milestone=%d" + % (self._num_epochs, milestone) + ) + # Must be in ascending order + assert start_epoch < milestone, ( + "Epoch milestone must be smaller than start epoch: start_epoch=%d, milestone=%d" + % (start_epoch, milestone) + ) + start_epoch = milestone + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "MultiStepParamScheduler": + """Instantiates a MultiStepParamScheduler from a configuration. + + Args: + config: A configuration for a MultiStepParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A MultiStepParamScheduler instance. + """ + assert ( + "values" in config + and isinstance(config["values"], list) + and len(config["values"]) > 0 + ), "Non-Equi Step scheduler requires a list of at least one param value" + assert is_pos_int(config["num_epochs"]), "Num epochs must be a positive integer" + assert config["num_epochs"] >= len( + config["values"] + ), "Num epochs must be greater than param schedule" + + milestones = config.get("milestones", None) + if "milestones" in config: + assert ( + isinstance(config["milestones"], list) + and len(config["milestones"]) == len(config["values"]) - 1 + ), ( + "Non-Equi Step scheduler requires a list of %d epochs" + % (len(config["values"]) - 1) + ) + return cls( + values=config["values"], + num_epochs=config["num_epochs"], + update_interval=UpdateInterval(config.get("update_interval", "epoch")), + milestones=milestones, + ) + + def __call__(self, where: float): + epoch_num = int((where + self.WHERE_EPSILON) * self._num_epochs) + return self._param_schedule[bisect.bisect_right(self._milestones, epoch_num)] diff --git a/classy_vision/optim/param_scheduler/polynomial_decay_scheduler.py b/classy_vision/optim/param_scheduler/polynomial_decay_scheduler.py new file mode 100644 index 0000000000..db91502be9 --- /dev/null +++ b/classy_vision/optim/param_scheduler/polynomial_decay_scheduler.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +from . import ClassyParamScheduler, register_param_scheduler + + +@register_param_scheduler("polynomial") +class PolynomialDecayParamScheduler(ClassyParamScheduler): + """ + Decays the param value after every epoch according to a + polynomial function with a fixed power. + + Example: + + .. code-block:: python + + base_lr: 0.1 + power: 0.9 + + Then the param value will be 0.1 for epoch 0, 0.099 for epoch 1, and + so on. + """ + + def __init__(self, base_lr, power): + super().__init__() + + self._base_lr = base_lr + self._power = power + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "PolynomialDecayParamScheduler": + """Instantiates a PolynomialDecayParamScheduler from a configuration. + + Args: + config: A configuration for a PolynomialDecayParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A PolynomialDecayParamScheduler instance. + """ + assert ( + "base_lr" in config and "power" in config + ), "Polynomial decay scheduler requires a base lr and a power of decay" + return cls(base_lr=config["base_lr"], power=config["power"]) + + def __call__(self, where: float): + return self._base_lr * (1 - where) ** self._power diff --git a/classy_vision/optim/param_scheduler/step_scheduler.py b/classy_vision/optim/param_scheduler/step_scheduler.py new file mode 100644 index 0000000000..5dbb46fb45 --- /dev/null +++ b/classy_vision/optim/param_scheduler/step_scheduler.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict, List, NamedTuple, Optional, Union + +from . import ClassyParamScheduler, register_param_scheduler + + +@register_param_scheduler("step") +class StepParamScheduler(ClassyParamScheduler): + """ + Takes a fixed schedule for a param value. If the length of the + fixed schedule is less than the number of epochs, then the epochs + are divided evenly among the param schedule. + + Example: + + .. code-block:: python + + values: [0.1, 0.01, 0.001, 0.0001] + num_epochs = 120 + + Then the param value will be 0.1 for epochs 0-29, 0.01 for + epochs 30-59, 0.001 for epoch 60-89, 0.0001 for epochs 90-119. + """ + + def __init__(self, num_epochs: Union[int, float], values: List[float]): + super().__init__() + + self._param_schedule = values + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "StepParamScheduler": + """Instantiates a StepParamScheduler from a configuration. + + Args: + config: A configuration for a StepParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A StepParamScheduler instance. + """ + assert ( + "values" in config + and isinstance(config["values"], list) + and len(config["values"]) > 0 + ), "Step scheduler requires a list of at least one param value" + assert config["num_epochs"] > 0, "Num epochs must be greater than 0" + + return cls(num_epochs=config["num_epochs"], values=config["values"]) + + def __call__(self, where: float): + ind = int((where + self.WHERE_EPSILON) * len(self._param_schedule)) + return self._param_schedule[ind] diff --git a/classy_vision/optim/param_scheduler/step_with_fixed_gamma_scheduler.py b/classy_vision/optim/param_scheduler/step_with_fixed_gamma_scheduler.py new file mode 100644 index 0000000000..7db70f4b35 --- /dev/null +++ b/classy_vision/optim/param_scheduler/step_with_fixed_gamma_scheduler.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +from . import ClassyParamScheduler, UpdateInterval, register_param_scheduler +from .step_scheduler import StepParamScheduler + + +@register_param_scheduler("step_with_fixed_gamma") +class StepWithFixedGammaParamScheduler(ClassyParamScheduler): + """ + Decays the param value by gamma at equal number of steps so as to have the + specified total number of decays. + + Example: + + .. code-block:: python + + base_lr: 0.1 + gamma: 0.1 + num_decays: 3 + num_epochs: 120 + + Then the param value will be 0.1 for epochs 0-29, 0.01 for + epochs 30-59, 0.001 for epoch 60-89, 0.0001 for epochs 90-119. + """ + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "StepWithFixedGammaParamScheduler": + """Instantiates a StepWithFixedGammaParamScheduler from a configuration. + + Args: + config: A configuration for a StepWithFixedGammaParamScheduler. + See :func:`__init__` for parameters expected in the config. + + Returns: + A StepWithFixedGammaParamScheduler instance. + """ + for key in ["base_lr", "gamma", "num_decays", "num_epochs"]: + assert key in config, f"Step with fixed decay scheduler requires: {key}" + for key in ["base_lr", "gamma"]: + assert ( + isinstance(config[key], (int, float)) and config[key] > 0 + ), f"{key} must be a positive number" + for key in ["num_decays", "num_epochs"]: + assert ( + isinstance(config[key], int) and config[key] > 0 + ), f"{key} must be a positive integer" + + return cls( + base_lr=config["base_lr"], + num_decays=config["num_decays"], + gamma=config["gamma"], + num_epochs=config["num_epochs"], + ) + + def __init__(self, base_lr, num_decays, gamma, num_epochs): + super().__init__() + + self.base_lr = base_lr + self.num_decays = num_decays + self.gamma = gamma + self.num_epochs = num_epochs + values = [base_lr] + for _ in range(num_decays): + values.append(values[-1] * gamma) + + self._step_param_scheduler = StepParamScheduler( + num_epochs=num_epochs, values=values + ) + + # make this a STEP scheduler + self.update_interval = UpdateInterval.STEP + + def __call__(self, where: float) -> float: + return self._step_param_scheduler(where) diff --git a/classy_vision/optim/rmsprop.py b/classy_vision/optim/rmsprop.py new file mode 100644 index 0000000000..f80d146d81 --- /dev/null +++ b/classy_vision/optim/rmsprop.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch.optim +from classy_vision.generic.util import is_pos_float +from classy_vision.optim.param_scheduler import build_param_scheduler + +from . import ClassyOptimizer, register_optimizer +from .param_scheduler.classy_vision_param_scheduler import ClassyParamScheduler + + +@register_optimizer("rmsprop") +class RMSProp(ClassyOptimizer): + def __init__( + self, + lr_scheduler: ClassyParamScheduler, + momentum: float, + weight_decay: float, + alpha: float, + eps: float = 1e-8, + centered: bool = False, + ) -> None: + super().__init__(lr_scheduler=lr_scheduler) + + self.momentum = momentum + self.weight_decay = weight_decay + self.alpha = alpha + self.eps = eps + self.centered = centered + + def init_pytorch_optimizer(self, model): + super().init_pytorch_optimizer(model) + self.optimizer = torch.optim.RMSprop( + self.param_groups_override, + lr=self.lr, + momentum=self.momentum, + weight_decay=self.weight_decay, + alpha=self.alpha, + eps=self.eps, + centered=self.centered, + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "RMSProp": + """Instantiates a RMSProp from a configuration. + + Args: + config: A configuration for a RMSProp. + See :func:`__init__` for parameters expected in the config. + + Returns: + A RMSProp instance. + """ + # Default params + config.setdefault("eps", 1e-8) + config.setdefault("centered", False) + + assert ( + "lr" in config + ), "Config must contain a learning rate 'lr' section for RMSProp optimizer" + for key in ["momentum", "alpha"]: + assert ( + key in config + and config[key] >= 0.0 + and config[key] < 1.0 + and type(config[key]) == float + ), f"Config must contain a '{key}' in [0, 1) for RMSProp optimizer" + for key in ["weight_decay", "eps"]: + assert key in config and is_pos_float( + config[key] + ), f"Config must contain a positive '{key}' for RMSProp optimizer" + assert "centered" in config and isinstance( + config["centered"], bool + ), "Config must contain a boolean 'centered' param for RMSProp optimizer" + + lr_config = config["lr"] + if not isinstance(lr_config, dict): + lr_config = {"name": "constant", "value": lr_config} + + lr_config["num_epochs"] = config["num_epochs"] + lr_scheduler = build_param_scheduler(lr_config) + + return cls( + lr_scheduler=lr_scheduler, + momentum=config["momentum"], + weight_decay=config["weight_decay"], + alpha=config["alpha"], + eps=config["eps"], + centered=config["centered"], + ) + + @property + def parameters(self) -> Dict[str, Any]: + return { + "lr": self.lr, + "momentum": self.momentum, + "weight_decay": self.weight_decay, + "alpha": self.alpha, + "eps": self.eps, + "centered": self.centered, + } diff --git a/classy_vision/optim/sgd.py b/classy_vision/optim/sgd.py new file mode 100644 index 0000000000..e6db404a25 --- /dev/null +++ b/classy_vision/optim/sgd.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +import torch.optim +from classy_vision.generic.util import is_pos_float +from classy_vision.optim.param_scheduler import ( + ClassyParamScheduler, + build_param_scheduler, +) + +from . import ClassyOptimizer, register_optimizer + + +@register_optimizer("sgd") +class SGD(ClassyOptimizer): + def __init__( + self, + lr_scheduler: ClassyParamScheduler, + momentum: float = 0, + weight_decay: float = 0, + nesterov=False, + ): + super().__init__(lr_scheduler=lr_scheduler) + + self.momentum = momentum + self.weight_decay = weight_decay + self.nesterov = nesterov + + def init_pytorch_optimizer(self, model): + super().init_pytorch_optimizer(model) + self.optimizer = torch.optim.SGD( + self.param_groups_override, + lr=self.lr, + nesterov=self.nesterov, + momentum=self.momentum, + weight_decay=self.weight_decay, + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "SGD": + """Instantiates a SGD from a configuration. + + Args: + config: A configuration for a SGD. + See :func:`__init__` for parameters expected in the config. + + Returns: + A SGD instance. + """ + # Default params + config["nesterov"] = config.get("nesterov", False) + + assert ( + "lr" in config + ), "Config must contain a learning rate 'lr' section for SGD optimizer" + assert ( + "momentum" in config + and config["momentum"] >= 0.0 + and config["momentum"] < 1.0 + and type(config["momentum"]) == float + ), "Config must contain a 'momentum' in [0, 1) for SGD optimizer" + assert "nesterov" in config and isinstance( + config["nesterov"], bool + ), "Config must contain a boolean 'nesterov' param for SGD optimizer" + assert "weight_decay" in config and is_pos_float( + config["weight_decay"] + ), "Config must contain a positive 'weight_decay' for SGD optimizer" + + lr_config = config["lr"] + if not isinstance(lr_config, dict): + lr_config = {"name": "constant", "value": lr_config} + + lr_config["num_epochs"] = config["num_epochs"] + lr_scheduler = build_param_scheduler(lr_config) + + return cls( + lr_scheduler=lr_scheduler, + momentum=config["momentum"], + weight_decay=config["weight_decay"], + nesterov=config["nesterov"], + ) + + @property + def parameters(self): + return { + "lr": self.lr, + "momentum": self.momentum, + "weight_decay": self.weight_decay, + "nesterov": self.nesterov, + } diff --git a/classy_vision/tasks/__init__.py b/classy_vision/tasks/__init__.py new file mode 100644 index 0000000000..17e7a013ee --- /dev/null +++ b/classy_vision/tasks/__init__.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + +from .classy_task import ClassyTask + + +FILE_ROOT = Path(__file__).parent + + +TASK_REGISTRY = {} +TASK_CLASS_NAMES = set() + + +def build_task(config): + """Builds a ClassyTask from a config. + + This assumes a 'name' key in the config which is used to determine what + task class to instantiate. For instance, a config `{"name": "my_task", + "foo": "bar"}` will find a class that was registered as "my_task" + (see :func:`register_task`) and call .from_config on it.""" + + return TASK_REGISTRY[config["name"]].from_config(config) + + +def register_task(name): + """Registers a ClassyTask subclass. + + This decorator allows Classy Vision to instantiate a subclass of ClassyTask + from a configuration file, even if the class itself is not part of the + Classy Vision framework. To use it, apply this decorator to a ClassyTask + subclass, like this: + + .. code-block:: python + + @register_task('my_task') + class MyTask(ClassyTask): + ... + + To instantiate a task from a configuration file, see :func:`build_task`.""" + + def register_task_cls(cls): + if name in TASK_REGISTRY: + raise ValueError("Cannot register duplicate task ({})".format(name)) + if not issubclass(cls, ClassyTask): + raise ValueError( + "Task ({}: {}) must extend ClassyTask".format(name, cls.__name__) + ) + if cls.__name__ in TASK_CLASS_NAMES: + raise ValueError( + "Cannot register task with duplicate class name ({})".format( + cls.__name__ + ) + ) + TASK_REGISTRY[name] = cls + TASK_CLASS_NAMES.add(cls.__name__) + return cls + + return register_task_cls + + +from .classification_task import ClassificationTask # isort:skip +from .fine_tuning_task import FineTuningTask # isort:skip + +__all__ = [ + "ClassyTask", + "FineTuningTask", + "build_task", + "register_task", + "ClassificationTask", +] + +# automatically import any Python files in the tasks/ directory +import_all_modules(FILE_ROOT, "classy_vision.tasks") diff --git a/classy_vision/tasks/classification_task.py b/classy_vision/tasks/classification_task.py new file mode 100644 index 0000000000..99695d64a7 --- /dev/null +++ b/classy_vision/tasks/classification_task.py @@ -0,0 +1,714 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import logging +from typing import Any, Dict, List, Union + +import torch +from classy_vision.dataset import ClassyDataset, build_dataset +from classy_vision.generic.distributed_util import ( + all_reduce_mean, + init_distributed_data_parallel_model, + is_distributed_training_run, +) +from classy_vision.generic.perf_stats import PerfTimer +from classy_vision.generic.util import ( + copy_model_to_gpu, + recursive_copy_to_gpu, + update_classy_state, +) +from classy_vision.losses import ClassyLoss, build_loss +from classy_vision.meters import build_meters +from classy_vision.models import ClassyModel, build_model +from classy_vision.optim import ClassyOptimizer, build_optimizer + +from . import register_task +from .classy_task import ClassyTask + + +@register_task("classification_task") +class ClassificationTask(ClassyTask): + """Basic classification training task. + + This task encapsultates all of the components and steps needed to + train a classifier using a :class:`classy_vision.trainer.ClassyTrainer`. + + Assumes a train / test phase per each epoch and that the datasets + have the same API as the map-style Dataset class in + `torch.utils.data.dataset `_ (in particular, this task makes use of + the len). If you are using an `IterableDataset `_ then a custom task + may be appropriate. + + + :var loss: Loss (see :class:`classy_vision.losses.ClassyLoss`) function used + for computing the loss in each forward pass + :var datasets: Mapping from a ``phase_type`` in ["train", "test'] + to dataset used for training (or testing) + :var meters: List of meters (see :class:`classy_vision.meters.ClassyMeter`) + to calculate during training + :var num_epochs: Number of epochs (passes over dataset) to train + :var test_only: Used to only run the test phase + :var base_model: Model to be trained, unwrapped in DDP or DP wrappers + :var optimizer: Optimizer used in train step + :var checkpoint: Serializable dict which represents state in training + :var phases: List of phase specific information, e.g. if phase is + train / test. + :var hooks: List of hooks to apply during training + :var train: Phase type, if true it means we are training, + false means testing + :var distributed_model: Base model, but wrapped in DDP (DistributedDataParallel) + :var phase_idx: Current phase id, first phase is 0, if task has not started + training then returns -1 + :var train_phase_idx: Only counts train phases + :var num_updates: Number of total parameter updates applied to model + by the optimizer + :var data_iterator: Iterator which can be used to obtain batches + :var num_samples_this_phase: Number of samples ran this phase + :var losses: Loss curve + + """ + + def __init__(self): + """Constructs a ClassificationTask + """ + super().__init__() + + self.loss = None + self.datasets = {} + self.meters = [] + self.num_epochs = 1 + self.test_only = False + self.base_model = None + self.optimizer = None + self.checkpoint = None + self.phases = [] + self.hooks = [] + self.train = True + self.distributed_model = None + self.phase_idx = -1 + self.train_phase_idx = -1 + self.num_updates = 0 + self.data_iterator = None + self.num_samples_this_phase = 0 + self.losses = [] + + def set_checkpoint(self, checkpoint): + """Sets checkpoint on task. + + Args: + checkpoint: A serializable dict representing current task state + """ + assert ( + checkpoint is None or "classy_state_dict" in checkpoint + ), "Checkpoint does not contain classy_state_dict" + self.checkpoint = checkpoint + + def set_num_epochs(self, num_epochs: Union[int, float]): + """Set number of epochs to be run. + + Args: + num_epochs: Number of epochs to run task + """ + self.num_epochs = num_epochs + return self + + def set_dataset(self, dataset: ClassyDataset, phase_type: str): + """Set dataset for phase type on task + + Args: + dataset: ClassyDataset for returning samples. + phase_type: str must be one of "train" or "test" + """ + assert phase_type in [ + "train", + "test", + ], "phase_type must be in ['train', 'test']" + self.datasets[phase_type] = dataset + return self + + def set_optimizer(self, optimizer: ClassyOptimizer): + """Set optimizer for task + + Args: + optimizer: optimizer for task + """ + self.optimizer = optimizer + return self + + def set_loss(self, loss: ClassyLoss): + """Set loss function for task + + Args: + loss: loss for task + """ + self.loss = loss + return self + + def set_meters(self, meters: List["ClassyMeter"]): + """Set meters for task + + Args: + meters: list of meters to compute during training + """ + self.meters = meters + return self + + def set_hooks(self, hooks: List["ClassyHook"]): + """Set hooks for task + + Args: + hooks: List of hooks to apply during training + """ + from classy_vision.hooks import ClassyHook + + assert isinstance(hooks, list) + assert all(isinstance(hook, ClassyHook) for hook in hooks) + assert len({hook.name() for hook in hooks}) == len( + hooks + ), "Cannot have repeated hooks of the same class" + + self.hooks = hooks + return self + + def set_model(self, model: ClassyModel): + """Set model for task + + Args: + model: Model to be trained + """ + self.base_model = model + return self + + def set_test_only(self, test_only: bool): + """Set test only flag + + Args: + test_only: If true, only test phases will be run + """ + self.test_only = test_only + return self + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassificationTask": + """Instantiates a ClassificationTask from a configuration. + + Args: + config: A configuration for a ClassificationTask. + See :func:`__init__` for parameters expected in the config. + + Returns: + A ClassificationTask instance. + """ + optimizer_config = config["optimizer"] + optimizer_config["num_epochs"] = config["num_epochs"] + + datasets = {} + phase_types = ["train", "test"] + for phase_type in phase_types: + datasets[phase_type] = build_dataset(config["dataset"][phase_type]) + loss = build_loss(config["loss"]) + test_only = config.get("test_only", False) + meters = build_meters(config.get("meters", {})) + model = build_model(config["model"]) + # put model in eval mode in case any hooks modify model states, it'll + # be reset to train mode before training + model.eval() + optimizer = build_optimizer(optimizer_config) + + task = ( + cls() + .set_num_epochs(config["num_epochs"]) + .set_loss(loss) + .set_test_only(test_only) + .set_model(model) + .set_optimizer(optimizer) + .set_meters(meters) + ) + for phase_type in phase_types: + task.set_dataset(datasets[phase_type], phase_type) + + return task + + @property + def num_batches_per_phase(self): + """Returns number of batches in current phase iterator + """ + return len(self.data_iterator) + + @property + def model(self): + """Returns model used in training (can be wrapped with DDP) + """ + return ( + self.distributed_model if is_distributed_training_run() else self.base_model + ) + + @property + def phase_type(self): + """Returns current phase type. String with value "train" or "test" + """ + return "train" if self.train else "test" + + @property + def eval_phase_idx(self): + """Returns current evaluation phase + """ + return self.phase_idx - self.train_phase_idx - 1 + + def get_data_iterator(self): + """Returns data iterator for current phase + """ + return self.data_iterator + + def get_total_training_phases(self): + """ + Returns the total number of "train" phases in the task + """ + num_training_phases = 0 + for phase in self.phases: + if phase["train"] is True: + num_training_phases += 1 + return num_training_phases + + def _build_phases(self): + """Returns list of phases from config. + + These phases will look like: + { + train: is this a train or test phase? + optimizer: optimizer settings + } + + If this is a test only run, then only test phases will be + generated, if this is a training run, then x phases = x train + phases + x test phases, interleaved. + """ + if not self.test_only: + phases = [{"train": True} for _ in range(self.num_epochs)] + + final_phases = [] + for phase in phases: + final_phases.append(phase) + final_phases.append({"train": False}) + return final_phases + + return [{"train": False} for _ in range(self.num_epochs)] + + def build_dataloader( + self, + phase_type, + num_workers, + pin_memory, + multiprocessing_context=None, + **kwargs, + ): + """Buildss a dataloader iterable for a particular phase type. + + Args: + phase_type: "train" or "test" iterable + num_workers: Number of dataloading processes. If 0, + dataloading is done on main process. See `PyTorch dataloader + documentation `_ for more details on + ``num_workers`` and the usage + of python multiprocessing in dataloaders + pin_memory: if true pin memory on GPU. See PyTorch dataloader + documentation for details on ``pin_memory``. + multiprocessing_context: Determines how processes are spawned. + Value must be one of None, "spawn", "fork", "forkserver". + If None, then context is inherited from parent process + + Returns: + Returns a iterable over the dataset + """ + return self.datasets[phase_type].iterator( + num_workers=num_workers, + pin_memory=pin_memory, + multiprocessing_context=multiprocessing_context, + **kwargs, + ) + + def build_dataloaders( + self, num_workers, pin_memory, multiprocessing_context=None, **kwargs + ): + """Build a dataloader for each phase type + + Args: + num_workers: Number of dataloading processes. If 0, + dataloading is done on main process. See `PyTorch dataloader + documentation `_ + for more details on num_workers and the usage + of python multiprocessing in dataloaders + pin_memory: if true pin memory on GPU. See PyTorch dataloader + documentation for details on pin_memory. + multiprocessing_context: Determines how processes are spawned. + Value must be one of None, "spawn", "fork", "forkserver". + If None, then context is inherited from parent process + + Returns: + Returns an iterable over the dataset associated with each phase_type + """ + return { + phase_type: self.build_dataloader( + phase_type, + num_workers=num_workers, + pin_memory=pin_memory, + multiprocessing_context=multiprocessing_context, + **kwargs, + ) + for phase_type in self.datasets.keys() + } + + def prepare( + self, + num_dataloader_workers=0, + pin_memory=False, + use_gpu=False, + dataloader_mp_context=None, + ): + """Prepares task for training, populates all derived attributes + + Args: + num_dataloader_workers: Number of dataloading processes. If 0, + dataloading is done on main process + pin_memory: if true pin memory on GPU + use_gpu: if true, load model, optimizer, loss, etc on GPU + dataloader_mp_context: Determines how processes are spawned. + Value must be one of None, "spawn", "fork", "forkserver". + If None, then context is inherited from parent process + """ + self.phases = self._build_phases() + self.dataloaders = self.build_dataloaders( + num_workers=num_dataloader_workers, + pin_memory=pin_memory, + multiprocessing_context=dataloader_mp_context, + ) + + # move the model and loss to the right device + if use_gpu: + self.loss.cuda() + self.base_model = copy_model_to_gpu(self.base_model) + else: + self.loss.cpu() + self.base_model.cpu() + + # initialize the pytorch optimizer now since the model has been moved to + # the appropriate device + self.optimizer.init_pytorch_optimizer(self.base_model) + + classy_state_dict = ( + None + if self.checkpoint is None + else self.checkpoint.get("classy_state_dict") + ) + + if classy_state_dict is not None: + state_load_success = update_classy_state(self, classy_state_dict) + assert ( + state_load_success + ), "Update classy state from checkpoint was unsuccessful." + + def init_distributed_data_parallel_model(self): + """Sets up distributed dataparallel and wraps model in DDP + """ + assert ( + self.distributed_model is None + ), "init_ddp_non_elastic must only be called once" + + self.distributed_model = init_distributed_data_parallel_model(self.base_model) + + @property + def where(self): + """Returns the proportion of training that has completed. + + Returned value is a float in the range [0, 1) + """ + current_step = self.num_updates / self.get_global_batchsize() + num_steps = self.get_total_training_phases() * self.num_batches_per_phase + where = current_step / num_steps + + assert where >= 0 and where < 1, f"Invalid where: {where}" + + return where + + def get_classy_state(self, deep_copy: bool = False): + """Returns serialiable state of task + + Args: + deep_copy: If true, does a deep copy of state before returning. + """ + classy_state_dict = { + "train": self.train, + "base_model": self.base_model.get_classy_state(), + "meters": [meter.get_classy_state() for meter in self.meters], + "optimizer": self.optimizer.get_classy_state(), + "phase_idx": self.phase_idx, + "train_phase_idx": self.train_phase_idx, + "num_updates": self.num_updates, + "num_samples_this_phase": self.num_samples_this_phase, + "losses": self.losses, + "hooks": {hook.name(): hook.get_classy_state() for hook in self.hooks}, + } + if deep_copy: + classy_state_dict = copy.deepcopy(classy_state_dict) + return classy_state_dict + + def set_classy_state(self, state): + """Set task state + + Args: + state: Dict containing state of a task + """ + self.train = state["train"] + self.base_model.set_classy_state(state["base_model"]) + for meter, meter_state in zip(self.meters, state["meters"]): + meter.set_classy_state(meter_state) + self.optimizer.set_classy_state(state["optimizer"]) + self.phase_idx = state["phase_idx"] + self.train_phase_idx = state["train_phase_idx"] + self.num_updates = state["num_updates"] + self.num_samples_this_phase = state["num_samples_this_phase"] + self.losses = state["losses"] + for hook in self.hooks: + # we still want to be able to run when new hooks are added or old + # hooks are removed + if hook.name() in state["hooks"]: + hook.set_classy_state(state["hooks"][hook.name()]) + else: + logging.warn(f"No state found for hook: {hook.name()}") + # TODO (mannatsingh): Figure out how to set the state of the dataloaders + # Re-build dataloader & re-create iterator. + self._recreate_data_loader_from_dataset() + self._reshuffle_data() + self.create_data_iterator() + # Set up pytorch module in train vs eval mode, update optimizer. + self._set_model_train_mode() + + def train_step(self, use_gpu, local_variables=None): + """Train step to be executed in train loop + + Args: + use_gpu: if true, execute training on GPU + local_variables: Dict containing intermediate values + in train_step for access by hooks + """ + from classy_vision.hooks import ClassyHookFunctions + + if local_variables is None: + local_variables = {} + + # We'll time train_step and some of its sections, and accumulate values + # into perf_stats if it were defined in local_variables: + perf_stats = local_variables.get("perf_stats", None) + timer_train_step = PerfTimer("train_step_total", perf_stats) + timer_train_step.start() + + # Process next sample + with PerfTimer("read_sample", perf_stats): + sample = next(self.get_data_iterator()) + local_variables["sample"] = sample + + assert ( + isinstance(local_variables["sample"], dict) + and "input" in local_variables["sample"] + and "target" in local_variables["sample"] + ), ( + f"Returned sample [{sample}] is not a map with 'input' and" + + "'target' keys" + ) + + self.run_hooks(local_variables, ClassyHookFunctions.on_sample.name) + + # Copy sample to GPU + local_variables["target"] = local_variables["sample"]["target"] + if use_gpu: + for key, value in local_variables["sample"].items(): + local_variables["sample"][key] = recursive_copy_to_gpu( + value, non_blocking=True + ) + + # Only need gradients during training + context = torch.enable_grad() if self.train else torch.no_grad() + with context: + # Forward pass + with PerfTimer("forward", perf_stats): + local_variables["output"] = self.model( + local_variables["sample"]["input"] + ) + + self.run_hooks(local_variables, ClassyHookFunctions.on_forward.name) + + model_output = local_variables["output"] + target = local_variables["sample"]["target"] + local_variables["local_loss"] = self.loss(model_output, target) + + # NOTE: This performs an all_reduce_mean() on the losses across the + # replicas. The reduce should ideally be weighted by the length of + # the targets on each replica. This will only be an issue when + # there are dummy samples present (once an epoch) and will only + # impact the loss reporting (slightly). + with PerfTimer("loss_allreduce", perf_stats): + local_variables["loss"] = local_variables["local_loss"].detach().clone() + local_variables["loss"] = all_reduce_mean(local_variables["loss"]) + + self.losses.append( + local_variables["loss"].data.cpu().item() + * local_variables["target"].size(0) + ) + + model_output_cpu = model_output.cpu() if use_gpu else model_output + + # Update meters + with PerfTimer("meters_update", perf_stats): + for meter in self.meters: + meter.update( + model_output_cpu, target.detach().cpu(), is_train=self.train + ) + # After both loss and meters are updated, we run hooks. Among hooks, + # `LossLrMeterLoggingHook` will log both loss and meter status + self.run_hooks(local_variables, ClassyHookFunctions.on_loss_and_meter.name) + + num_samples_in_step = self.get_global_batchsize() + self.num_samples_this_phase += num_samples_in_step + + # For training phases, run backwards pass / update optimizer + if self.train: + with PerfTimer("backward", perf_stats): + self.optimizer.backward(local_variables["local_loss"]) + + self.run_hooks(local_variables, ClassyHookFunctions.on_backward.name) + + self.optimizer.update_schedule_on_step(self.where) + with PerfTimer("optimizer_step", perf_stats): + self.optimizer.step() + + self.run_hooks(local_variables, ClassyHookFunctions.on_update.name) + + self.num_updates += num_samples_in_step + + timer_train_step.stop() + timer_train_step.record() + + def advance_phase(self): + """Performs bookkeeping / task updates between phases + + Increments phase idx, resets meters, resets loss history, + resets counters, shuffles dataset, rebuilds iterators, and + sets the train / test state for phase. + """ + logging.info("Advancing phase") + # Reset meters for next phase / epoch + for meter in self.meters: + meter.reset() + + # Reset loss history for next epoch + self.losses = [] + + # Setup new phase + self.num_samples_this_phase = 0 + self.phase_idx += 1 + phase = self.phases[self.phase_idx] + self.train = True if phase["train"] else False + if self.train: + self.train_phase_idx += 1 + + # Re-build dataloader & re-create iterator anytime membership changes. + self._recreate_data_loader_from_dataset() + self._reshuffle_data() + self.create_data_iterator() + # Set up pytorch module in train vs eval mode, update optimizer. + self._set_model_train_mode() + + def done_training(self): + """Stop condition for training + """ + return self.phase_idx + 1 >= len(self.phases) + + def _recreate_data_loader_from_dataset(self, phase_type=None): + """ + This utility is invoked to re-create the data loader object + for the current phase of execution, using the existing dataset. + This is sufficient when advancing phases. + """ + if phase_type is None: + phase_type = self.phase_type + + logging.info("Recreating data loader for new phase") + num_workers = 0 + if hasattr(self.dataloaders[phase_type], "num_workers"): + num_workers = self.dataloaders[phase_type].num_workers + pin_memory = False + if hasattr(self.dataloaders[phase_type], "pin_memory"): + pin_memory = self.dataloaders[phase_type].pin_memory + multiprocessing_context = None + if hasattr(self.dataloaders[phase_type], "multiprocessing_context"): + multiprocessing_context = self.dataloaders[ + phase_type + ].multiprocessing_context + if phase_type == "test": + current_phase_id = 0 + else: + current_phase_id = max(self.train_phase_idx, 0) + + self.dataloaders[phase_type] = self.build_dataloader( + phase_type=phase_type, + num_workers=num_workers, + pin_memory=pin_memory, + multiprocessing_context=multiprocessing_context, + current_phase_id=current_phase_id, + ) + + def _reshuffle_data(self): + """Shuffles the dataset if needed. + """ + if hasattr(self.dataloaders[self.phase_type].dataset, "do_shuffle"): + self.dataloaders[self.phase_type].dataset.do_shuffle( + epoch_num=self.phase_idx + ) + logging.info("Data shuffled.") + + def create_data_iterator(self): + """Creates data iterator for phase. + """ + # Delete iterator explicitly so that all dataloader processes + # are cleaned up. + del self.data_iterator + self.data_iterator = iter(self.dataloaders[self.phase_type]) + + def _set_model_train_mode(self): + """Set train mode for model + """ + phase = self.phases[self.phase_idx] + self.base_model.train(phase["train"]) + + if self.train and self.train_phase_idx >= 0: + self.optimizer.update_schedule_on_epoch(self.where) + + # TODO: Functions below should be better abstracted into the dataloader + # abstraction + def get_batchsize_per_replica(self): + """Return local replica's batchsize for dataset (e.g. batchsize per GPU) + """ + # TODO(T47573564) - cleaner abstraction + return self.dataloaders[self.phase_type].dataset.get_batchsize_per_replica() + + def get_global_batchsize(self): + """Return global batchsize across all trainers + """ + return self.dataloaders[self.phase_type].dataset.get_global_batchsize() + + def get_total_samples_trained_this_phase(self): + """Returns the total number of samples processed in current phase + """ + # TODO(T47573564) - cleaner abstraction + # TODO(T47387605) - instead of get_world_size, we need the max world + # size for elasticity to match parity with Uru and other systems, + # although DPP will solve this by dynamically re-sharding. + return self.num_samples_this_phase diff --git a/classy_vision/tasks/classy_task.py b/classy_vision/tasks/classy_task.py new file mode 100644 index 0000000000..08ad3dea45 --- /dev/null +++ b/classy_vision/tasks/classy_task.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +from abc import ABC, abstractmethod +from typing import Any, Dict, Optional + + +class ClassyTask(ABC): + """ + An abstract base class for a training task. + + A ClassyTask encapsulates all the components and steps needed + to train using a :class:`classy_vision.trainer.ClassyTrainer`. + """ + + def __init__(self) -> "ClassyTask": + """ + Constructs a ClassyTask. + """ + self.hooks = [] + + @classmethod + @abstractmethod + def from_config(cls, config: Dict[str, Any]) -> "ClassyTask": + """Instantiates a ClassyTask from a configuration. + + Args: + config: A configuration for a ClassyTask. + + Returns: + A ClassyTask instance. + """ + raise NotImplementedError() + + @abstractmethod + def init_distributed_data_parallel_model(self) -> None: + """ + Initialize + `torch.nn.parallel.distributed.DistributedDataParallel `_. + + Needed for distributed training. This is where a model should be wrapped by DDP. + """ + pass + + @property + @abstractmethod + def where(self) -> float: + """ + Tells how far along (where) we are during training. + + Returns: + A float in [0, 1) which tells the training progress. + """ + pass + + @abstractmethod + def advance_phase(self) -> None: + """ + Advance the task a phase. + + Called when one phase of reading from + :class:`classy_vision.dataset.ClassyDataset` is over. + """ + pass + + @abstractmethod + def done_training(self) -> bool: + """ + Tells if we are done training. + + Returns: + A boolean telling if training is over. + """ + pass + + @abstractmethod + def get_classy_state(self, deep_copy: bool = False) -> Dict[str, Any]: + """Get the state of the ClassyTask. + + The returned state is used for checkpointing. + + Args: + deep_copy: If True, creates a deep copy of the state dict. Otherwise, the + returned dict's state will be tied to the object's. + + Returns: + A state dictionary containing the state of the task. + """ + pass + + @abstractmethod + def set_classy_state(self, state): + """Set the state of the ClassyTask. + + Args: + state_dict: The state dictionary. Must be the output of a call to + :func:`get_classy_state`. + + This is used to load the state of the task from a checkpoint. + """ + pass + + @abstractmethod + def prepare( + self, + num_dataloader_workers=0, + pin_memory=False, + use_gpu=False, + dataloader_mp_context=None, + ) -> None: + """ + Prepares the task for training. + + Will be called by the :class:`classy_vision.trainer.ClassyTrainer` to + prepare the task. + + Args: + num_dataloader_workers: Number of workers to create for the dataloaders + pin_memory: Whether the dataloaders should copy the Tensors into CUDA + pinned memory (default False) + use_gpu: True if training on GPUs, False otherwise + """ + pass + + @abstractmethod + def train_step(self, use_gpu, local_variables: Optional[Dict] = None) -> None: + """ + Run a train step. + + This corresponds to training over one batch of data from the dataloaders. + + Args: + use_gpu: True if training on GPUs, False otherwise + local_variables: Local variables created in the function. Can be passed to + custom :class:`classy_vision.hooks.ClassyHook`. + """ + pass + + def run_hooks(self, local_variables: Dict[str, Any], hook_function: str) -> None: + """ + Helper function that runs a hook function for all the + :class:`classy_vision.hooks.ClassyHook`. + + Args: + local_variables: Local variables created in :func:`train_step` + hook_function: One of the hook functions in the + :class:`classy_vision.hooks.ClassyHookFunctions` + enum. + """ + for hook in self.hooks: + getattr(hook, hook_function)(self, local_variables) diff --git a/classy_vision/tasks/fine_tuning_task.py b/classy_vision/tasks/fine_tuning_task.py new file mode 100644 index 0000000000..e60dbb1937 --- /dev/null +++ b/classy_vision/tasks/fine_tuning_task.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict + +from classy_vision.generic.util import update_classy_model +from classy_vision.tasks import ClassificationTask, register_task + + +@register_task("fine_tuning") +class FineTuningTask(ClassificationTask): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.pretrained_checkpoint = None + self.reset_heads = False + self.freeze_trunk = False + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "FineTuningTask": + """Instantiates a FineTuningTask from a configuration. + + Args: + config: A configuration for a FineTuningTask. + See :func:`__init__` for parameters expected in the config. + + Returns: + A FineTuningTask instance. + """ + task = super().from_config(config) + task.set_reset_heads(config.get("reset_heads", False)) + task.set_freeze_trunk(config.get("freeze_trunk", False)) + return task + + def set_pretrained_checkpoint(self, checkpoint: Dict[str, Any]) -> "FineTuningTask": + assert ( + "classy_state_dict" in checkpoint + ), "Checkpoint does not contain classy_state_dict" + self.pretrained_checkpoint = checkpoint + return self + + def set_reset_heads(self, reset_heads: bool) -> "FineTuningTask": + self.reset_heads = reset_heads + return self + + def set_freeze_trunk(self, freeze_trunk: bool) -> "FineTuningTask": + self.freeze_trunk = freeze_trunk + return self + + def _set_model_train_mode(self): + phase = self.phases[self.phase_idx] + if self.freeze_trunk: + # convert all the sub-modules to the eval mode, except the heads + self.base_model.eval() + for heads in self.base_model.get_heads().values(): + for h in heads.values(): + h.train(phase["train"]) + else: + self.base_model.train(phase["train"]) + + if self.train and self.train_phase_idx >= 0: + self.optimizer.update_schedule_on_epoch(self.where) + + def prepare( + self, + num_dataloader_workers: int = 0, + pin_memory: bool = False, + use_gpu: bool = False, + dataloader_mp_context=None, + ) -> None: + assert ( + self.pretrained_checkpoint is not None + ), "Need a pretrained checkpoint for fine tuning" + super().prepare( + num_dataloader_workers, pin_memory, use_gpu, dataloader_mp_context + ) + if self.checkpoint is None: + # no checkpoint exists, load the model's state from the pretrained + # checkpoint + state_load_success = update_classy_model( + self.base_model, + self.pretrained_checkpoint["classy_state_dict"]["base_model"], + self.reset_heads, + ) + assert ( + state_load_success + ), "Update classy state from pretrained checkpoint was unsuccessful." + + if self.freeze_trunk: + # do not track gradients for all the parameters in the model except + # for the parameters in the heads + for param in self.base_model.parameters(): + param.requires_grad = False + for heads in self.base_model.get_heads().values(): + for h in heads.values(): + for param in h.parameters(): + param.requires_grad = True diff --git a/classy_vision/templates/synthetic/configs/template_config.json b/classy_vision/templates/synthetic/configs/template_config.json new file mode 100644 index 0000000000..daa3f0a48c --- /dev/null +++ b/classy_vision/templates/synthetic/configs/template_config.json @@ -0,0 +1,66 @@ +{ + "name": "classification_task", + "num_epochs": 2, + "loss": { + "name": "my_loss" + }, + "dataset": { + "train": { + "name": "my_dataset", + "split": "train", + "crop_size": 224, + "class_ratio": 0.5, + "num_samples": 320, + "seed": 0, + "batchsize_per_replica": 32, + "use_shuffle": true, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "RandomResizedCrop", "size": 224}, + {"name": "RandomHorizontalFlip"}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}] + }, + "test": { + "name": "my_dataset", + "split": "val", + "crop_size": 224, + "class_ratio": 0.5, + "num_samples": 100, + "seed": 1, + "batchsize_per_replica": 32, + "use_shuffle": false, + "transforms": [{"name": "generic_image_transform", "transforms": [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225] + } + ]}] + } + }, + "meters": { + "accuracy": { + "topk": [1] + } + }, + "model": { + "name": "my_model" + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "step", + "values": [0.1, 0.01] + }, + "weight_decay": 1e-4, + "momentum": 0.9 + } +} diff --git a/classy_vision/templates/synthetic/datasets/__init__.py b/classy_vision/templates/synthetic/datasets/__init__.py new file mode 100644 index 0000000000..a152ab0446 --- /dev/null +++ b/classy_vision/templates/synthetic/datasets/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + + +FILE_ROOT = Path(__file__).parent + +# Automatically import any Python files in the datasets/ directory +import_all_modules(FILE_ROOT, "datasets") diff --git a/classy_vision/templates/synthetic/datasets/my_dataset.py b/classy_vision/templates/synthetic/datasets/my_dataset.py new file mode 100644 index 0000000000..58ac50f938 --- /dev/null +++ b/classy_vision/templates/synthetic/datasets/my_dataset.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +from typing import Any, Callable, Dict, Optional, Union + +from classy_vision.dataset import ClassyDataset, register_dataset +from classy_vision.dataset.core.random_image_datasets import ( + RandomImageBinaryClassDataset, + SampleType, +) +from classy_vision.dataset.transforms import ClassyTransform, build_transforms + + +@register_dataset("my_dataset") +class MyDataset(ClassyDataset): + def __init__( + self, + batchsize_per_replica: int, + shuffle: bool, + transform: Optional[Union[ClassyTransform, Callable]], + num_samples: int, + crop_size: int, + class_ratio: float, + seed: int, + split: Optional[str] = None, + ) -> None: + dataset = RandomImageBinaryClassDataset( + crop_size, class_ratio, num_samples, seed, SampleType.TUPLE + ) + super().__init__( + dataset, split, batchsize_per_replica, shuffle, transform, num_samples + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "MyDataset": + assert all(key in config for key in ["crop_size", "class_ratio", "seed"]) + + split = config.get("split") + crop_size = config["crop_size"] + class_ratio = config["class_ratio"] + seed = config["seed"] + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + ) = cls.parse_config(config) + transform = build_transforms(transform_config) + return cls( + batchsize_per_replica, + shuffle, + transform, + num_samples, + crop_size, + class_ratio, + seed, + split=split, + ) diff --git a/classy_vision/templates/synthetic/losses/__init__.py b/classy_vision/templates/synthetic/losses/__init__.py new file mode 100644 index 0000000000..4402c47283 --- /dev/null +++ b/classy_vision/templates/synthetic/losses/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + + +FILE_ROOT = Path(__file__).parent + +# Automatically import any Python files in the losses/ directory +import_all_modules(FILE_ROOT, "losses") diff --git a/classy_vision/templates/synthetic/losses/my_loss.py b/classy_vision/templates/synthetic/losses/my_loss.py new file mode 100644 index 0000000000..8ce9c445d3 --- /dev/null +++ b/classy_vision/templates/synthetic/losses/my_loss.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch.nn.functional as F +from classy_vision.losses import ClassyLoss, register_loss + + +@register_loss("my_loss") +class MyLoss(ClassyLoss): + def forward(self, input, target): + labels = F.one_hot(target, num_classes=2).float() + return F.binary_cross_entropy(input, labels) + + @classmethod + def from_config(cls, config): + # We don't need anything from the config + return cls() diff --git a/classy_vision/templates/synthetic/models/__init__.py b/classy_vision/templates/synthetic/models/__init__.py new file mode 100644 index 0000000000..51e5b16284 --- /dev/null +++ b/classy_vision/templates/synthetic/models/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path + +from classy_vision.generic.registry_utils import import_all_modules + + +FILE_ROOT = Path(__file__).parent + +# Automatically import any Python files in the models/ directory +import_all_modules(FILE_ROOT, "models") diff --git a/classy_vision/templates/synthetic/models/my_model.py b/classy_vision/templates/synthetic/models/my_model.py new file mode 100644 index 0000000000..8cc42a9510 --- /dev/null +++ b/classy_vision/templates/synthetic/models/my_model.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import torch.nn as nn +import torchvision.models as models +from classy_vision.models import ClassyModel, register_model + + +@register_model("my_model") +class MyModel(ClassyModel): + def __init__(self): + super().__init__() + self.model = nn.Sequential( + nn.AdaptiveAvgPool2d((20, 20)), + nn.Flatten(1), + nn.Linear(3 * 20 * 20, 2), + nn.Sigmoid(), + ) + + def forward(self, x): + x = self.model(x) + return x + + @classmethod + def from_config(cls, config): + return cls() diff --git a/classy_vision/trainer/__init__.py b/classy_vision/trainer/__init__.py new file mode 100644 index 0000000000..3474b8cc79 --- /dev/null +++ b/classy_vision/trainer/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from .classy_trainer import ClassyTrainer +from .distributed_trainer import DistributedTrainer +from .local_trainer import LocalTrainer + + +__all__ = ["ClassyTrainer", "DistributedTrainer", "LocalTrainer"] diff --git a/classy_vision/trainer/classy_trainer.py b/classy_vision/trainer/classy_trainer.py new file mode 100644 index 0000000000..29fabb8e23 --- /dev/null +++ b/classy_vision/trainer/classy_trainer.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from typing import Optional + +import torch +from classy_vision.generic.distributed_util import barrier, is_distributed_training_run +from classy_vision.hooks import ClassyHookFunctions +from classy_vision.tasks import ClassyTask + + +class ClassyTrainer: + """Base class for shared training code. + + A trainer is responsible for setting up the environment for + training, for instance: configuring rendezvous for distributed + training, deciding what GPU to use and so on. Trainers also + control the outer portion of the training loop, but delegate to + the task to decide how exactly to perform inference, compute loss + etc. That allows combining tasks with different trainers depending + on whether you want to train on your current machine, AWS cluster + etc. + + """ + + def __init__( + self, + use_gpu: Optional[bool] = None, + num_dataloader_workers: int = 0, + dataloader_mp_context: Optional[str] = None, + ): + """Constructor for ClassyTrainer. + + Args: + use_gpu: If true, then use GPUs for training. + If None, then check if we have GPUs available, if we do + then use GPU for training. + num_dataloader_workers: Number of CPU processes doing dataloading + per GPU. If 0, then dataloading is done on main thread. + dataloader_mp_context: Determines how to launch + new processes for dataloading. Must be one of "fork", "forkserver", + "spawn". If None, process launching is inherited from parent. + """ + if use_gpu is None: + use_gpu = torch.cuda.is_available() + self.use_gpu = use_gpu + self.num_dataloader_workers = num_dataloader_workers + self.dataloader_mp_context = dataloader_mp_context + + def train(self, task: ClassyTask): + """Runs training phases, phases are generated from the config. + + Args: + task: Task to be used in training. It should contain + everything that is needed for training + """ + + pin_memory = self.use_gpu and torch.cuda.device_count() > 1 + task.prepare( + num_dataloader_workers=self.num_dataloader_workers, + pin_memory=pin_memory, + use_gpu=self.use_gpu, + dataloader_mp_context=self.dataloader_mp_context, + ) + assert isinstance(task, ClassyTask) + + if is_distributed_training_run(): + task.init_distributed_data_parallel_model() + + local_variables = {} + task.run_hooks(local_variables, ClassyHookFunctions.on_start.name) + + while not task.done_training(): + task.advance_phase() + + # Start phase hooks + task.run_hooks(local_variables, ClassyHookFunctions.on_phase_start.name) + while True: + # Process next sample + try: + task.train_step(self.use_gpu, local_variables) + except StopIteration: + break + + logging.info("Syncing meters on phase end...") + for meter in task.meters: + meter.sync_state() + logging.info("...meters synced") + barrier() + task.run_hooks(local_variables, ClassyHookFunctions.on_phase_end.name) + + task.run_hooks(local_variables, ClassyHookFunctions.on_end.name) diff --git a/classy_vision/trainer/distributed_trainer.py b/classy_vision/trainer/distributed_trainer.py new file mode 100644 index 0000000000..df301dd00e --- /dev/null +++ b/classy_vision/trainer/distributed_trainer.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import os +from typing import Optional + +import torch +from classy_vision.generic.distributed_util import ( + get_rank, + get_world_size, + set_cpu_device, + set_cuda_device_index, +) + +from .classy_trainer import ClassyTrainer + + +def _init_env_vars(): + """Function sets up default environment variables for distributed training. + """ + if "WORLD_SIZE" not in os.environ or "RANK" not in os.environ: + os.environ["WORLD_SIZE"] = "1" + os.environ["RANK"] = "0" + os.environ["LOCAL_RANK"] = "0" + + if "MASTER_ADDR" not in os.environ or "MASTER_PORT" not in os.environ: + os.environ["MASTER_ADDR"] = "127.0.0.1" + os.environ["MASTER_PORT"] = "29500" + + +def _init_distributed(use_gpu: bool): + """Function perform distributed setup for DDP. + + Requires the script to be started with torch.distributed.launch + script and uses environment variables for node finding. + + Args: + use_gpu: If true, use distributed GPU training, else use CPU + """ + distributed_world_size = int(os.environ["WORLD_SIZE"]) + distributed_rank = int(os.environ["RANK"]) + backend = "nccl" if use_gpu else "gloo" + torch.distributed.init_process_group( + backend=backend, + init_method="env://", + world_size=distributed_world_size, + rank=distributed_rank, + ) + + +class DistributedTrainer(ClassyTrainer): + """Distributed trainer for using multiple training processes + """ + + def __init__( + self, + use_gpu: Optional[bool] = None, + num_dataloader_workers: int = 0, + dataloader_mp_context: Optional[str] = None, + ): + """Constructor for DistributedTrainer. + + Args: + use_gpu: If true, then use GPU 0 for training. + If None, then check if we have GPUs available, if we do + then use GPU for training. + num_dataloader_workers: Number of CPU processes doing dataloading + per GPU. If 0, then dataloading is done on main thread. + dataloader_mp_context: Determines how to launch + new processes for dataloading. Must be one of "fork", "forkserver", + "spawn". If None, process launching is inherited from parent. + """ + super().__init__( + use_gpu=use_gpu, + num_dataloader_workers=num_dataloader_workers, + dataloader_mp_context=dataloader_mp_context, + ) + _init_env_vars() + _init_distributed(self.use_gpu) + logging.info( + f"Done setting up distributed process_group with rank {get_rank()}" + + f", world_size {get_world_size()}" + ) + local_rank = int(os.environ["LOCAL_RANK"]) + if self.use_gpu: + logging.info("Using GPU, CUDA device index: {}".format(local_rank)) + set_cuda_device_index(local_rank) + else: + logging.info("Using CPU") + set_cpu_device() diff --git a/classy_vision/trainer/elastic_trainer.py b/classy_vision/trainer/elastic_trainer.py new file mode 100644 index 0000000000..5f25dafabb --- /dev/null +++ b/classy_vision/trainer/elastic_trainer.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import io +import logging +import os +from typing import Any, Optional + +import numpy +import torch +import torchelastic +import torchelastic.distributed as dist +from classy_vision.generic.distributed_util import ( + barrier, + set_cpu_device, + set_cuda_device_index, +) +from classy_vision.generic.util import get_checkpoint_dict +from classy_vision.hooks import ClassyHookFunctions +from classy_vision.tasks import ClassyTask +from classy_vision.trainer import ClassyTrainer +from torchelastic.worker_stats import WorkerStats + + +log = logging.getLogger(__name__) + + +class ElasticTrainer(ClassyTrainer): + def __init__( + self, + use_gpu, + num_dataloader_workers, + elastic_coordinator, + input_args, + local_rank, + dataloader_mp_context=None, + ): + super().__init__( + use_gpu=use_gpu, + num_dataloader_workers=num_dataloader_workers, + dataloader_mp_context=dataloader_mp_context, + ) + pid = os.getpid() + if use_gpu: + set_cuda_device_index(local_rank) + device_idx = torch.cuda.current_device() + log.info(f"initialized worker {local_rank} (pid={pid}, gpu={device_idx})") + device_properties = torch.cuda.get_device_properties(device_idx) + log.info(f"gpu device properties: {device_properties}") + else: + # cpu + set_cpu_device() + log.info(f"initialized worker {local_rank} (pid={pid}, cpu)") + + self.elastic_coordinator = elastic_coordinator + self.input_args = input_args + + def train(self, task): + """ + Runs training phases, phases are generated from the config. + """ + + assert isinstance(task, ClassyTask) + pin_memory = self.use_gpu and torch.cuda.device_count() > 1 + + task.prepare( + num_dataloader_workers=self.num_dataloader_workers, + pin_memory=pin_memory, + use_gpu=self.use_gpu, + dataloader_mp_context=self.dataloader_mp_context, + ) + state = self._ClassyElasticState(task, self.input_args) + + local_variables = {} + + state.advance_to_next_phase = True + + def elastic_train_step(orig_state): + return self._run_step(orig_state, local_variables, self.use_gpu) + + task.run_hooks(local_variables, ClassyHookFunctions.on_start.name) + + torchelastic.train(self.elastic_coordinator, elastic_train_step, state) + + task.run_hooks(local_variables, ClassyHookFunctions.on_end.name) + + def _run_step(self, state, local_variables, use_gpu): + # Check for training complete but only terminate when the last phase is done + if state.task.done_training() and state.advance_to_next_phase: + raise StopIteration + + if state.advance_to_next_phase: + state.task.advance_phase() + + # Start phase hooks + state.task.run_hooks( + local_variables, ClassyHookFunctions.on_phase_start.name + ) + + state.advance_to_next_phase = False + + # Process one train step + try: + if state.skip_current_phase: + state.advance_to_next_phase = True + state.skip_current_phase = False # Reset flag + else: + state.task.train_step(use_gpu, local_variables) + except StopIteration: + state.advance_to_next_phase = True + if state.advance_to_next_phase: + logging.info("Syncing meters on phase end...") + for meter in state.task.meters: + meter.sync_state() + logging.info("...meters synced") + barrier() + # Phase complete + # NOTE: this is a good time to checkpoint, as it guarantees + # that loading from checkpoint will properly advance the phase. + state.task.run_hooks(local_variables, ClassyHookFunctions.on_phase_end.name) + + progress_rate = None # using None to signal 'unknown' + perf_stats = local_variables.get("perf_stats", None) + if perf_stats is not None: + batch_time = perf_stats._cuda_stats["train_step_total"].smoothed_value + if batch_time is not None and batch_time > 0.0: + # rate = number of mini-batches per second + progress_rate = 1.0 / batch_time + + progress_stats = self._ClassyWorkerStats(progress_rate) + return state, progress_stats + + class _ClassyWorkerStats(WorkerStats): + """ + ClassyVision-specific implementation of WorkerStats, + which is used by torchelastic train_loop + to detect (and correct stragglers), or other progress-impeding issues. + """ + + def __init__(self, progress_rate): + self.progress_rate = progress_rate + + def get_progress_rate(self) -> Optional[float]: + return self.progress_rate + + class _ClassyElasticState(torchelastic.State): + """ + Rollback is disabled on this state since currently, data loaders are + too expensive to snapshot on every train_step + """ + + def __init__(self, task: ClassyTask, input_args: Any): + self.task = task + self.input_args = input_args if input_args else {} + self.advance_to_next_phase = True + self.skip_current_phase = False + + def broadcast_state(self, rank, src_rank): + data = None + if rank == src_rank: + save_stream = io.BytesIO() + self.save(save_stream) + # Note: save_stream.getbuffer() will return a memoryview, which + # cannot be convert to a tensor, need convert it to np array first + data = numpy.asarray(save_stream.getbuffer()) + data = dist.broadcast_binary(data, src_rank) + load_stream = io.BytesIO(data) + self.load(load_stream) + + def sync(self, world_size, rank): + self._recreate_ddp_model() + + # Figure out which trainer has the most up-to-date data, and + # use that trainer to broadcast task to all others. + src_rank = self._compute_most_tenured_rank(rank) + self.broadcast_state(rank, src_rank) + + # Current on-box data loaders don't support recovery in the middle of + # a phase and since we don't rollback the model, re-training is + # worse than losing data so we're skipping rest of the phase. + # + # Also we can't just set advance_to_next_phase to True here as it + # will cause on_phase_end() hooks to not run. + # We also only skip the current phase if this isn't the first time + # calling sync from the PET train_loop. We'll need to reconsider this + # if the PET train_loop changes. advance_to_next_phase is already + # synced from rest of the trainers at this point. + if not self.advance_to_next_phase: + self.skip_current_phase = True + + logging.warning( + "RANK {}: now we all have {} updates and latest task".format( + rank, self.task.num_updates + ) + ) + + # Re-build dataloader, dataset, and iterator anytime membership + # changes. When world_size potentially changes (e.g. re-rendezvous), we + # need to re-create both the dataset and dataloader objects because we + # create a ShardDataset based on the world size at the time of + # construction. + # TODO (T55691442): Figure out how to solve re-sharding without + # rebuilding the datasets. sync() only works correctly without elasticity + # currently. + for phase_type in self.task.datasets.keys(): + self.task._recreate_data_loader_from_dataset(phase_type) + self.task._reshuffle_data() + self.task.create_data_iterator() + # Set up pytorch module in train vs eval mode, update optimizer. + self.task._set_model_train_mode() + + def should_save_checkpoint(self, rank): + # should_save_checkpoint need to return same value for all trainers + # we take checkpoint when a phase completed + # TODO add test coverage for this + + # currently for typical imagenet resnet model checkpointing take 15 seconds + # consider the cost it is not very necessary to do checkpoint for test phase + return self.task.train and self.advance_to_next_phase + + def save(self, stream): + checkpoint_state = get_checkpoint_dict(self.task, self.input_args) + checkpoint_state["advance_to_next_phase"] = self.advance_to_next_phase + torch.save(checkpoint_state, stream) + + def load(self, stream): + checkpoint_state = torch.load(stream) + state = checkpoint_state["classy_state_dict"] + self.task.set_classy_state(state) + if "advance_to_next_phase" in checkpoint_state: + self.advance_to_next_phase = checkpoint_state["advance_to_next_phase"] + + def _recreate_ddp_model(self): + # Delete & re-create the DDP module wrapper. This is required because + # each instance of DDP is tied to a specific process group, and + # any time the set of workers in PET changes, we create a new + # process group, so the old DDP wrapper is invalid. + # TODO: does calling del here invoke C++ destructor if it's the last + # reference? Or is assigning None sufficient? + del self.task.distributed_model + self.task.distributed_model = None + self.task.init_distributed_data_parallel_model() + + def _compute_most_tenured_rank(self, rank): + logging.warning( + "RANK {}: syncing, I have {} updates".format( + rank, self.task.num_updates + ) + ) + # Propagate state to new trainer processes. + # First, figure out which process has a copy of the most recent + # state by getting a copy of everybody's iteration counter. + max_rank, max_num_updates = dist.all_gather_return_max_long( + self.task.num_updates + ) + + logging.warning( + "RANK {}: rank {} has the most updates {}".format( + rank, max_rank, max_num_updates + ) + ) + + return max_rank diff --git a/classy_vision/trainer/local_trainer.py b/classy_vision/trainer/local_trainer.py new file mode 100644 index 0000000000..4435381ffb --- /dev/null +++ b/classy_vision/trainer/local_trainer.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +from typing import Optional + +from classy_vision.generic.distributed_util import set_cpu_device, set_cuda_device_index + +from .classy_trainer import ClassyTrainer + + +class LocalTrainer(ClassyTrainer): + """Trainer to be used if you want want use only a single training process. + """ + + def __init__( + self, + use_gpu: Optional[bool] = None, + num_dataloader_workers: int = 0, + dataloader_mp_context: Optional[str] = None, + ): + """Constructor for LocalTrainer. + + Args: + use_gpu: If true, then use GPU 0 for training. + If None, then check if we have GPUs available, if we do + then use GPU for training. + num_dataloader_workers: Number of CPU processes doing dataloading + per GPU. If 0, then dataloading is done on main thread. + dataloader_mp_context: Determines how to launch + new processes for dataloading. Must be one of "fork", "forkserver", + "spawn". If None, process launching is inherited from parent. + """ + super().__init__( + use_gpu=use_gpu, + num_dataloader_workers=num_dataloader_workers, + dataloader_mp_context=dataloader_mp_context, + ) + if self.use_gpu: + logging.info("Using GPU, CUDA device index: {}".format(0)) + set_cuda_device_index(0) + else: + logging.info("Using CPU") + set_cpu_device() diff --git a/examples/ray/cluster_config.yml b/examples/ray/cluster_config.yml new file mode 100644 index 0000000000..fec38fde31 --- /dev/null +++ b/examples/ray/cluster_config.yml @@ -0,0 +1,165 @@ +# An unique identifier for the head node and workers of this cluster. +cluster_name: default +# The minimum number of workers nodes to launch in addition to the head +# node. This number should be >= 0. +min_workers: 2 + +# The maximum number of workers nodes to launch in addition to the head +# node. This takes precedence over min_workers. +max_workers: 2 + +# The initial number of worker nodes to launch in addition to the head +# node. When the cluster is first brought up (or when it is refreshed with a +# subsequent `ray up`) this number of nodes will be started. +initial_workers: 2 + +# Whether or not to autoscale aggressively. If this is enabled, if at any point +# we would start more workers, we start at least enough to bring us to +# initial_workers. +autoscaling_mode: default + +# This executes all commands on all nodes in the docker container, +# and opens all the necessary ports to support the Ray cluster. +# Empty string means disabled. +docker: + image: "" # e.g., tensorflow/tensorflow:1.5.0-py3 + container_name: "" # e.g. ray_docker + run_options: [] # Extra options to pass into "docker run" + +# The autoscaler will scale up the cluster to this target fraction of resource +# usage. For example, if a cluster of 10 nodes is 100% busy and +# target_utilization is 0.8, it would resize the cluster to 13. This fraction +# can be decreased to increase the aggressiveness of upscaling. +# This value must be less than 1.0 for scaling to happen. +# +# For this Classy Vision example, we want to disable autoscaling. +target_utilization_fraction: 1.0 + +# If a node is idle for this many minutes, it will be removed. +idle_timeout_minutes: 5 + +# Cloud-provider specific configuration. +provider: + type: aws + region: us-west-2 + # Availability zone(s), comma-separated, that nodes may be launched in. + # Nodes are currently spread between zones by a round-robin approach, + # however this implementation detail should not be relied upon. + availability_zone: us-west-2a,us-west-2b + + # This prevents Ray from re-using nodes from a previous cluster that were stopped. + # Reusing nodes is desirable cause it speeds up the time to setup a new cluster, + # but in our experiments it made each run hard to reproduce. You have to make sure + # your setup commands are idempotent. For now disable caching to keep things simple, + # but it shouldn't be too hard to get this working with caching. + cache_stopped_nodes: False + +# How Ray will authenticate with newly launched nodes. +auth: + ssh_user: ubuntu +# By default Ray creates a new private keypair, but you can also use your own. +# If you do so, make sure to also set "KeyName" in the head and worker node +# configurations below. +# ssh_private_key: /path/to/your/key.pem + +# Provider-specific config for the head node, e.g. instance type. By default +# Ray will auto-configure unspecified fields such as SubnetId and KeyName. +# For more documentation on available fields, see: +# http://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.ServiceResource.create_instances +# +# In this Classy Vision example, we will have a CPU-only machine as the head node, +# and GPU workers to actually run training jobs. +head_node: + InstanceType: m5.large + ImageId: ami-05931d11d2bf831c3 # Deep Learning AMI (Ubuntu) Version 24.3 + + # You can provision additional disk space with a conf as follows + BlockDeviceMappings: + - DeviceName: /dev/sda1 + Ebs: + VolumeSize: 100 + + # Additional options in the boto docs. + +# Provider-specific config for worker nodes, e.g. instance type. By default +# Ray will auto-configure unspecified fields such as SubnetId and KeyName. +# For more documentation on available fields, see: +# http://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.ServiceResource.create_instances +worker_nodes: + # We will use GPU workers + InstanceType: p2.xlarge + ImageId: ami-05931d11d2bf831c3 # Deep Learning AMI (Ubuntu) Version 24.3 + + # Additional options in the boto docs. + +# Files or directories to copy to the head and worker nodes. The format is a +# dictionary from REMOTE_PATH: LOCAL_PATH, e.g. +file_mounts: { +# "/path1/on/remote/machine": "/path1/on/local/machine", +} + +# List of commands that will be run before `setup_commands`. If docker is +# enabled, these commands will run outside the container and before docker +# is setup. +initialization_commands: [] + +# List of shell commands to run to set up nodes. +setup_commands: + # These commands are carefully crafted to get EFS mounts working correctly + # with this AMI. Be careful editing this: in my experience the EFS setup + # process is very fragile + # + # ********************************************* + # + # ATTENTION: edit the {{FileSystemId}} entry below to match the EFS volume you have created + # + # ********************************************* + - sudo kill -9 `sudo lsof /var/lib/dpkg/lock-frontend | awk '{print $2}' | tail -n 1`; + sudo pkill -9 apt-get; + sudo pkill -9 dpkg; + sudo dpkg --configure -a; + sudo apt-get -y update; + sudo apt-get -y install binutils; + cd $HOME; + git clone https://github.com/aws/efs-utils; + cd $HOME/efs-utils; + ./build-deb.sh; + sudo kill -9 `sudo lsof /var/lib/dpkg/lock-frontend | awk '{print $2}' | tail -n 1`; + sudo pkill -9 apt-get; + sudo pkill -9 dpkg; + sudo dpkg --configure -a; + sudo apt-get -y install ./build/amazon-efs-utils*deb; + sudo apt-get -y install ./build/amazon-efs-utils*deb; + cd $HOME; + mkdir efs; + sudo mount -t efs {{FileSystemId}}:/ efs; + sudo chmod 777 efs; + +# Custom commands that will be run on the head node after common setup. +head_setup_commands: + - conda install pytorch torchvision -c pytorch -y + - pip install --upgrade pip + - pip install -U https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.8.0.dev6-cp36-cp36m-manylinux1_x86_64.whl + - pip install boto3==1.4.8 # 1.4.8 adds InstanceMarketOptions + - pip install classy-vision + +# Custom commands that will be run on worker nodes after common setup. +worker_setup_commands: + # The AMI we use does not have a version of PyTorch compatible with Classy Vision, + # so we have to install it manually here. This AMI also comes with the hard drive + # almost full, so we have to uninstall a conda environment to make up space. + - conda env remove -n caffe2_p27 -y || true + - conda install pytorch torchvision -c pytorch -y + - pip install --upgrade pip + - pip install -U https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.8.0.dev6-cp36-cp36m-manylinux1_x86_64.whl + - pip install classy-vision + +# Command to start ray on the head node. You don't need to change this. +head_start_ray_commands: + - ray stop + - ulimit -n 65536; ray start --head --redis-port=6379 --object-manager-port=8076 --autoscaling-config=~/ray_bootstrap_config.yaml + +# Command to start ray on worker nodes. You don't need to change this. +worker_start_ray_commands: + - ray stop + - ulimit -n 65536; ray start --redis-address=$RAY_HEAD_IP:6379 --object-manager-port=8076 diff --git a/examples/ray/requirements.txt b/examples/ray/requirements.txt new file mode 100644 index 0000000000..bae4aa9b29 --- /dev/null +++ b/examples/ray/requirements.txt @@ -0,0 +1,2 @@ +ray==0.7.6 +boto3==1.10.28 diff --git a/hubconf.py b/hubconf.py new file mode 100644 index 0000000000..a5e21c5c4a --- /dev/null +++ b/hubconf.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import functools + +import torch +from classy_vision.hub import ClassyHubInterface + + +dependencies = ["torch", "torchvision"] + +# export the wsl models (https://github.com/facebookresearch/WSL-Images) +resnext_wsl_models = [ + "resnext101_32x8d_wsl", + "resnext101_32x16d_wsl", + "resnext101_32x32d_wsl", + "resnext101_32x48d_wsl", +] + + +def _create_interface_from_torchhub(github, *args, **kwargs): + model = torch.hub.load(github, *args, **kwargs) + return ClassyHubInterface.from_model(model) + + +for model in resnext_wsl_models: + globals()[model] = functools.partial( + _create_interface_from_torchhub, "facebookresearch/WSL-Images", model + ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..d2a2c65e68 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +torch==1.3.1 +torchvision==0.4.2 diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh new file mode 100755 index 0000000000..5ddcc2739c --- /dev/null +++ b/scripts/build_docs.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# run this script from the project root using `./scripts/build_docs.sh` + +usage() { + echo "Usage: $0 [-b]" + echo "" + echo "Build Classy Vision documentation." + echo "" + echo " -b Build static version of documentation (otherwise start server)" + echo "" + exit 1 +} + +BUILD_STATIC=false + +while getopts 'hb' flag; do + case "${flag}" in + h) + usage + ;; + b) + BUILD_STATIC=true + ;; + *) + usage + ;; + esac +done + +echo "-----------------------------------" +echo "Generating API reference via Sphinx" +echo "-----------------------------------" +cd sphinx || exit +make html +cd .. || exit + +echo "-----------------------------------" +echo "Building Captum Docusaurus site" +echo "-----------------------------------" +cd website || exit +yarn + +# run script to parse html generated by sphinx +echo "--------------------------------------------" +echo "Parsing Sphinx docs and moving to Docusaurus" +echo "--------------------------------------------" +cd .. +mkdir -p "website/pages/api/" + +cwd=$(pwd) +python scripts/parse_sphinx.py -i "${cwd}/sphinx/build/html/" -o "${cwd}/website/pages/api/" + +SPHINX_JS_DIR='sphinx/build/html/_static/' +DOCUSAURUS_JS_DIR='website/static/js/' + +mkdir -p $DOCUSAURUS_JS_DIR + +# move JS files from /sphinx/build/html/_static/*: +cp "${SPHINX_JS_DIR}documentation_options.js" "${DOCUSAURUS_JS_DIR}documentation_options.js" +cp "${SPHINX_JS_DIR}jquery.js" "${DOCUSAURUS_JS_DIR}jquery.js" +cp "${SPHINX_JS_DIR}underscore.js" "${DOCUSAURUS_JS_DIR}underscore.js" +cp "${SPHINX_JS_DIR}doctools.js" "${DOCUSAURUS_JS_DIR}doctools.js" +cp "${SPHINX_JS_DIR}language_data.js" "${DOCUSAURUS_JS_DIR}language_data.js" +cp "${SPHINX_JS_DIR}searchtools.js" "${DOCUSAURUS_JS_DIR}searchtools.js" + +# searchindex.js is not static util +cp "sphinx/build/html/searchindex.js" "${DOCUSAURUS_JS_DIR}searchindex.js" + +# copy module sources +cp -r "sphinx/build/html/_sources/" "website/static/_sphinx-sources/" + +echo "-----------------------------------" +echo "Generating tutorials" +echo "-----------------------------------" +mkdir -p "website/_tutorials" +mkdir -p "website/static/files" +python scripts/parse_tutorials.py -w "${cwd}" + +cd website || exit + +if [[ $BUILD_STATIC == true ]]; then + echo "-----------------------------------" + echo "Building static site" + echo "-----------------------------------" + yarn build +else + echo "-----------------------------------" + echo "Starting local server" + echo "-----------------------------------" + yarn start +fi diff --git a/scripts/parse_sphinx.py b/scripts/parse_sphinx.py new file mode 100644 index 0000000000..5eeb2909e5 --- /dev/null +++ b/scripts/parse_sphinx.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import argparse +import os + +from bs4 import BeautifulSoup + + +js_scripts = """ + + + + + + +""" # noqa: E501 + +search_js_scripts = """ + + + +""" + + +def parse_sphinx(input_dir, output_dir): + for cur, _, files in os.walk(input_dir): + for fname in files: + if fname.endswith(".html"): + with open(os.path.join(cur, fname), "r") as f: + soup = BeautifulSoup(f.read(), "html.parser") + doc = soup.find("div", {"class": "document"}) + wrapped_doc = doc.wrap(soup.new_tag("div", **{"class": "sphinx"})) + # add js + if fname == "search.html": + out = js_scripts + search_js_scripts + str(wrapped_doc) + else: + out = js_scripts + str(wrapped_doc) + output_path = os.path.join(output_dir, os.path.relpath(cur, input_dir)) + os.makedirs(output_path, exist_ok=True) + with open(os.path.join(output_path, fname), "w") as fout: + fout.write(out) + + # update reference in JS file + with open(os.path.join(input_dir, "_static/searchtools.js"), "r") as js_file: + js = js_file.read() + js = js.replace( + "DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/'", "'_sphinx-sources/'" + ) + with open(os.path.join(input_dir, "_static/searchtools.js"), "w") as js_file: + js_file.write(js) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Strip HTML body from Sphinx docs.") + parser.add_argument( + "-i", + "--input_dir", + metavar="path", + required=True, + help="Input directory for Sphinx HTML.", + ) + parser.add_argument( + "-o", + "--output_dir", + metavar="path", + required=True, + help="Output directory in Docusaurus.", + ) + args = parser.parse_args() + parse_sphinx(args.input_dir, args.output_dir) diff --git a/scripts/parse_tutorials.py b/scripts/parse_tutorials.py new file mode 100644 index 0000000000..07bcc111f3 --- /dev/null +++ b/scripts/parse_tutorials.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import argparse +import json +import os + +import nbformat +from bs4 import BeautifulSoup +from nbconvert import HTMLExporter, ScriptExporter + + +TEMPLATE = """const CWD = process.cwd(); + +const React = require('react'); +const Tutorial = require(`${{CWD}}/core/Tutorial.js`); + +class TutorialPage extends React.Component {{ + render() {{ + const {{config: siteConfig}} = this.props; + const {{baseUrl}} = siteConfig; + return ; + }} +}} + +module.exports = TutorialPage; + +""" + +JS_SCRIPTS = """ + + +""" # noqa: E501 + + +def gen_tutorials(repo_dir: str) -> None: + """Generate HTML tutorials for captum Docusaurus site from Jupyter notebooks. + + Also create ipynb and py versions of tutorial in Docusaurus site for + download. + """ + with open(os.path.join(repo_dir, "website", "tutorials.json"), "r") as infile: + tutorial_config = json.loads(infile.read()) + + tutorial_ids = {x["id"] for v in tutorial_config.values() for x in v} + + for tid in tutorial_ids: + print("Generating {} tutorial".format(tid)) + + # convert notebook to HTML + ipynb_in_path = os.path.join(repo_dir, "tutorials", "{}.ipynb".format(tid)) + with open(ipynb_in_path, "r") as infile: + nb_str = infile.read() + nb = nbformat.reads(nb_str, nbformat.NO_CONVERT) + + # displayname is absent from notebook metadata + nb["metadata"]["kernelspec"]["display_name"] = "python3" + + exporter = HTMLExporter() + html, meta = exporter.from_notebook_node(nb) + + # pull out html div for notebook + soup = BeautifulSoup(html, "html.parser") + nb_meat = soup.find("div", {"id": "notebook-container"}) + del nb_meat.attrs["id"] + nb_meat.attrs["class"] = ["notebook"] + html_out = JS_SCRIPTS + str(nb_meat) + + # generate html file + html_out_path = os.path.join( + repo_dir, "website", "_tutorials", "{}.html".format(tid) + ) + with open(html_out_path, "w") as html_outfile: + html_outfile.write(html_out) + + # generate JS file + script = TEMPLATE.format(tid) + js_out_path = os.path.join( + repo_dir, "website", "pages", "tutorials", "{}.js".format(tid) + ) + with open(js_out_path, "w") as js_outfile: + js_outfile.write(script) + + # output tutorial in both ipynb & py form + ipynb_out_path = os.path.join( + repo_dir, "website", "static", "files", "{}.ipynb".format(tid) + ) + with open(ipynb_out_path, "w") as ipynb_outfile: + ipynb_outfile.write(nb_str) + exporter = ScriptExporter() + script, meta = exporter.from_notebook_node(nb) + py_out_path = os.path.join( + repo_dir, "website", "static", "files", "{}.py".format(tid) + ) + with open(py_out_path, "w") as py_outfile: + py_outfile.write(script) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate JS, HTML, ipynb, and py files for tutorials." + ) + parser.add_argument( + "-w", "--repo_dir", metavar="path", required=True, help="captum repo directory." + ) + args = parser.parse_args() + gen_tutorials(args.repo_dir) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..f99fd88e4e --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +import os +import re +import sys + +from setuptools import find_packages, setup + + +if __name__ == "__main__": + if sys.version_info < (3, 6): + sys.exit("Sorry, Python >=3.6 is required for Classy Vision.") + + # get version string from module + with open( + os.path.join(os.path.dirname(__file__), "classy_vision/__init__.py"), "r" + ) as f: + version = re.search(r"__version__ = ['\"]([^'\"]*)['\"]", f.read(), re.M).group( + 1 + ) + print("-- Building version " + version) + + with open("README.md", encoding="utf8") as f: + readme = f.read() + + with open("requirements.txt") as f: + reqs = f.read() + + setup( + name="classy_vision", + version=version, + description="An end-to-end PyTorch framework for image and video classification.", + long_description_content_type="text/markdown", + long_description=readme, + url="https://classyvision.ai", + project_urls={ + "Documentation": "https://classyvision.ai", + "Source": "https://github.com/facebookresearch/ClassyVision", + }, + license="MIT License", + python_requires=">=3.6", + packages=find_packages(exclude=("tests",)), + install_requires=reqs.strip().split("\n"), + extras_require={"dev": ["black", "sphinx", "isort", "bs4", "nbconvert"]}, + package_data={"classy_vision": ["configs/*.json", "templates"]}, + data_files=[("classy_vision", ["classy_train.py"])], + include_package_data=True, + test_suite="test.suites.unittests", + scripts=["bin/classy-project"], + keywords=["deep learning", "pytorch", "AI"], + classifiers=[ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Image Recognition", + ], + ) diff --git a/sphinx/Makefile b/sphinx/Makefile new file mode 100644 index 0000000000..ed88099027 --- /dev/null +++ b/sphinx/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/sphinx/conf.py b/sphinx/conf.py new file mode 100644 index 0000000000..67cf729d61 --- /dev/null +++ b/sphinx/conf.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# + +import os +import sys + + +sys.path.insert(0, os.path.abspath("..")) + + +# -- Project information ----------------------------------------------------- + +project = "Classy Vision" +copyright = "2019, Facebook AI Research" +author = "Facebook AI Research" + +# The full version, including alpha/beta/rc tags +release = "0.1" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named "sphinx.ext.*") or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.githubpages", + "sphinx.ext.napoleon", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", +] + +autodoc_default_flags = ["undoc-members"] +autodoc_default_options = {"special-members": "__init__", "autodoc_typehints": "none"} + +primary_domain = "py" + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "pytorch": ("https://pytorch.org/docs/stable", None), +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "alabaster" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ["_static"] +html_static_path = [] # for now we have no static files to track + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = False diff --git a/sphinx/dataset.rst b/sphinx/dataset.rst new file mode 100644 index 0000000000..15181efac4 --- /dev/null +++ b/sphinx/dataset.rst @@ -0,0 +1,5 @@ +Dataset +========== + +.. automodule:: classy_vision.dataset + :members: diff --git a/sphinx/heads.rst b/sphinx/heads.rst new file mode 100644 index 0000000000..f7a5590e35 --- /dev/null +++ b/sphinx/heads.rst @@ -0,0 +1,5 @@ +Heads +========== + +.. automodule:: classy_vision.heads + :members: diff --git a/sphinx/hooks.rst b/sphinx/hooks.rst new file mode 100644 index 0000000000..832e967a93 --- /dev/null +++ b/sphinx/hooks.rst @@ -0,0 +1,5 @@ +Hooks +========== + +.. automodule:: classy_vision.hooks + :members: diff --git a/sphinx/index.rst b/sphinx/index.rst new file mode 100644 index 0000000000..823a951bf7 --- /dev/null +++ b/sphinx/index.rst @@ -0,0 +1,34 @@ +.. Classy Vision documentation master file, created by + sphinx-quickstart on Sat Oct 26 09:05:26 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Classy Vision's API Reference +========================================= + +This is an exhaustive reference for APIs in Classy Vision. Please refer to our +tutorials for high-level information about +how these abstractions can used together. + +.. toctree:: + :maxdepth: 3 + + dataset + heads + hooks + losses + meters + models + optim + param_scheduler + tasks + trainer + transforms + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/sphinx/losses.rst b/sphinx/losses.rst new file mode 100644 index 0000000000..92b48d32bb --- /dev/null +++ b/sphinx/losses.rst @@ -0,0 +1,5 @@ +Losses +========== + +.. automodule:: classy_vision.losses + :members: diff --git a/sphinx/make.bat b/sphinx/make.bat new file mode 100644 index 0000000000..2119f51099 --- /dev/null +++ b/sphinx/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/sphinx/meters.rst b/sphinx/meters.rst new file mode 100644 index 0000000000..9e32d98d59 --- /dev/null +++ b/sphinx/meters.rst @@ -0,0 +1,5 @@ +Meters +========== + +.. automodule:: classy_vision.meters + :members: diff --git a/sphinx/models.rst b/sphinx/models.rst new file mode 100644 index 0000000000..78add08080 --- /dev/null +++ b/sphinx/models.rst @@ -0,0 +1,5 @@ +Models +========== + +.. automodule:: classy_vision.models + :members: diff --git a/sphinx/optim.rst b/sphinx/optim.rst new file mode 100644 index 0000000000..7d326bd5cb --- /dev/null +++ b/sphinx/optim.rst @@ -0,0 +1,5 @@ +Optimizers +========== + +.. automodule:: classy_vision.optim + :members: diff --git a/sphinx/param_scheduler.rst b/sphinx/param_scheduler.rst new file mode 100644 index 0000000000..6d4e8a0245 --- /dev/null +++ b/sphinx/param_scheduler.rst @@ -0,0 +1,5 @@ +Param Schedulers +========== + +.. automodule:: classy_vision.optim.param_scheduler + :members: diff --git a/sphinx/tasks.rst b/sphinx/tasks.rst new file mode 100644 index 0000000000..6caca1b657 --- /dev/null +++ b/sphinx/tasks.rst @@ -0,0 +1,5 @@ +Tasks +========== + +.. automodule:: classy_vision.tasks + :members: diff --git a/sphinx/trainer.rst b/sphinx/trainer.rst new file mode 100644 index 0000000000..2041db57b0 --- /dev/null +++ b/sphinx/trainer.rst @@ -0,0 +1,5 @@ +Trainer +========== + +.. automodule:: classy_vision.trainer + :members: diff --git a/sphinx/transforms.rst b/sphinx/transforms.rst new file mode 100644 index 0000000000..b343bfa64b --- /dev/null +++ b/sphinx/transforms.rst @@ -0,0 +1,16 @@ +Transforms +========== + +Classy Vision is able to work directly with `torchvision` transforms +, so it ships with +very few built-in transforms. However, during research it's common to +experiment with new transforms. The `ClassyTransform` class allows users to +express their transforms in a common format and define them in a configuration +file. + +Like other Classy Vision abstractions, `ClassyTransform` is accompannied by a +`register_transform` decorator and `build_transform` function for integration +with the config system. + +.. automodule:: classy_vision.dataset.transforms + :members: diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000000..734a1eb4e2 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. diff --git a/test/api_test.py b/test/api_test.py new file mode 100644 index 0000000000..1db5ed45ee --- /dev/null +++ b/test/api_test.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import unittest +from typing import Any, Callable, Dict, Optional, Union + +import torch.nn as nn +import torch.nn.functional as F +from classy_vision.dataset import ClassyDataset, register_dataset +from classy_vision.dataset.core.random_image_datasets import ( + RandomImageBinaryClassDataset, + SampleType, +) +from classy_vision.dataset.transforms import ( + ClassyTransform, + GenericImageTransform, + build_transforms, +) +from classy_vision.losses import ClassyLoss, register_loss +from classy_vision.models import ClassyModel, register_model +from classy_vision.optim import SGD +from classy_vision.optim.param_scheduler import ConstantParamScheduler +from classy_vision.tasks import ClassificationTask +from classy_vision.trainer import LocalTrainer +from torchvision import transforms + + +# WARNING: The goal of this test is to use our public API as advertised in our +# tutorials and make sure everything trains successfully. If you break this +# test, make sure you also update our tutorials. + + +@register_dataset("my_dataset") +class MyDataset(ClassyDataset): + def __init__( + self, + batchsize_per_replica: int, + shuffle: bool, + transform: Optional[Union[ClassyTransform, Callable]], + num_samples: int, + crop_size: int, + class_ratio: float, + seed: int, + split: Optional[str] = None, + ) -> None: + dataset = RandomImageBinaryClassDataset( + crop_size, class_ratio, num_samples, seed, SampleType.TUPLE + ) + super().__init__( + dataset, split, batchsize_per_replica, shuffle, transform, num_samples + ) + + @classmethod + def from_config(cls, config: Dict[str, Any]) -> "MyDataset": + assert all(key in config for key in ["crop_size", "class_ratio", "seed"]) + + split = config.get("split") + crop_size = config["crop_size"] + class_ratio = config["class_ratio"] + seed = config["seed"] + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + ) = cls.parse_config(config) + transform = build_transforms(transform_config) + return cls( + batchsize_per_replica, + shuffle, + transform, + num_samples, + crop_size, + class_ratio, + seed, + split=split, + ) + + +@register_loss("my_loss") +class MyLoss(ClassyLoss): + def forward(self, input, target): + labels = F.one_hot(target, num_classes=2).float() + return F.binary_cross_entropy(input, labels) + + @classmethod + def from_config(cls, config): + # We don't need anything from the config + return cls() + + +@register_model("my_model") +class MyModel(ClassyModel): + def __init__(self): + super().__init__() + self.model = nn.Sequential( + nn.AdaptiveAvgPool2d((20, 20)), + nn.Flatten(1), + nn.Linear(3 * 20 * 20, 2), + nn.Sigmoid(), + ) + + def forward(self, x): + x = self.model(x) + return x + + @classmethod + def from_config(cls, config): + return cls() + + +class APITest(unittest.TestCase): + def testOne(self): + train_dataset = MyDataset( + batchsize_per_replica=32, + shuffle=False, + transform=GenericImageTransform( + transform=transforms.Compose( + [ + transforms.RandomResizedCrop(224), + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ), + ] + ) + ), + num_samples=100, + crop_size=224, + class_ratio=0.5, + seed=0, + ) + + test_dataset = MyDataset( + batchsize_per_replica=32, + shuffle=False, + transform=GenericImageTransform( + transform=transforms.Compose( + [ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ), + ] + ) + ), + num_samples=100, + crop_size=224, + class_ratio=0.5, + seed=0, + ) + + model = MyModel() + loss = MyLoss() + + optimizer = SGD(lr_scheduler=ConstantParamScheduler(0.01)) + + task = ( + ClassificationTask() + .set_model(model) + .set_dataset(train_dataset, "train") + .set_dataset(test_dataset, "test") + .set_loss(loss) + .set_optimizer(optimizer) + .set_num_epochs(1) + ) + + trainer = LocalTrainer() + trainer.train(task) diff --git a/test/classy_block_test.py b/test/classy_block_test.py new file mode 100644 index 0000000000..45491dbf59 --- /dev/null +++ b/test/classy_block_test.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +from classy_vision.heads import ClassyHead +from classy_vision.models import ClassyModel + + +class TestClassyBlock(unittest.TestCase): + class DummyTestHead(ClassyHead): + def __init__(self): + super().__init__("head_id") + self.layer = torch.nn.Linear(2, 2) + + def forward(self, x): + return self.layer(x) + + class DummyTestModel(ClassyModel): + def __init__(self): + super().__init__() + self.layer1 = self.build_attachable_block( + "dummy_block", torch.nn.Linear(2, 2) + ) + self.layer2 = self.build_attachable_block( + "dummy_block2", torch.nn.Linear(2, 2) + ) + + def forward(self, x): + out = self.layer1(x) + return self.layer2(out) + + def test_head_execution(self): + model = self.DummyTestModel() + head = self.DummyTestHead() + model.set_heads({"dummy_block2": {head.unique_id: head}}) + input = torch.randn(1, 2) + output = model(input) + head_output = model.execute_heads() + self.assertTrue(torch.allclose(head(output), head_output["head_id"])) + + def test_duplicated_head_ids(self): + model = self.DummyTestModel() + head1 = self.DummyTestHead() + head2 = self.DummyTestHead() + heads = { + "dummy_block": {head1.unique_id: head1}, + "dummy_block2": {head2.unique_id: head2}, + } + with self.assertRaises(ValueError): + model.set_heads(heads) + + head2.unique_id = "head_id2" + model.set_heads(heads) + + def test_set_heads(self): + model = self.DummyTestModel() + head = self.DummyTestHead() + self.assertEqual( + len(model.get_heads()), 0, "heads should be empty before set_heads" + ) + model.set_heads({"dummy_block2": {head.unique_id: head}}) + input = torch.randn(1, 2) + model(input) + head_outputs = model.execute_heads() + self.assertEqual(len(head_outputs), 1, "should have output for one head") + + # remove all heads + model.set_heads({}) + self.assertEqual(len(model.get_heads()), 0, "heads should be empty") diff --git a/test/classy_vision_head_test.py b/test/classy_vision_head_test.py new file mode 100644 index 0000000000..e768578211 --- /dev/null +++ b/test/classy_vision_head_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +from classy_vision.heads import ClassyHead, build_head, register_head + + +class TestClassyHead(unittest.TestCase): + @register_head("dummy_head") + class DummyHead(ClassyHead): + def __init__(self, unique_id, num_classes, in_plane): + super().__init__(unique_id, num_classes) + self.fc = torch.nn.Linear(in_plane, num_classes) + + def forward(self, x): + return self.fc(x) + + @classmethod + def from_config(cls, config): + return cls(config["unique_id"], config["num_classes"], config["in_plane"]) + + def _get_config(self): + return { + "name": "dummy_head", + "num_classes": 3, + "unique_id": "cortex_dummy_head", + "fork_block": "block3", + "in_plane": 2048, + } + + def test_build_head(self): + config = self._get_config() + head = build_head(config) + self.assertEqual(head.unique_id, config["unique_id"]) + + del config["unique_id"] + with self.assertRaises(AssertionError): + head = build_head(config) + + def test_forward(self): + config = self._get_config() + head = build_head(config) + input = torch.randn(1, config["in_plane"]) + output = head(input) + self.assertEqual(output.size(), torch.Size([1, 3])) + + def _get_pass_through_config(self): + return { + "name": "identity", + "num_classes": 3, + "unique_id": "cortex_pass_through_head", + "fork_block": "block3", + "in_plane": 4, + } + + def test_identity_forward(self): + config = self._get_pass_through_config() + head = build_head(config) + input = torch.randn(1, config["in_plane"]) + output = head(input) + self.assertEqual(input.size(), output.size()) + self.assert_(torch.all(torch.eq(input, output))) diff --git a/test/dataset_classy_dataset_test.py b/test/dataset_classy_dataset_test.py new file mode 100644 index 0000000000..2885465c9e --- /dev/null +++ b/test/dataset_classy_dataset_test.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest +import unittest.mock as mock +from test.generic.utils import compare_batches, compare_samples + +import classy_vision.dataset.classy_dataset as classy_dataset +import torch +from classy_vision.dataset import build_dataset, register_dataset +from classy_vision.dataset.core import ListDataset +from torch.utils.data import DataLoader + + +DUMMY_SAMPLES_1 = [ + {"input": torch.tensor([[[0, 1], [2, 3]]]), "target": torch.tensor([[0]])} +] + + +DUMMY_SAMPLES_2 = [ + {"input": torch.tensor([[[0, 1], [2, 3]]]), "target": torch.tensor([[0]])}, + {"input": torch.tensor([[[4, 5], [6, 7]]]), "target": torch.tensor([[1]])}, +] + +BATCHED_DUMMY_SAMPLES_2 = [ + { + "input": torch.tensor([[[[0, 1], [2, 3]]], [[[4, 5], [6, 7]]]]), + "target": torch.tensor([[[0]], [[1]]]), + } +] + +DUMMY_CONFIG = {"name": "test_dataset", "dummy0": 0, "dummy1": 1} + +OTHER_DUMMY_CONFIG = {"name": "other_test_dataset", "dummy0": 0, "dummy1": 1} + + +def mock_get_world_size(): + return 2 + + +def mock_get_rank(): + return 1 + + +@register_dataset("test_dataset") +class TestDataset(classy_dataset.ClassyDataset): + """Test dataset for validating registry functions""" + + def __init__( + self, + samples, + batchsize_per_replica=1, + num_samples=None, + shuffle=False, + transform=None, + ): + input_tensors = [sample["input"] for sample in samples] + target_tensors = [sample["target"] for sample in samples] + dataset = ListDataset(input_tensors, target_tensors, loader=lambda x: x) + super().__init__( + dataset=dataset, + split=None, + batchsize_per_replica=batchsize_per_replica, + shuffle=shuffle, + transform=transform, + num_samples=len(samples) if num_samples is None else num_samples, + ) + + @classmethod + def from_config(cls, config, *args, **kwargs): + return cls(*args, **kwargs) + + +@register_dataset("other_test_dataset") +class OtherTestDataset(classy_dataset.ClassyDataset): + """ + Test dataset for validating registry functions that has a different + type than TestDataset + """ + + def __init__(self, samples, batchsize_per_replica=1): + input_tensors = [sample["input"] for sample in samples] + target_tensors = [sample["target"] for sample in samples] + dataset = ListDataset(input_tensors, target_tensors, loader=lambda x: x) + super().__init__( + dataset=dataset, + split=None, + batchsize_per_replica=batchsize_per_replica, + shuffle=False, + transform=None, + num_samples=len(samples), + ) + + @classmethod + def from_config(cls, config, *args, **kwargs): + return cls(*args, **kwargs) + + +class TestRegistryFunctions(unittest.TestCase): + """ + Tests functions that use registry + """ + + def test_build_model(self): + dataset = build_dataset(DUMMY_CONFIG, DUMMY_SAMPLES_1) + self.assertTrue(isinstance(dataset, TestDataset)) + + +class TestClassyDataset(unittest.TestCase): + """ + Tests member functions of ClassyDataset. Note, NotImplemented + functions are mocked in TestDataset class. + """ + + def setUp(self): + self.dataset1 = build_dataset(DUMMY_CONFIG, DUMMY_SAMPLES_1) + self.dataset2 = build_dataset(DUMMY_CONFIG, DUMMY_SAMPLES_2) + + def _compare_samples(self, sample1, sample2): + compare_samples(self, sample1, sample2) + + def _compare_batches(self, batch1, batch2): + compare_batches(self, batch1, batch2) + + def test_init(self): + self.assertTrue(self.dataset1 is not None) + self.assertTrue(self.dataset2 is not None) + + def test_len(self): + self.assertEqual(len(self.dataset1), 1) + self.assertEqual(len(self.dataset2), 2) + + def test_getitem(self): + sample = self.dataset1[0] + self._compare_samples(sample, DUMMY_SAMPLES_1[0]) + + for idx in range(len(self.dataset2)): + sample = self.dataset2[idx] + self._compare_samples(sample, DUMMY_SAMPLES_2[idx]) + + def test_get_iterator(self): + # Verifies that we can retrieve samples with iterators + dl = self.dataset1.iterator(num_workers=0) + assert isinstance( + dl, DataLoader + ), "Classy Iterator should return instance of PyTorch Dataloader" + next(iter(dl)) + + dl = self.dataset1.iterator(num_workers=2) + assert isinstance( + dl, DataLoader + ), "Classy Iterator should return instance of PyTorch Dataloader" + it = iter(dl) + next(it) + # Because we use multiprocessing we delete the iterable to + # shutdown workers + del it + + def test_batch_logic(self): + dataset = TestDataset(DUMMY_SAMPLES_2, batchsize_per_replica=2) + dl = dataset.iterator(num_workers=0) + batch = next(iter(dl)) + self.assertEqual(batch["input"].size()[0], 2) + self._compare_batches(batch, BATCHED_DUMMY_SAMPLES_2[0]) + + @mock.patch( + "classy_vision.dataset.classy_dataset.get_world_size", mock_get_world_size + ) + @mock.patch("classy_vision.dataset.classy_dataset.get_rank", mock_get_rank) + def test_shard_logic(self): + # This test uses a world size of 2, rank 1 to verify that the + # second sample is returned by the dataloader + dataset = TestDataset(DUMMY_SAMPLES_2, batchsize_per_replica=1) + dl = dataset.iterator(num_workers=0) + sample = next(iter(dl)) + self._compare_batches(sample, DUMMY_SAMPLES_2[1]) + + def test_num_samples_logic(self): + dataset = TestDataset(DUMMY_SAMPLES_2) + self.assertEqual(len(dataset), 2) + + dataset = TestDataset(DUMMY_SAMPLES_2, num_samples=1) + # Verify len returns right value for dataset + self.assertEqual(len(dataset), 1) + # Verify len returns right value for iterator + self.assertEqual(len(dataset.iterator(num_workers=0)), 1) + # Verify iterator returns correct number of samples + it = iter(dataset.iterator(num_workers=0)) + num_samples = 0 + while True: + try: + next(it) + num_samples += 1 + except StopIteration: + break + self.assertEqual(num_samples, 1) + + # Check assert for num_samples > length of base dataset + dataset = TestDataset(DUMMY_SAMPLES_2, num_samples=3) + with self.assertRaises(AssertionError): + len(dataset) + + def test_shuffle_logic(self): + # Simple samples to test shuffling, just a single value tensor + # so we know how things were shuffled + dummy_samples_10 = [ + {"input": torch.tensor([[0]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[1]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[2]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[3]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[4]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[5]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[6]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[7]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[8]]), "target": torch.tensor([0])}, + {"input": torch.tensor([[9]]), "target": torch.tensor([0])}, + ] + dataset = TestDataset(dummy_samples_10, shuffle=True) + + def unpack_tensors(tensor_list): + return [t["input"].item() for t in tensor_list] + + # Epoch 0 + iterator = dataset.iterator(num_workers=0, current_phase_id=0) + it = iter(iterator) + epoch_0_list = [sample for sample in it] + epoch_0_list = unpack_tensors(epoch_0_list) + + # Epoch 1 + iterator = dataset.iterator(num_workers=0, current_phase_id=1) + it = iter(iterator) + epoch_1_list = [sample for sample in it] + epoch_1_list = unpack_tensors(epoch_1_list) + + # Should be same length, should be shuffled, should be + # different shuffles for each epoch + self.assertEqual(len(epoch_0_list), len(epoch_1_list)) + self.assertTrue(epoch_0_list != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + self.assertTrue(epoch_0_list != epoch_1_list) + + # Test different shuffle seeds + iterator = dataset.iterator(num_workers=0, current_phase_id=0, shuffle_seed=10) + it = iter(iterator) + epoch_0_seed_10_list = [sample for sample in it] + epoch_0_seed_10_list = unpack_tensors(epoch_0_seed_10_list) + self.assertTrue(epoch_0_seed_10_list != epoch_0_list) + + def test_transform_logic(self): + def _return_1_transform(sample): + return 1 + + dataset = TestDataset(DUMMY_SAMPLES_2, transform=_return_1_transform) + sample = dataset[0] + self.assertEqual(sample, 1) diff --git a/test/dataset_classy_imagenet_test.py b/test/dataset_classy_imagenet_test.py new file mode 100644 index 0000000000..0304a27be7 --- /dev/null +++ b/test/dataset_classy_imagenet_test.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +import shutil +import tempfile +import unittest + +import PIL +from classy_vision.dataset import build_dataset +from classy_vision.dataset.classy_imagenet import ImageNetDataset +from torchvision import transforms + + +class TestImageNet(unittest.TestCase): + def get_test_image_dataset(self, num_samples): + config = { + "name": "synthetic_image", + "crop_size": 224, + "num_channels": 3, + "seed": 0, + "class_ratio": 0.5, + "num_samples": num_samples, + "batchsize_per_replica": 1, + "use_shuffle": False, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [{"name": "ToTensor"}], + "key": "input", + } + ], + } + dataset = build_dataset(config) + return dataset + + def setUp(self): + # create a base directory to write image files to + self.base_dir = tempfile.mkdtemp() + for split in ["train", "val"]: + os.makedirs(f"{self.base_dir}/{split}/0") + os.makedirs(f"{self.base_dir}/{split}/1") + + def tearDown(self): + # delete all the temporary data created + shutil.rmtree(self.base_dir) + + def test_imagenet_retrieve_sample(self): + num_samples = 10 + for split in ["train", "val"]: + dataloader = self.get_test_image_dataset(num_samples).iterator() + for i, sample in enumerate(dataloader): + input = sample["input"] + target = sample["target"] + image = transforms.ToPILImage()(input.squeeze()) + path = f"{self.base_dir}/{split}/{target.item()}/{i}.png" + # save the image in a lossless format (png) + image.save(path) + + dataset = ImageNetDataset( + split="train", + batchsize_per_replica=1, + shuffle=True, + transform=None, + num_samples=None, + root=self.base_dir, + ) + self.assertEqual(len(dataset), num_samples) + img, target = dataset[0] + self.assertTrue(isinstance(img, PIL.Image.Image)) + self.assertTrue(isinstance(target, int)) diff --git a/test/dataset_classy_video_dataset_test.py b/test/dataset_classy_video_dataset_test.py new file mode 100644 index 0000000000..4d045b8c66 --- /dev/null +++ b/test/dataset_classy_video_dataset_test.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +from classy_vision.dataset import build_dataset, register_dataset +from classy_vision.dataset.classy_video_dataset import ( + ClassyVideoDataset, + MaxLengthClipSampler, +) +from classy_vision.dataset.core import ListDataset +from classy_vision.dataset.transforms.util_video import ( + build_video_field_transform_default, +) +from torch.utils.data import Sampler + + +DUMMY_SAMPLES_1 = [ + { + "input": { + "video": torch.randint(0, 256, (8, 3, 128, 128), dtype=torch.uint8), + "audio": torch.rand(1000, 1, dtype=torch.float32), + }, + "target": torch.tensor([[0]]), + } +] + + +DUMMY_CONFIG = { + "name": "test_video_dataset", + "split": "train", + "batchsize_per_replica": 1, + "use_shuffle": True, + "num_samples": 1, + "frames_per_clip": 8, + "video_dir": "dummy_video_dir", +} + + +class MockClipSampler(Sampler): + def __init__(self, full_size=1000): + self.full_size = full_size + + def __iter__(self): + indices = list(range(self.full_size)) + return iter(indices) + + def __len__(self): + return self.full_size + + +@register_dataset("test_video_dataset") +class TestVideoDataset(ClassyVideoDataset): + """Test dataset for validating registry functions""" + + def __init__( + self, + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + clips_per_video, + samples, + ): + self.samples = samples + input_tensors = [sample["input"] for sample in samples] + target_tensors = [sample["target"] for sample in samples] + dataset = ListDataset(input_tensors, target_tensors, loader=lambda x: x) + super(TestVideoDataset, self).__init__( + dataset, + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + clips_per_video, + ) + + @classmethod + def from_config(cls, config, samples): + split = config.get("split") + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + ) = cls.parse_config(config) + transform = build_video_field_transform_default(transform_config, split) + return cls( + split, + batchsize_per_replica, + shuffle, + transform, + num_samples, + clips_per_video, + samples, + ) + + +class TestRegistryFunctions(unittest.TestCase): + """ + Tests functions that use registry + """ + + def test_build_dataset(self): + dataset = build_dataset(DUMMY_CONFIG, DUMMY_SAMPLES_1) + self.assertTrue(isinstance(dataset, TestVideoDataset)) + + +class TestClassyVideoDataset(unittest.TestCase): + """ + Tests member functions of ClassyVideoDataset. + """ + + def setUp(self): + self.dataset = build_dataset(DUMMY_CONFIG, DUMMY_SAMPLES_1) + + def test_parse_config(self): + ( + transform_config, + batchsize_per_replica, + shuffle, + num_samples, + frames_per_clip, + video_width, + video_height, + video_min_dimension, + audio_samples, + step_between_clips, + frame_rate, + clips_per_video, + ) = self.dataset.parse_config(DUMMY_CONFIG) + + def test_max_length_clip_sampler(self): + clip_sampler = MockClipSampler(full_size=1000) + clip_sampler = MaxLengthClipSampler(clip_sampler, num_samples=64) + count = 0 + for _clip_index in iter(clip_sampler): + count += 1 + self.assertEqual(count, 64) + self.assertEqual(len(clip_sampler), 64) diff --git a/test/dataset_image_path_dataset_test.py b/test/dataset_image_path_dataset_test.py new file mode 100644 index 0000000000..2fac897f42 --- /dev/null +++ b/test/dataset_image_path_dataset_test.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +import shutil +import tempfile +import unittest + +import torch +from classy_vision.dataset import ClassyDataset, build_dataset +from classy_vision.dataset.image_path_dataset import ImagePathDataset +from torchvision import transforms + + +class TestImageDataset(unittest.TestCase): + def get_test_image_dataset(self): + config = { + "name": "synthetic_image", + "crop_size": 224, + "num_channels": 3, + "seed": 0, + "class_ratio": 0.5, + "num_samples": 100, + "batchsize_per_replica": 1, + "use_shuffle": False, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [{"name": "ToTensor"}], + "key": "input", + } + ], + } + dataset = build_dataset(config) + return dataset + + def setUp(self): + # create a base directory to write image files to + self.base_dir = tempfile.mkdtemp() + os.mkdir(f"{self.base_dir}/0") + os.mkdir(f"{self.base_dir}/1") + + def tearDown(self): + # delete all the temporary data created + shutil.rmtree(self.base_dir) + + def get_dataset_config(self): + return { + "batchsize_per_replica": 1, + "use_shuffle": False, + "num_samples": None, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [{"name": "ToTensor"}], + "key": "input", + } + ], + } + + @unittest.skip( + "Skipping test since build_dataset doesn't " + "work right now for ImagePathDataset" + ) + def test_build_dataset(self): + config = self.get_dataset_config() + dataset = build_dataset(config) + self.assertIsInstance(dataset, ClassyDataset) + + def test_image_dataset(self): + image_paths = [] + inputs = [] + targets = [] + dataloader = self.get_test_image_dataset().iterator() + for i, sample in enumerate(dataloader): + input = sample["input"] + target = sample["target"] + image = transforms.ToPILImage()(input.squeeze()) + path = f"{self.base_dir}/{target.item()}/{i}.png" + # save the image in a lossless format (png) + image.save(path) + image_paths.append(path) + inputs.append(input) + targets.append(target) + + # config for the image dataset + config = self.get_dataset_config() + + # create an image dataset from the list of images + dataset = ImagePathDataset.from_config( + config, image_paths=image_paths, targets=targets + ) + dataloader = dataset.iterator() + # the samples should be in the same order + for sample, expected_input, expected_target in zip(dataloader, inputs, targets): + self.assertTrue(torch.allclose(sample["input"], expected_input)) + self.assertEqual(sample["target"], expected_target) + + # test the dataset works without targets as well + dataset = ImagePathDataset.from_config(config, image_paths=image_paths) + dataloader = dataset.iterator() + # the samples should be in the same order + for sample, expected_input in zip(dataloader, inputs): + self.assertTrue(torch.allclose(sample["input"], expected_input)) + + # create an image dataset from the root dir + dataset = ImagePathDataset.from_config(config, image_paths=self.base_dir) + dataloader = dataset.iterator() + # test that we get the same class distribution + # we don't test the actual samples since the ordering isn't defined + counts = [0, 0] + for sample in dataloader: + counts[sample["target"].item()] += 1 + expected_counts = [0, 0] + for target in targets: + expected_counts[target.item()] += 1 + self.assertEqual(counts, expected_counts) diff --git a/test/dataset_transforms_lighting_transform_test.py b/test/dataset_transforms_lighting_transform_test.py new file mode 100644 index 0000000000..c280bc5ccc --- /dev/null +++ b/test/dataset_transforms_lighting_transform_test.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +from classy_vision.dataset.core.random_image_datasets import ( + RandomImageBinaryClassDataset, +) +from classy_vision.dataset.transforms.util import build_field_transform_default_imagenet + + +class LightingTransformTest(unittest.TestCase): + def get_test_image_dataset(self): + return RandomImageBinaryClassDataset( + crop_size=224, class_ratio=0.5, num_samples=100, seed=0 + ) + + def test_lighting_transform_no_errors(self): + """ + Tests that the lighting transform runs without any errors. + """ + dataset = self.get_test_image_dataset() + + config = [{"name": "ToTensor"}, {"name": "lighting"}] + transform = build_field_transform_default_imagenet(config) + sample = dataset[0] + try: + # test that lighting has been registered and runs without errors + transform(sample) + except Exception: + self.fail("LightingTransform raised an exception") + return diff --git a/test/dataset_transforms_test.py b/test/dataset_transforms_test.py new file mode 100644 index 0000000000..b6317fb813 --- /dev/null +++ b/test/dataset_transforms_test.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +import torchvision.transforms as transforms +from classy_vision.dataset.transforms import ( + ClassyTransform, + build_transforms, + register_transform, +) +from classy_vision.dataset.transforms.util import ImagenetNoAugmentTransform + + +@register_transform("resize") +class resize(ClassyTransform): + def __init__(self, size: int): + self.transform = transforms.Resize(size=size) + + def __call__(self, img): + return self.transform(img) + + +@register_transform("center_crop") +class center_crop(ClassyTransform): + def __init__(self, size: int): + self.transform = transforms.CenterCrop(size=size) + + def __call__(self, img): + return self.transform(img) + + +class DatasetTransformsTest(unittest.TestCase): + def get_test_image(self): + return transforms.ToPILImage()(torch.randn((3, 224, 224))) + + def test_transforms(self): + input = self.get_test_image() + + # reference transform which we will use to validate the built transforms + reference_transform = ImagenetNoAugmentTransform() + reference_output = reference_transform(input) + + # test a registered transform + config = [{"name": "imagenet_no_augment"}] + transform = build_transforms(config) + output = transform(input) + self.assertTrue(torch.allclose(output, reference_output)) + + # test a transform built using torchvision transforms + config = [ + {"name": "Resize", "size": 256}, + {"name": "CenterCrop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ] + transform = build_transforms(config) + output = transform(input) + self.assertTrue(torch.allclose(output, reference_output)) + + # test a combination of registered and torchvision transforms + config = [ + {"name": "resize", "size": 256}, + {"name": "center_crop", "size": 224}, + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ] + transform = build_transforms(config) + output = transform(input) + self.assertTrue(torch.allclose(output, reference_output)) diff --git a/test/dataset_transforms_util_test.py b/test/dataset_transforms_util_test.py new file mode 100644 index 0000000000..1f2d21061b --- /dev/null +++ b/test/dataset_transforms_util_test.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import random +import unittest + +import numpy +import torch +import torchvision.transforms as transforms +from classy_vision.dataset.core.random_image_datasets import ( + RandomImageBinaryClassDataset, + SampleType, +) +from classy_vision.dataset.transforms import build_transforms +from classy_vision.dataset.transforms.util import ( + GenericImageTransform, + ImagenetAugmentTransform, + ImagenetNoAugmentTransform, + build_field_transform_default_imagenet, +) + + +class DatasetTransformsUtilTest(unittest.TestCase): + def get_test_image_dataset(self, sample_type): + return RandomImageBinaryClassDataset( + crop_size=224, + class_ratio=0.5, + num_samples=100, + seed=0, + sample_type=sample_type, + ) + + def transform_checks( + self, sample, transform, expected_transform, key, transformed_key=None + ): + # If transformed key is None, then use key + transformed_key = transformed_key if transformed_key is not None else key + input_image = copy.deepcopy(sample[key]) + + torch.manual_seed(0) + numpy.random.seed(0) + random.seed(0) + output_image = transform(sample)[transformed_key] + + torch.manual_seed(0) + numpy.random.seed(0) + random.seed(0) + self.assertTrue(torch.allclose(output_image, expected_transform(input_image))) + + def test_build_dict_field_transform_default_imagenet(self): + dataset = self.get_test_image_dataset(SampleType.DICT) + + # should apply the transform in the config + config = [{"name": "ToTensor"}] + default_transform = transforms.Compose( + [transforms.CenterCrop(100), transforms.ToTensor()] + ) + transform = build_field_transform_default_imagenet( + config, default_transform=default_transform + ) + sample = dataset[0] + self.transform_checks(sample, transform, transforms.ToTensor(), "input") + + # should apply default_transform + config = None + transform = build_field_transform_default_imagenet( + config, default_transform=default_transform + ) + sample = dataset[0] + self.transform_checks(sample, transform, default_transform, "input") + + # should apply the transform for a test split + transform = build_field_transform_default_imagenet(config, split="test") + sample = dataset[0] + self.transform_checks(sample, transform, ImagenetNoAugmentTransform(), "input") + + def test_build_tuple_field_transform_default_imagenet(self): + dataset = self.get_test_image_dataset(SampleType.TUPLE) + + # should apply the transform in the config + config = [{"name": "ToTensor"}] + default_transform = transforms.Compose( + [transforms.CenterCrop(100), transforms.ToTensor()] + ) + transform = build_field_transform_default_imagenet( + config, default_transform=default_transform, key=0, key_map_transform=None + ) + sample = dataset[0] + self.transform_checks(sample, transform, transforms.ToTensor(), 0) + + # should apply default_transform + config = None + transform = build_field_transform_default_imagenet( + config, default_transform=default_transform, key=0, key_map_transform=None + ) + sample = dataset[0] + self.transform_checks(sample, transform, default_transform, 0) + + # should apply the transform for a test split + transform = build_field_transform_default_imagenet( + config, split="test", key=0, key_map_transform=None + ) + sample = dataset[0] + self.transform_checks(sample, transform, ImagenetNoAugmentTransform(), 0) + + def test_apply_transform_to_key_from_config(self): + dataset = self.get_test_image_dataset(SampleType.DICT) + + config = [ + { + "name": "apply_transform_to_key", + "transforms": [{"name": "ToTensor"}], + "key": "input", + } + ] + transform = build_transforms(config) + sample = dataset[0] + self.transform_checks(sample, transform, transforms.ToTensor(), "input") + + def test_generic_image_transform(self): + dataset = self.get_test_image_dataset(SampleType.TUPLE) + + # Check class constructor + transform = GenericImageTransform(transform=transforms.ToTensor()) + sample = dataset[0] + self.transform_checks(sample, transform, transforms.ToTensor(), 0, "input") + + transform = GenericImageTransform(split="train") + sample = dataset[0] + self.transform_checks(sample, transform, ImagenetAugmentTransform(), 0, "input") + + transform = GenericImageTransform(split="test") + sample = dataset[0] + self.transform_checks( + sample, transform, ImagenetNoAugmentTransform(), 0, "input" + ) + + # Check from_config constructor / registry + config = [ + {"name": "generic_image_transform", "transforms": [{"name": "ToTensor"}]} + ] + transform = build_transforms(config) + sample = dataset[0] + self.transform_checks(sample, transform, transforms.ToTensor(), 0, "input") + + # Check with Imagenet defaults + config = [{"name": "generic_image_transform", "split": "train"}] + transform = build_transforms(config) + sample = dataset[0] + self.transform_checks(sample, transform, ImagenetAugmentTransform(), 0, "input") + + config = [{"name": "generic_image_transform", "split": "test"}] + transform = build_transforms(config) + sample = dataset[0] + self.transform_checks( + sample, transform, ImagenetNoAugmentTransform(), 0, "input" + ) diff --git a/test/dataset_transforms_util_video_test.py b/test/dataset_transforms_util_video_test.py new file mode 100644 index 0000000000..4c5f7d18c9 --- /dev/null +++ b/test/dataset_transforms_util_video_test.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +import torch +import torchvision.transforms._transforms_video as transforms_video +from classy_vision.dataset.core.random_video_datasets import RandomVideoDataset +from classy_vision.dataset.transforms.util_video import ( + VideoConstants, + build_video_field_transform_default, +) + + +class DatasetTransformUtilVideoTest(unittest.TestCase): + def get_test_video_dataset(self): + self.num_classes = 10 + self.split = "train" + self.num_samples = 100 + self.frames_per_clip = 32 + self.video_width = 320 + self.video_height = 256 + self.audio_samples = 44000 + self.clips_per_video = 1 + self.seed = 1 + + dataset = RandomVideoDataset( + self.num_classes, + self.split, + self.num_samples, + self.frames_per_clip, + self.video_width, + self.video_height, + self.audio_samples, + self.clips_per_video, + self.seed, + ) + return dataset + + def test_build_field_transform_default_video(self): + dataset = self.get_test_video_dataset() + + # transform config is not provided. Use default transforms + config = None + # default training data transform + sample = dataset[0] + + transform = build_video_field_transform_default(config, "train") + output_clip = transform(sample)["input"]["video"] + self.assertEqual( + output_clip.size(), + torch.Size( + ( + 3, + self.frames_per_clip, + VideoConstants.CROP_SIZE, + VideoConstants.CROP_SIZE, + ) + ), + ) + # default testing data transform + sample = dataset[1] + sample_copy = copy.deepcopy(sample) + + expected_output_clip = transforms_video.ToTensorVideo()( + sample["input"]["video"] + ) + expected_output_clip = transforms_video.CenterCropVideo( + VideoConstants.CROP_SIZE + )(expected_output_clip) + expected_output_clip = transforms_video.NormalizeVideo( + mean=VideoConstants.MEAN, std=VideoConstants.STD + )(expected_output_clip) + + transform = build_video_field_transform_default(config, "test") + output_clip = transform(sample_copy)["input"]["video"] + + rescaled_width = int( + VideoConstants.SIZE_RANGE[0] * self.video_width / self.video_height + ) + self.assertEqual( + output_clip.size(), + torch.Size( + (3, self.frames_per_clip, VideoConstants.SIZE_RANGE[0], rescaled_width) + ), + ) + # transform config is provided. Simulate training config + sample = dataset[2] + config = { + "video": [ + {"name": "ToTensorVideo"}, + { + "name": "video_clip_random_resize_crop", + "crop_size": 64, + "size_range": [256, 320], + }, + {"name": "RandomHorizontalFlipVideo"}, + { + "name": "NormalizeVideo", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ] + } + transform = build_video_field_transform_default(config, "train") + output_clip = transform(sample)["input"]["video"] + self.assertEqual( + output_clip.size(), torch.Size((3, self.frames_per_clip, 64, 64)) + ) + self.assertTrue(output_clip.dtype == torch.float) + + # transform config is provided. Simulate testing config + sample = dataset[3] + config = { + "video": [ + {"name": "ToTensorVideo"}, + {"name": "video_clip_resize", "size": 64}, + { + "name": "NormalizeVideo", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ] + } + transform = build_video_field_transform_default(config, "train") + output_clip = transform(sample)["input"]["video"] + + rescaled_width = int(64 * self.video_width / self.video_height) + self.assertEqual( + output_clip.size(), + torch.Size((3, self.frames_per_clip, 64, rescaled_width)), + ) + self.assertTrue(output_clip.dtype == torch.float) diff --git a/test/generic/__init__.py b/test/generic/__init__.py new file mode 100644 index 0000000000..734a1eb4e2 --- /dev/null +++ b/test/generic/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. diff --git a/test/generic/config_utils.py b/test/generic/config_utils.py new file mode 100644 index 0000000000..42e8aac669 --- /dev/null +++ b/test/generic/config_utils.py @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from classy_vision.tasks import build_task + + +def get_test_task_config(head_num_classes=1000): + return { + "name": "classification_task", + "num_epochs": 12, + "loss": {"name": "CrossEntropyLoss"}, + "dataset": { + "train": { + "name": "synthetic_image", + "split": "train", + "crop_size": 224, + "class_ratio": 0.5, + "num_samples": 2000, + "seed": 0, + "batchsize_per_replica": 32, + "use_shuffle": True, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + "test": { + "name": "synthetic_image", + "split": "test", + "crop_size": 224, + "class_ratio": 0.5, + "num_samples": 2000, + "seed": 0, + "batchsize_per_replica": 32, + "use_shuffle": False, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + }, + "meters": {"accuracy": {"topk": [1, 5]}}, + "model": { + "name": "resnet", + "num_blocks": [3, 4, 6, 3], + "small_input": False, + "zero_init_bn_residuals": True, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": head_num_classes, + "fork_block": "block3-2", + "in_plane": 2048, + } + ], + }, + "optimizer": { + "name": "sgd", + "num_epochs": 12, + "lr": {"name": "step", "values": [0.1, 0.01]}, + "weight_decay": 1e-4, + "momentum": 0.9, + }, + } + + +def get_fast_test_task_config(head_num_classes=1000): + return { + "name": "classification_task", + "num_epochs": 1, + "loss": {"name": "CrossEntropyLoss"}, + "dataset": { + "train": { + "name": "synthetic_image", + "split": "train", + "crop_size": 20, + "class_ratio": 0.5, + "num_samples": 10, + "seed": 0, + "batchsize_per_replica": 2, + "use_shuffle": False, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + "test": { + "name": "synthetic_image", + "split": "test", + "crop_size": 20, + "class_ratio": 0.5, + "num_samples": 10, + "seed": 0, + "batchsize_per_replica": 2, + "use_shuffle": False, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + }, + "model": { + "name": "resnet", + "num_blocks": [1], + "small_input": False, + "zero_init_bn_residuals": True, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": head_num_classes, + "fork_block": "block0-0", + "in_plane": 256, + } + ], + }, + "meters": {"accuracy": {"topk": [1]}}, + "optimizer": {"name": "sgd", "lr": 0.01, "weight_decay": 1e-4, "momentum": 0.9}, + } + + +def get_test_classy_task(): + config = get_test_task_config() + task = build_task(config) + return task + + +def get_test_mlp_task_config(): + return { + "name": "classification_task", + "num_epochs": 10, + "loss": {"name": "CrossEntropyLoss"}, + "dataset": { + "train": { + "name": "synthetic_image", + "split": "train", + "num_classes": 2, + "crop_size": 20, + "class_ratio": 0.5, + "num_samples": 10, + "seed": 0, + "batchsize_per_replica": 3, + "use_augmentation": False, + "use_shuffle": True, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + "test": { + "name": "synthetic_image", + "split": "test", + "num_classes": 2, + "crop_size": 20, + "class_ratio": 0.5, + "num_samples": 10, + "seed": 0, + "batchsize_per_replica": 1, + "use_augmentation": False, + "use_shuffle": False, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + }, + "model": { + "name": "mlp", + # 3x20x20 = 1200 + "input_dim": 1200, + "output_dim": 1000, + "hidden_dims": [10], + }, + "meters": {"accuracy": {"topk": [1]}}, + "optimizer": { + "name": "sgd", + "num_epochs": 10, + "lr": 0.1, + "weight_decay": 1e-4, + "momentum": 0.9, + }, + } + + +def get_test_model_configs(): + return [ + # resnet 101 + { + "name": "resnet", + "num_blocks": [3, 4, 6, 3], + "small_input": False, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048, + } + ], + }, + # resnext 101 32-4d + { + "name": "resnext", + "num_blocks": [3, 4, 6, 3], + "base_width_and_cardinality": [4, 32], + "small_input": False, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-2", + "in_plane": 2048, + } + ], + }, + ] + + +def get_test_video_task_config(): + return { + "name": "classification_task", + "num_epochs": 27, + "loss": {"name": "CrossEntropyLoss"}, + "dataset": { + "train": { + "name": "synthetic_video", + "split": "train", + "batchsize_per_replica": 8, + "use_shuffle": True, + "num_samples": 128, + "frames_per_clip": 8, + "video_height": 128, + "video_width": 160, + "num_classes": 50, + "clips_per_video": 1, + }, + "test": { + "name": "synthetic_video", + "split": "test", + "batchsize_per_replica": 10, + "use_shuffle": False, + "num_samples": 40, + "frames_per_clip": 8, + "video_height": 128, + "video_width": 160, + "num_classes": 50, + "clips_per_video": 10, + }, + }, + "meters": {"accuracy": {"topk": [1, 5]}}, + "model": { + "name": "resnext3d", + "frames_per_clip": 8, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "postactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 5, + "stem_spatial_kernel": 7, + "stem_maxpool": True, + "stage_planes": 64, + "stage_temporal_kernel_basis": [[3], [3, 1], [3, 1], [1, 3]], + "temporal_conv_1x1": [True, True, True, True], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 50, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [8, 7, 7], + "activation_func": "softmax", + "num_classes": 50, + "fork_block": "pathway0-stage4-block2", + "in_plane": 512, + "use_dropout": True, + } + ], + }, + "optimizer": { + "name": "sgd", + "lr": { + "name": "multistep", + "num_epochs": 10, + "values": [0.1, 0.01, 0.001, 0.0001], + "milestones": [3, 7, 9], + }, + "weight_decay": 0.0001, + "momentum": 0.9, + }, + } + + +def get_test_classy_video_task(): + config = get_test_video_task_config() + task = build_task(config) + return task diff --git a/test/generic/merge_dataset.py b/test/generic/merge_dataset.py new file mode 100644 index 0000000000..7f70ab793f --- /dev/null +++ b/test/generic/merge_dataset.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + + +class MergeDataset: + """ + Dataset that merges samples from multiple datasets into single sample. + + If datasets have distinct keys, then we merge dicts, e.g. + + dataset1[idx] = {'input': input_tensor} + dataset2[idx] = {'target': target_tensor} + merged_dataset[idx] = {'input': input_tensor, 'target': target_tensor} + + If datasets have matching keys then we create a list and append, e.g. + + dataset1[idx] = {'input': input_tensor1} + dataset2[idx] = {'input': input_tensor2} + merged_dataset[idx] = {'input': [input_tensor1, input_tensor2]} + + Note, if your datasets' samples do not have consistent keys for each sample, + this could lead to inconsistent samples merged samples. + """ + + def __init__(self, datasets): + + # assertions: + assert isinstance(datasets, list) + assert all(len(dataset) == len(datasets[0]) for dataset in datasets) + + # create object: + super(MergeDataset, self).__init__() + self.datasets = datasets + + def __getitem__(self, idx): + final_sample = {} + for dataset in self.datasets: + curr_sample = dataset[idx] + assert isinstance(curr_sample, dict), "Merge dataset only supports dicts" + for key in curr_sample.keys(): + # If keys are distinct, then + if key not in final_sample: + final_sample[key] = curr_sample[key] + elif not isinstance(final_sample[key], list): + final_sample[key] = [final_sample[key], curr_sample[key]] + else: + final_sample[key].append(curr_sample[key]) + + return final_sample + + def __len__(self): + return len(self.datasets[0]) diff --git a/test/generic/meter_test_utils.py b/test/generic/meter_test_utils.py new file mode 100644 index 0000000000..7d489f8df4 --- /dev/null +++ b/test/generic/meter_test_utils.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import multiprocessing +import queue +import tempfile +import unittest + +import torch + + +UPDATE_SIGNAL = 0 +VALUE_SIGNAL = 1 +SHUTDOWN_SIGNAL = 2 +TIMEOUT = 100 + + +def _get_value_or_raise_error(qout, qerr): + try: + return qout.get(timeout=TIMEOUT) + except queue.Empty: + raise qerr.get(timeout=TIMEOUT) + + +def _run(qin, qout, qerr, func, *args): + try: + func(qin, qout, *args) + except Exception as e: + print(e) + qerr.put(e) + + +def _meter_worker(qin, qout, meter, is_train, world_size, rank, filename): + backend = "gloo" + torch.distributed.init_process_group( + backend=backend, + init_method="file://{filename}".format(filename=filename), + world_size=world_size, + rank=rank, + ) + # Listen for commands on queues + while True: + try: + signal, val = qin.get() + except queue.Empty: + continue + + if signal == UPDATE_SIGNAL: + meter.update(val[0], val[1], is_train=is_train) + + elif signal == VALUE_SIGNAL: + meter.sync_state() + qout.put(meter.value) + + elif signal == SHUTDOWN_SIGNAL: + break + + else: + raise NotImplementedError("Bad signal value") + + return + + +class ClassificationMeterTest(unittest.TestCase): + def setUp(self): + self.mp = multiprocessing.get_context("spawn") + self.processes = [] + + def tearDown(self): + for p in self.processes: + p.terminate() + + def _spawn(self, func, *args): + name = "process #%d" % len(self.processes) + qin = self.mp.Queue() + qout = self.mp.Queue() + qerr = self.mp.Queue() + qio = (qin, qout, qerr) + args = qio + (func,) + args + process = self.mp.Process(target=_run, name=name, args=args, daemon=True) + process.start() + self.processes.append(process) + return qio + + def _apply_updates_and_test_meter( + self, meter, model_output, target, expected_value, **kwargs + ): + """ + Runs a valid meter test. Does not reset meter before / after running + """ + if not isinstance(model_output, list): + model_output = [model_output] + + if not isinstance(target, list): + target = [target] + + for i in range(len(model_output)): + meter.update(model_output[i], target[i], **kwargs) + + meter.sync_state() + meter_value = meter.value + for key, val in expected_value.items(): + self.assertTrue( + key in meter_value, msg="{0} not in meter value!".format(key) + ) + self.assertAlmostEqual( + meter_value[key], + val, + places=4, + msg="{0} meter value mismatch!".format(key), + ) + + def _values_match_expected_value(self, value0, value1, expected_value): + for key, val in expected_value.items(): + self.assertTrue(key in value0, msg="{0} not in meter value!".format(key)) + self.assertAlmostEqual( + value0[key], val, places=4, msg="{0} meter value mismatch!".format(key) + ) + self.assertTrue(key in value1, msg="{0} not in meter value!".format(key)) + self.assertAlmostEqual( + value1[key], val, places=4, msg="{0} meter value mismatch!".format(key) + ) + + def meter_update_and_reset_test( + self, meter, model_outputs, targets, expected_value, **kwargs + ): + """ + This test verifies that a single update on the meter is successful, + resets the meter, then applies the update again. + """ + # If a single output is provided, wrap in list + if not isinstance(model_outputs, list): + model_outputs = [model_outputs] + targets = [targets] + + for i in range(len(model_outputs)): + meter.validate(model_outputs[i].size(), targets[i].size()) + + self._apply_updates_and_test_meter( + meter, model_outputs, targets, expected_value, **kwargs + ) + + meter.reset() + + # Verify reset works by reusing single update test + self._apply_updates_and_test_meter( + meter, model_outputs, targets, expected_value, **kwargs + ) + + def meter_invalid_meter_input_test(self, meter, model_output, target): + # Invalid model + with self.assertRaises(AssertionError): + meter.validate(model_output.shape, target.shape) + + def meter_invalid_update_test(self, meter, model_output, target, **kwargs): + """ + Runs a valid meter test. Does not reset meter before / after running + """ + if not isinstance(model_output, list): + model_output = [model_output] + + if not isinstance(target, list): + target = [target] + + with self.assertRaises(AssertionError): + for i in range(len(model_output)): + meter.update(model_output[i], target[i], **kwargs) + + def meter_get_set_classy_state_test( + self, meters, model_outputs, targets, expected_value, **kwargs + ): + """ + Tests get and set classy state methods of meter. + """ + assert len(meters) == 2, "Incorrect number of meters passed to test" + assert ( + len(model_outputs) == 2 + ), "Incorrect number of model_outputs passed to test" + assert len(targets) == 2, "Incorrect number of targets passed to test" + meter0 = meters[0] + meter1 = meters[1] + + meter0.update(model_outputs[0], targets[0], **kwargs) + meter1.update(model_outputs[1], targets[1], **kwargs) + + meter0.sync_state() + value0 = meter0.value + + meter1.sync_state() + value1 = meter1.value + for key, val in value0.items(): + self.assertNotEqual( + value1[key], val, msg="{0} meter values should not be same!".format(key) + ) + + meter0.set_classy_state(meter1.get_classy_state()) + value0 = meter0.value + for key, val in value0.items(): + self.assertAlmostEqual( + value1[key], + val, + places=4, + msg="{0} meter value mismatch after state transfer!".format(key), + ) + self.assertAlmostEqual( + value1[key], + expected_value[key], + places=4, + msg="{0} meter value mismatch from ground truth!".format(key), + ) + + def _spawn_all_meter_workers(self, world_size, meters, is_train): + filename = tempfile.NamedTemporaryFile(delete=True).name + qins = [] + qerrs = [] + qouts = [] + + for i in range(world_size): + qin, qout, qerr = self._spawn( + _meter_worker, meters[i], is_train, world_size, i, filename + ) + qins.append(qin) + qouts.append(qout) + qerrs.append(qerr) + + return qins, qouts, qerrs + + def meter_distributed_test( + self, meters, model_outputs, targets, expected_values, is_train=False + ): + """ + Sets up two processes each with a given meter on that process. + Verifies that sync code path works. + """ + world_size = len(meters) + assert world_size == 2, "This test only works for world_size of 2" + assert len(model_outputs) == 4, ( + "Test assumes 4 model outputs, " + "0, 2 passed to meter0 and 1, 3 passed to meter1" + ) + assert ( + len(targets) == 4 + ), "Test assumes 4 targets, 0, 2 passed to meter0 and 1, 3 passed to meter1" + assert len(expected_values) == 2, ( + "Test assumes 2 expected values, " + "first is result of applying updates 0,1 to the meter, " + "second is result of applying all 4 updates to meter" + ) + + qins, qouts, qerrs = self._spawn_all_meter_workers( + world_size, meters, is_train=is_train + ) + + # First update each meter, then get value from each meter + qins[0].put_nowait((UPDATE_SIGNAL, (model_outputs[0], targets[0]))) + qins[1].put_nowait((UPDATE_SIGNAL, (model_outputs[1], targets[1]))) + + qins[0].put_nowait((VALUE_SIGNAL, None)) + qins[1].put_nowait((VALUE_SIGNAL, None)) + + value0 = _get_value_or_raise_error(qouts[0], qerrs[0]) + value1 = _get_value_or_raise_error(qouts[1], qerrs[1]) + self._values_match_expected_value(value0, value1, expected_values[0]) + + # Verify that calling value again does not break things + qins[0].put_nowait((VALUE_SIGNAL, None)) + qins[1].put_nowait((VALUE_SIGNAL, None)) + + value0 = _get_value_or_raise_error(qouts[0], qerrs[0]) + value1 = _get_value_or_raise_error(qouts[1], qerrs[1]) + self._values_match_expected_value(value0, value1, expected_values[0]) + + # Second, update each meter, then get value from each meter + qins[0].put_nowait((UPDATE_SIGNAL, (model_outputs[2], targets[2]))) + qins[1].put_nowait((UPDATE_SIGNAL, (model_outputs[3], targets[3]))) + + qins[0].put_nowait((VALUE_SIGNAL, None)) + qins[1].put_nowait((VALUE_SIGNAL, None)) + + value0 = _get_value_or_raise_error(qouts[0], qerrs[0]) + value1 = _get_value_or_raise_error(qouts[1], qerrs[1]) + self._values_match_expected_value(value0, value1, expected_values[1]) + + qins[0].put_nowait((SHUTDOWN_SIGNAL, None)) + qins[1].put_nowait((SHUTDOWN_SIGNAL, None)) diff --git a/test/generic/optim_test_util.py b/test/generic/optim_test_util.py new file mode 100644 index 0000000000..998c57dcf6 --- /dev/null +++ b/test/generic/optim_test_util.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from abc import ABC, abstractmethod +from unittest.mock import MagicMock + +import torch +from classy_vision.models import ClassyModel +from classy_vision.optim import build_optimizer + + +class TestOptimizer(ABC): + @abstractmethod + def _get_config(self): + pass + + @abstractmethod + def _instance_to_test(self): + pass + + def _get_optimizer_params(self): + return { + "regularized_params": [ + torch.tensor([[1.0, 2.0]], requires_grad=True), + torch.tensor([[3.0, 4.0]], requires_grad=True), + ], + "unregularized_params": [torch.tensor([[1.0, 2.0]], requires_grad=True)], + } + + def _get_mock_classy_vision_model(self, trainable_params=True): + mock_classy_vision_model = ClassyModel() + + if trainable_params: + mock_classy_vision_model.get_optimizer_params = MagicMock( + return_value=self._get_optimizer_params() + ) + mock_classy_vision_model.parameters = MagicMock( + return_value=self._get_optimizer_params()["regularized_params"] + + self._get_optimizer_params()["unregularized_params"] + ) + else: + mock_classy_vision_model.get_optimizer_params = MagicMock( + return_value={"regularized_params": [], "unregularized_params": []} + ) + mock_classy_vision_model.parameters = MagicMock( + return_value=[ + param.detach() + for param in self._get_optimizer_params()["regularized_params"] + + self._get_optimizer_params()["unregularized_params"] + ] + ) + + return mock_classy_vision_model + + def _set_gradient(self, params, grad_values=None): + if grad_values is None: + grad_values = [0.1, 0.1] + for i in range(len(params)): + params[i].grad = torch.tensor([grad_values]) + + def _set_model_gradient(self, model, grad_values=None): + for param_type in ["regularized_params", "unregularized_params"]: + self._set_gradient(model.get_optimizer_params()[param_type], grad_values) + + def _compare_momentum_values(self, optim1, optim2): + self.assertEqual(len(optim1["param_groups"]), len(optim2["param_groups"])) + + for i in range(len(optim1["param_groups"])): + self.assertEqual( + len(optim1["param_groups"][i]["params"]), + len(optim2["param_groups"][i]["params"]), + ) + + for j in range(len(optim1["param_groups"][i]["params"])): + id1 = optim1["param_groups"][i]["params"][j] + id2 = optim2["param_groups"][i]["params"][j] + self.assertTrue( + torch.allclose( + optim1["state"][id1]["momentum_buffer"], + optim2["state"][id2]["momentum_buffer"], + ) + ) + + def _get_set_state(self, grad_values): + config = self._get_config() + + mock_classy_vision_model = self._get_mock_classy_vision_model() + opt1 = build_optimizer(config) + opt1.init_pytorch_optimizer(mock_classy_vision_model) + + self._set_model_gradient(mock_classy_vision_model, grad_values) + opt1.step() + state = opt1.get_classy_state() + + config["lr"] += 0.1 + opt2 = build_optimizer(config) + opt2.init_pytorch_optimizer(mock_classy_vision_model) + self.assertTrue(isinstance(opt1, self._instance_to_test())) + opt2.set_classy_state(state) + self.assertEqual(opt1.parameters, opt2.parameters) + for i in range(len(opt1.optimizer.param_groups[0]["params"])): + self.assertTrue( + torch.allclose( + opt1.optimizer.param_groups[0]["params"][i], + opt2.optimizer.param_groups[0]["params"][i], + ) + ) + self._compare_momentum_values( + opt1.get_classy_state()["optim"], opt2.get_classy_state()["optim"] + ) + + # check if the optimizers behave the same on params update + mock_classy_vision_model1 = self._get_mock_classy_vision_model() + mock_classy_vision_model2 = self._get_mock_classy_vision_model() + self._set_model_gradient(mock_classy_vision_model1, grad_values) + self._set_model_gradient(mock_classy_vision_model2, grad_values) + opt1 = build_optimizer(config) + opt1.init_pytorch_optimizer(mock_classy_vision_model1) + opt2 = build_optimizer(config) + opt2.init_pytorch_optimizer(mock_classy_vision_model2) + opt1.step() + opt2.step() + for i in range(len(opt1.optimizer.param_groups[0]["params"])): + print(opt1.optimizer.param_groups[0]["params"][i]) + self.assertTrue( + torch.allclose( + opt1.optimizer.param_groups[0]["params"][i], + opt2.optimizer.param_groups[0]["params"][i], + ) + ) + self._compare_momentum_values( + opt1.get_classy_state()["optim"], opt2.get_classy_state()["optim"] + ) + + def test_build_sgd(self): + config = self._get_config() + mock_classy_vision_model = self._get_mock_classy_vision_model( + trainable_params=True + ) + opt = build_optimizer(config) + opt.init_pytorch_optimizer(mock_classy_vision_model) + self.assertTrue(isinstance(opt, self._instance_to_test())) + + def test_raise_error_on_non_trainable_params(self): + # Test Raise ValueError if there are no trainable params in the model. + config = self._get_config() + with self.assertRaises(ValueError): + opt = build_optimizer(config) + opt.init_pytorch_optimizer( + self._get_mock_classy_vision_model(trainable_params=False) + ) + + def test_get_set_state(self): + for grad_values in [[0.1, 0.1], [-0.1, -0.1], [0.0, 0.0], [0.1, -0.1]]: + self._get_set_state(grad_values) + + def test_set_invalid_state(self): + config = self._get_config() + mock_classy_vision_model = self._get_mock_classy_vision_model() + opt = build_optimizer(config) + opt.init_pytorch_optimizer(mock_classy_vision_model) + self.assertTrue(isinstance(opt, self._instance_to_test())) + + with self.assertRaises(KeyError): + opt.set_classy_state({}) + + def test_lr_schedule(self): + config = self._get_config() + + mock_classy_vision_model = self._get_mock_classy_vision_model() + opt = build_optimizer(config) + opt.init_pytorch_optimizer(mock_classy_vision_model) + + # Test initial learning rate + for group in opt.optimizer.param_groups: + self.assertEqual(group["lr"], 0.1) + + def _test_lr_schedule(optimizer, num_epochs, epochs, targets): + for i in range(len(epochs)): + epoch = epochs[i] + target = targets[i] + param_groups = optimizer.optimizer.param_groups.copy() + optimizer.update_schedule_on_epoch(epoch / num_epochs) + for idx, group in enumerate(optimizer.optimizer.param_groups): + self.assertEqual(group["lr"], target) + # Make sure all but LR is same + param_groups[idx]["lr"] = target + self.assertEqual(param_groups[idx], group) + + # Test constant learning schedule + num_epochs = 90 + epochs = [0, 0.025, 0.05, 0.1, 0.5, 1, 15, 29, 30, 31, 59, 60, 61, 88, 89] + targets = [0.1] * 15 + _test_lr_schedule(opt, num_epochs, epochs, targets) + + # Test step learning schedule + config["lr"] = {"name": "step", "values": [0.1, 0.01, 0.001]} + opt = build_optimizer(config) + opt.init_pytorch_optimizer(mock_classy_vision_model) + targets = [0.1] * 8 + [0.01] * 3 + [0.001] * 4 + _test_lr_schedule(opt, num_epochs, epochs, targets) + + # Test step learning schedule with warmup + init_lr = 0.01 + warmup_epochs = 0.1 + config["lr"] = { + "name": "composite", + "schedulers": [ + {"name": "linear", "start_lr": init_lr, "end_lr": 0.1}, + {"name": "step", "values": [0.1, 0.01, 0.001]}, + ], + "update_interval": "epoch", + "interval_scaling": ["rescaled", "fixed"], + "lengths": [warmup_epochs / num_epochs, 1 - warmup_epochs / num_epochs], + } + + opt = build_optimizer(config) + opt.init_pytorch_optimizer(mock_classy_vision_model) + targets = [0.01, 0.0325, 0.055] + [0.1] * 5 + [0.01] * 3 + [0.001] * 4 + _test_lr_schedule(opt, num_epochs, epochs, targets) diff --git a/test/generic/utils.py b/test/generic/utils.py new file mode 100644 index 0000000000..0d04aa3edd --- /dev/null +++ b/test/generic/utils.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +from functools import wraps + +import torch + + +class Arguments(object): + """Object that looks like input arguments. Used to spoof argparse namespace.""" + + def __init__(self, **args): + self.args = args + self.__dict__.update(args) + + def __iter__(self): + return iter(self.args) + + def __eq__(self, other): + if isinstance(other, Arguments): + return self.args == other.args + else: + return NotImplemented + + def _asdict(self): + return vars(self) + + +def skip_if_no_gpu(func): + """Decorator that can be used to skip GPU tests on non-GPU machines.""" + func.skip_if_no_gpu = True + + @wraps(func) + def wrapper(*args, **kwargs): + if not torch.cuda.is_available(): + return + if torch.cuda.device_count() <= 0: + return + + return func(*args, **kwargs) + + return wrapper + + +def repeat_test(original_function=None, *, num_times=3): + """Decorator that can be used to repeat test multiple times.""" + + def repeat_test_decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + for _ in range(num_times): + func(*args, **kwargs) + + return wrapper + + # this handles default arguments to decorator: + if original_function: + return repeat_test_decorator(original_function) + return repeat_test_decorator + + +def make_torch_deterministic(seed=0): + """Makes Torch code run deterministically.""" + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + torch.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + os.environ["MKL_NUM_THREADS"] = "1" + os.environ["OMP_NUM_THREADS"] = "1" + + +def compare_batches(test_fixture, batch1, batch2): + """Compare two batches. Does not do recursive comparison""" + test_fixture.assertEqual(type(batch1), type(batch2)) + if isinstance(batch1, (tuple, list)): + test_fixture.assertEqual(len(batch1), len(batch2)) + for n in range(len(batch1)): + value1 = batch1[n] + value2 = batch2[n] + test_fixture.assertEqual(type(value1), type(value2)) + if torch.is_tensor(value1): + test_fixture.assertTrue(torch.allclose(value1, value2)) + else: + test_fixture.assertEqual(value1, value2) + + elif isinstance(batch1, dict): + test_fixture.assertEqual(batch1.keys(), batch2.keys()) + for key, value1 in batch1.items(): + value2 = batch2[key] + test_fixture.assertEqual(type(value1), type(value2)) + if torch.is_tensor(value1): + test_fixture.assertTrue(torch.allclose(value1, value2)) + else: + test_fixture.assertEqual(value1, value2) + + +def compare_datasets(test_fixture, dataset1, dataset2): + test_fixture.assertEqual(len(dataset1), len(dataset2)) + for idx in range(len(dataset1)): + compare_batches(test_fixture, dataset1[idx], dataset2[idx]) + + +def compare_batchlist_and_dataset_with_skips( + test_fixture, batch_list, dataset, skip_indices=None +): + """ + Compares a list of batches and the dataset. If some samples were + skipped in the iterator (i.e. if we simulated an error on that + sample), that should be indicated in the skip_indices list + """ + if skip_indices is None: + skip_indices = [] + if isinstance(skip_indices, int): + skip_indices = [skip_indices] + + skips = 0 + for idx, batch in enumerate(batch_list): + while (idx + skips) in skip_indices: + skips += 1 + dataset_batch = dataset[idx + skips] + compare_batches(test_fixture, batch, dataset_batch) + + +class MockErrorDataset: + """ + Dataset used for testing. Wraps a real dataset with a + batchsize_per_replica, but allows us to delete samples on return + to simulate errors (similar to what happens with Everstore) + """ + + def __init__(self, dataset): + self.rebatch_map = {} + self.dataset = dataset + self.batchsize_per_replica = dataset.batchsize_per_replica + + def __getitem__(self, idx): + batch = self.dataset[idx] + # If rebatch map contains index, resize the batch + if idx in self.rebatch_map: + num_samples = self.rebatch_map[idx] + if num_samples < batch["input"].size()[0]: + batch["input"] = batch["input"][:num_samples] + batch["target"] = batch["target"][:num_samples] + + return batch + + def __len__(self): + return len(self.dataset) + + +def recursive_unpack(batch): + """ + Takes a batch of samples, e.g. + + batch = {'input': tensor([256, 3, 224, 224]), 'target': tensor([256])} + + and unpacks them into a list of single samples, e.g. + + [{'input': tensor([1, 3, 224, 224]), 'target': tensor([1])} ... ] + """ + new_list = [] + if isinstance(batch, dict): + unpacked_dict = {} + batchsize_per_replica = -1 + for key, val in batch.items(): + unpacked_dict[key] = recursive_unpack(val) + batchsize_per_replica = ( + len(unpacked_dict[key]) + if not torch.is_tensor(unpacked_dict[key]) + else 1 + ) + + for idx in range(batchsize_per_replica): + sample = {} + for key, val in unpacked_dict.items(): + sample[key] = val[idx] + + new_list.append(sample) + return new_list + + elif isinstance(batch, (list, tuple)): + unpacked_list = [] + if isinstance(batch, tuple): + batch = list(batch) + + for val in batch: + unpacked_list.append(recursive_unpack(val)) + batchsize_per_replica = ( + len(unpacked_list[0]) if not torch.is_tensor(unpacked_list[0]) else 1 + ) + + for idx in range(batchsize_per_replica): + sample = [] + for val in unpacked_list: + sample.append(val[idx]) + + if isinstance(batch, tuple): + sample = tuple(sample) + new_list.append(sample) + return new_list + + elif torch.is_tensor(batch): + for i in range(batch.size()[0]): + new_list.append(batch[i]) + return new_list + + raise TypeError("Unexpected type %s passed to unpack" % type(batch)) + + +def compare_model_state(test_fixture, state, state2, check_heads=True): + for k in state["model"]["trunk"].keys(): + if not torch.allclose(state["model"]["trunk"][k], state2["model"]["trunk"][k]): + print(k, state["model"]["trunk"][k], state2["model"]["trunk"][k]) + test_fixture.assertTrue( + torch.allclose(state["model"]["trunk"][k], state2["model"]["trunk"][k]) + ) + if check_heads: + for block, head_states in state["model"]["heads"].items(): + for head_id, states in head_states.items(): + for k in states.keys(): + test_fixture.assertTrue( + torch.allclose( + state["model"]["heads"][block][head_id][k], + state2["model"]["heads"][block][head_id][k], + ) + ) + + +def compare_samples(test_fixture, sample1, sample2): + test_fixture.assertEqual(sample1.keys(), sample2.keys()) + test_fixture.assertTrue(torch.is_tensor(sample1["input"])) + test_fixture.assertTrue(torch.is_tensor(sample2["input"])) + test_fixture.assertTrue(torch.is_tensor(sample1["target"])) + test_fixture.assertTrue(torch.is_tensor(sample2["target"])) + + test_fixture.assertTrue(torch.allclose(sample1["input"], sample2["input"])) + test_fixture.assertTrue(torch.allclose(sample1["target"], sample2["target"])) + + +def compare_states(test_fixture, state_1, state_2, check_heads=True): + """ + Tests the classy state dicts for equality, but skips the member objects + which implement their own {get, set}_classy_state functions. + """ + # check base_model + compare_model_state( + test_fixture, state_1["base_model"], state_2["base_model"], check_heads + ) + # check losses + test_fixture.assertEqual(len(state_1["losses"]), len(state_2["losses"])) + for loss_1, loss_2 in zip(state_1["losses"], state_2["losses"]): + test_fixture.assertAlmostEqual(loss_1, loss_2) + + for key in ["base_model", "meters", "optimizer", "losses"]: + # we trust that these have been tested using their unit tests or + # by the code above + test_fixture.assertIn(key, state_1) + test_fixture.assertIn(key, state_2) + del state_1[key] + del state_2[key] + test_fixture.assertDictEqual(state_1, state_2) diff --git a/test/generic_util_json_blob_test.json b/test/generic_util_json_blob_test.json new file mode 100644 index 0000000000..24d1af7276 --- /dev/null +++ b/test/generic_util_json_blob_test.json @@ -0,0 +1,34 @@ +{ + "name": "test_task", + "num_epochs": 12, + "loss": { + "name": "test_loss" + }, + "dataset": { + "name": "test_data", + "batchsize_per_replica": 8, + "use_pairs": false, + "num_samples": null, + "use_shuffle": { + "train": true, + "test": false + } + }, + "meters": [ + { + "name": "test_meter", + "test_param": 0.1 + } + ], + "model": { + "name": "test_model", + "architecture": [1, 2, 3, 4] + }, + "optimizer": { + "name": "test_optimizer", + "test_param": { + "name": "test_scheduler", + "values": [0.1, 0.01, 0.001, 0.0001] + } + } +} diff --git a/test/generic_util_test.py b/test/generic_util_test.py new file mode 100644 index 0000000000..f4f9120637 --- /dev/null +++ b/test/generic_util_test.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import argparse +import typing +import unittest +import unittest.mock as mock +from pathlib import Path +from test.generic.config_utils import get_fast_test_task_config, get_test_model_configs +from test.generic.utils import compare_model_state, compare_states + +import classy_vision.generic.util as util +import torch +from classy_vision.generic.util import update_classy_model, update_classy_state +from classy_vision.models import build_model +from classy_vision.tasks import build_task +from classy_vision.trainer import LocalTrainer + + +ROOT = Path(__file__).parent + + +@mock.patch("torch.tensor") +def get_mock_tensor(mock_class): + def get_cuda_tensor(): + t = torch.tensor([1, 2, 3]) + t.is_cuda = True + return t + + mock_class.return_value.cuda.return_value = get_cuda_tensor() + mock_class.is_cuda = False + return torch.tensor([1, 2, 3]) + + +class TestUtilMethods(unittest.TestCase): + def _get_base_pred_probs(self): + return torch.tensor( + [ + [0.92, 0.08], # 1 + [0.91, 0.09], # 0 + [0.89, 0.11], # 0 + [0.79, 0.21], # 0 + [0.78, 0.22], # 0 + [0.69, 0.31], # 1 + [0.68, 0.32], # 0 + [0.59, 0.41], # 1 + [0.58, 0.42], # 0 + [0.49, 0.51], # 1 + [0.48, 0.52], # 1 + [0.39, 0.61], # 0 + [0.38, 0.62], # 0 + [0.29, 0.71], # 1 + [0.28, 0.72], # 1 + [0.19, 0.81], # 0 + [0.18, 0.82], # 1 + [0.09, 0.91], # 1 + [0.08, 0.92], # 1 + [0.07, 0.93], # 0 + [0.06, 0.94], # 1 + [0.03, 0.97], # 0 + ] + ) + + def _get_base_targets(self): + return torch.tensor( + [1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0] + ).int() + + def _get_base_class_hist(self): + return torch.stack( + [ + torch.Tensor( + [1, 1, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 1, 0, 2, 0, 1, 1, 0] + ), + torch.Tensor( + [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 2, 0, 0, 0, 2, 0, 1, 0, 3, 0] + ), + ], + dim=1, + ).long() + + def _get_base_total_hist(self): + return torch.stack( + [ + torch.Tensor( + [1, 4, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1, 2, 0] + ), + torch.Tensor( + [0, 2, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 4, 1] + ), + ], + dim=1, + ).long() + + def test_create_class_histograms_success(self): + pred_probs = self._get_base_pred_probs() + targets = self._get_base_targets() + + class_hist, total_hist = util.create_class_histograms(pred_probs, targets, 20) + torch.testing.assert_allclose(class_hist, self._get_base_class_hist()) + torch.testing.assert_allclose(total_hist, self._get_base_total_hist()) + + def test_create_class_histograms_fail(self): + pred_probs = self._get_base_pred_probs() + targets = self._get_base_targets() + + # Torch tensors only + with self.assertRaises(AssertionError): + class_hist, total_hist = util.create_class_histograms( + pred_probs.numpy(), targets, 20 + ) + + # Torch tensors only + with self.assertRaises(AssertionError): + class_hist, total_hist = util.create_class_histograms( + pred_probs, targets.numpy(), 20 + ) + + # Prediction and target are same size + with self.assertRaises(AssertionError): + class_hist, total_hist = util.create_class_histograms( + pred_probs[0:5, :], targets, 20 + ) + + # Prediction is between 0 and 1 + with self.assertRaises(AssertionError): + pred_probs[0, :] = torch.tensor([-0.1, 1.1]) + class_hist, total_hist = util.create_class_histograms( + pred_probs, targets, 20 + ) + + def test_compute_pr_curves(self): + class_hist = self._get_base_class_hist() + total_hist = self._get_base_total_hist() + + pr_curves = util.compute_pr_curves(class_hist, total_hist) + # For curves without duplicates removed / precisions cleaned + # up, see: P60302268 + exp_pos_prec = torch.tensor( + [ + 3.0 / 5.0, + 4.0 / 7.0, + 6.0 / 9.0, + 6.0 / 11.0, + 8.0 / 13.0, + 9.0 / 15.0, + 10.0 / 17.0, + 10.0 / 19.0, + 10.0 / 20.0, + 11.0 / 22.0, + ], + dtype=torch.double, + ) + exp_pos_recall = torch.tensor( + [ + 3.0 / 11.0, + 4.0 / 11.0, + 6.0 / 11.0, + 6.0 / 11.0, + 8.0 / 11.0, + 9.0 / 11.0, + 10.0 / 11.0, + 10.0 / 11.0, + 10.0 / 11.0, + 11.0 / 11.0, + ], + dtype=torch.double, + ) + + exp_neg_prec = torch.tensor( + [ + 1.0 / 2.0, + 2.0 / 3.0, + 4.0 / 5.0, + 5.0 / 7.0, + 6.0 / 9.0, + 6.0 / 11.0, + 8.0 / 13.0, + 8.0 / 15.0, + 9.0 / 17.0, + 10.0 / 21.0, + 11.0 / 22.0, + ], + dtype=torch.double, + ) + exp_neg_recall = torch.tensor( + [ + 1.0 / 11.0, + 2.0 / 11.0, + 4.0 / 11.0, + 5.0 / 11.0, + 6.0 / 11.0, + 6.0 / 11.0, + 8.0 / 11.0, + 8.0 / 11.0, + 9.0 / 11.0, + 10.0 / 11.0, + 11.0 / 11.0, + ], + dtype=torch.double, + ) + + torch.testing.assert_allclose(pr_curves["prec"][1], exp_pos_prec) + torch.testing.assert_allclose(pr_curves["prec"][0], exp_neg_prec) + + torch.testing.assert_allclose(pr_curves["recall"][1], exp_pos_recall) + torch.testing.assert_allclose(pr_curves["recall"][0], exp_neg_recall) + + torch.testing.assert_allclose( + pr_curves["ap"][1], torch.tensor(0.589678058127256).double() + ) + torch.testing.assert_allclose( + pr_curves["ap"][0], torch.tensor(0.6073388287292031).double() + ) + + def test_compute_pr_curves_fail(self): + class_hist = self._get_base_class_hist() + total_hist = self._get_base_total_hist() + + # invalid histograms + with self.assertRaises(AssertionError): + class_hist += torch.ones(class_hist.size(), dtype=torch.int64) * 100 + util.compute_pr_curves(class_hist, total_hist) + + # Doesn't accept numpy + with self.assertRaises(AssertionError): + util.compute_pr_curves(class_hist.numpy(), total_hist) + + with self.assertRaises(AssertionError): + util.compute_pr_curves(class_hist, total_hist.numpy()) + + # Longs only + with self.assertRaises(AssertionError): + util.compute_pr_curves(class_hist.float(), total_hist.float()) + + # Bad tensor size + with self.assertRaises(AssertionError): + util.compute_pr_curves(class_hist.view(40, 1), total_hist) + + with self.assertRaises(AssertionError): + util.compute_pr_curves(class_hist, total_hist.view(40, 1)) + + def test_recursive_copy_to_gpu(self): + tensor_a = get_mock_tensor() + tensor_b = get_mock_tensor() + + valid_gpu_copy_value = tensor_a + gpu_value = util.recursive_copy_to_gpu(valid_gpu_copy_value) + self.assertTrue(gpu_value.is_cuda) + + valid_recursive_copy_value = [[tensor_a]] + gpu_value = util.recursive_copy_to_gpu(valid_recursive_copy_value) + self.assertTrue(gpu_value[0][0].is_cuda) + + valid_gpu_copy_collections = [ + (tensor_a, tensor_b), + [tensor_a, tensor_b], + {"tensor_a": tensor_a, "tensor_b": tensor_b}, + ] + for value in valid_gpu_copy_collections: + gpu_value = util.recursive_copy_to_gpu(value) + if isinstance(value, dict): + self.assertTrue(gpu_value["tensor_a"].is_cuda) + self.assertTrue(gpu_value["tensor_b"].is_cuda) + else: + self.assertEqual(len(gpu_value), 2) + self.assertTrue(gpu_value[0].is_cuda) + self.assertTrue(gpu_value[1].is_cuda) + + invalid_gpu_copy_values = [1234, True, 1.0] + for value in invalid_gpu_copy_values: + with self.assertRaises(AttributeError): + gpu_value = util.recursive_copy_to_gpu(value) + + invalid_gpu_copy_depth = [ + ((((tensor_a, tensor_b), tensor_b), tensor_b), tensor_b), + {"tensor_map_a": {"tensor_map_b": {"tensor_map_c": {"tensor": tensor_a}}}}, + [[[[tensor_a, tensor_b], tensor_b], tensor_b], tensor_b], + "abcd", # Strings are sequences, includeing single char strings + ] + for value in invalid_gpu_copy_depth: + with self.assertRaises(ValueError): + gpu_value = util.recursive_copy_to_gpu(value, max_depth=3) + + _json_config_file = ROOT / "generic_util_json_blob_test.json" + + def _get_config(self): + return { + "name": "test_task", + "num_epochs": 12, + "loss": {"name": "test_loss"}, + "dataset": { + "name": "test_data", + "batchsize_per_replica": 8, + "use_pairs": False, + "num_samples": None, + "use_shuffle": {"train": True, "test": False}, + }, + "meters": [{"name": "test_meter", "test_param": 0.1}], + "model": {"name": "test_model", "architecture": [1, 2, 3, 4]}, + "optimizer": { + "name": "test_optimizer", + "test_param": { + "name": "test_scheduler", + "values": [0.1, 0.01, 0.001, 0.0001], + }, + }, + } + + def test_load_config(self): + expected_config = self._get_config() + config = util.load_json(self._json_config_file) + + self.assertEqual(config, expected_config) + + def test_torch_seed(self): + # test that using util.torch_seed doesn't impact the generation of + # random numbers outside its context and that random numbers generated + # within its context are the same as setting a manual seed + torch.manual_seed(0) + torch.randn(10) + random_tensor_1 = torch.randn(10) + torch.manual_seed(0) + torch.randn(10) + with util.torch_seed(1): + random_tensor_2 = torch.randn(10) + self.assertTrue(torch.equal(torch.randn(10), random_tensor_1)) + torch.manual_seed(1) + self.assertTrue(torch.equal(torch.randn(10), random_tensor_2)) + + def test_get_model_dummy_input(self): + for config in get_test_model_configs(): + model = build_model(config) # pass in a dummy model for the cuda check + batchsize = 8 + # input_key is list + input_key = ["audio", "video"] + input_shape = [[3, 40, 100], [4, 16, 223, 223]] # dummy input shapes + result = util.get_model_dummy_input( + model, input_shape, input_key, batchsize + ) + self.assertEqual(result.keys(), {"audio", "video"}) + for i in range(len(input_key)): + self.assertEqual( + result[input_key[i]].size(), tuple([batchsize] + input_shape[i]) + ) + # input_key is string + input_key = "video" + input_shape = [4, 16, 223, 223] + result = util.get_model_dummy_input( + model, input_shape, input_key, batchsize + ) + self.assertEqual(result.keys(), {"video"}) + self.assertEqual(result[input_key].size(), tuple([batchsize] + input_shape)) + # input_key is None + input_key = None + input_shape = [4, 16, 223, 223] + result = util.get_model_dummy_input( + model, input_shape, input_key, batchsize + ) + self.assertEqual(result.size(), tuple([batchsize] + input_shape)) + + +class TestUpdateStateFunctions(unittest.TestCase): + def _compare_states(self, state_1, state_2, check_heads=True): + compare_states(self, state_1, state_2) + + def _compare_model_state(self, state_1, state_2, check_heads=True): + return compare_model_state(self, state_1, state_2, check_heads=check_heads) + + def test_update_classy_state(self): + """ + Tests that the update_classy_state successfully updates from a + checkpoint + """ + config = get_fast_test_task_config() + task = build_task(config) + task_2 = build_task(config) + task_2.prepare() + trainer = LocalTrainer(use_gpu=False) + trainer.train(task) + update_classy_state(task_2, task.get_classy_state(deep_copy=True)) + self._compare_states(task.get_classy_state(), task_2.get_classy_state()) + + def test_update_classy_model(self): + """ + Tests that the update_classy_model successfully updates from a + checkpoint + """ + config = get_fast_test_task_config() + task = build_task(config) + use_gpu = torch.cuda.is_available() + trainer = LocalTrainer(use_gpu=use_gpu) + trainer.train(task) + for reset_heads in [False, True]: + task_2 = build_task(config) + # prepare task_2 for the right device + task_2.prepare(use_gpu=use_gpu) + update_classy_model( + task_2.model, task.model.get_classy_state(deep_copy=True), reset_heads + ) + self._compare_model_state( + task.model.get_classy_state(), + task_2.model.get_classy_state(), + check_heads=not reset_heads, + ) + if reset_heads: + # the model head states should be different + with self.assertRaises(Exception): + self._compare_model_state( + task.model.get_classy_state(), + task_2.model.get_classy_state(), + check_heads=True, + ) diff --git a/test/hooks_checkpoint_hook_test.py b/test/hooks_checkpoint_hook_test.py new file mode 100644 index 0000000000..147588cc8d --- /dev/null +++ b/test/hooks_checkpoint_hook_test.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +import shutil +import tempfile +import unittest +from test.generic.config_utils import get_test_task_config + +from classy_vision.generic.util import load_checkpoint +from classy_vision.hooks import CheckpointHook +from classy_vision.tasks import build_task + + +class TestCheckpointHook(unittest.TestCase): + def setUp(self) -> None: + self.base_dir = tempfile.mkdtemp() + + def tearDown(self) -> None: + shutil.rmtree(self.base_dir) + + def test_state_checkpointing(self) -> None: + """ + Test that the state gets checkpointed without any errors, but only on the + right phase_type and only if the checkpoint directory exists. + """ + config = get_test_task_config() + task = build_task(config) + task.prepare() + + local_variables = {} + checkpoint_folder = self.base_dir + "/checkpoint_end_test/" + device = "cpu" + input_args = {"foo": "bar"} + + # create a checkpoint hook + checkpoint_hook = CheckpointHook( + checkpoint_folder, input_args, phase_types=["train"] + ) + + # checkpoint directory doesn't exist + # call the on start function + with self.assertRaises(FileNotFoundError): + checkpoint_hook.on_start(task, local_variables) + # call the on end phase function + with self.assertRaises(AssertionError): + checkpoint_hook.on_phase_end(task, local_variables) + # try loading a non-existent checkpoint + checkpoint = load_checkpoint(checkpoint_folder, device) + self.assertIsNone(checkpoint) + + # create checkpoint dir, verify on_start hook runs + os.mkdir(checkpoint_folder) + checkpoint_hook.on_start(task, local_variables) + + # Phase_type is test, expect no checkpoint + task.train = False + # call the on end phase function + checkpoint_hook.on_phase_end(task, local_variables) + checkpoint = load_checkpoint(checkpoint_folder, device) + self.assertIsNone(checkpoint) + + task.train = True + # call the on end phase function + checkpoint_hook.on_phase_end(task, local_variables) + # model should be checkpointed. load and compare + checkpoint = load_checkpoint(checkpoint_folder, device) + self.assertIsNotNone(checkpoint) + for key in ["input_args", "classy_state_dict"]: + self.assertIn(key, checkpoint) + # not testing for equality of classy_state_dict, that is tested in + # a separate test + self.assertDictEqual(checkpoint["input_args"], input_args) + + def test_checkpoint_period(self) -> None: + """ + Test that the checkpoint_period works as expected. + """ + config = get_test_task_config() + task = build_task(config) + task.prepare() + + local_variables = {} + checkpoint_folder = self.base_dir + "/checkpoint_end_test/" + device = "cpu" + checkpoint_period = 10 + + for phase_types in [["train"], ["train", "test"]]: + # create a checkpoint hook + checkpoint_hook = CheckpointHook( + checkpoint_folder, + {}, + phase_types=phase_types, + checkpoint_period=checkpoint_period, + ) + + # create checkpoint dir + os.mkdir(checkpoint_folder) + + # call the on start function + checkpoint_hook.on_start(task, local_variables) + + # shouldn't create any checkpoints until there are checkpoint_period + # phases which are in phase_types + count = 0 + valid_phase_count = 0 + while valid_phase_count < checkpoint_period - 1: + task.train = count % 2 == 0 + # call the on end phase function + checkpoint_hook.on_phase_end(task, local_variables) + checkpoint = load_checkpoint(checkpoint_folder, device) + self.assertIsNone(checkpoint) + valid_phase_count += 1 if task.phase_type in phase_types else 0 + count += 1 + + # create a phase which is in phase_types + task.train = True + # call the on end phase function + checkpoint_hook.on_phase_end(task, local_variables) + # model should be checkpointed. load and compare + checkpoint = load_checkpoint(checkpoint_folder, device) + self.assertIsNotNone(checkpoint) + # delete the checkpoint dir + shutil.rmtree(checkpoint_folder) diff --git a/test/hooks_classy_hook_test.py b/test/hooks_classy_hook_test.py new file mode 100644 index 0000000000..d7a22e37de --- /dev/null +++ b/test/hooks_classy_hook_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +from classy_vision.hooks import ClassyHook + + +class TestHook(ClassyHook): + on_rendezvous = ClassyHook._noop + on_start = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_update = ClassyHook._noop + on_phase_end = ClassyHook._noop + on_end = ClassyHook._noop + + def __init__(self, a, b): + super().__init__() + self.state.a = a + self.state.b = b + + +class TestClassyHook(unittest.TestCase): + def test_state_dict(self): + a = 0 + b = {1: 2, 3: [4]} + test_hook = TestHook(a, b) + state_dict = test_hook.get_classy_state() + # create a new test_hook and set its state to the old hook's. + test_hook = TestHook("", 0) + test_hook.set_classy_state(state_dict) + self.assertEqual(test_hook.state.a, a) + self.assertEqual(test_hook.state.b, b) diff --git a/test/hooks_exponential_moving_average_model_hook_test.py b/test/hooks_exponential_moving_average_model_hook_test.py new file mode 100644 index 0000000000..6248bfba39 --- /dev/null +++ b/test/hooks_exponential_moving_average_model_hook_test.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import math +import unittest +import unittest.mock as mock + +import torch +import torch.nn as nn +from classy_vision.hooks import ExponentialMovingAverageModelHook +from classy_vision.models import ClassyModel + + +class TestModel(ClassyModel): + def __init__(self): + super().__init__() + self.fc = nn.Linear(10, 10) + self.bn = nn.BatchNorm1d(10) + + def init_fc_weight(self): + nn.init.zeros_(self.fc.weight) + + def update_fc_weight(self): + nn.init.ones_(self.fc.weight) + + def forward(self, x): + return self.bn(self.fc(x)) + + +class TestExponentialMovingAverageModelHook(unittest.TestCase): + def _map_device_string(self, device): + return "cuda" if device == "gpu" else "cpu" + + def _test_exponential_moving_average_hook(self, model_device, hook_device): + task = mock.MagicMock() + model = TestModel().to(device=self._map_device_string(model_device)) + local_variables = {} + task.base_model = model + task.train = True + decay = 0.5 + num_updates = 10 + model.init_fc_weight() + exponential_moving_average_hook = ExponentialMovingAverageModelHook( + decay=decay, device=hook_device + ) + + exponential_moving_average_hook.on_start(task, local_variables) + exponential_moving_average_hook.on_phase_start(task, local_variables) + # set the weights to all ones and simulate 10 updates + task.base_model.update_fc_weight() + fc_weight = model.fc.weight.clone() + for _ in range(num_updates): + exponential_moving_average_hook.on_update(task, local_variables) + exponential_moving_average_hook.on_phase_end(task, local_variables) + # the model weights shouldn't have changed + self.assertTrue(torch.allclose(model.fc.weight, fc_weight)) + + # simulate a test phase now + task.train = False + exponential_moving_average_hook.on_phase_start(task, local_variables) + exponential_moving_average_hook.on_phase_end(task, local_variables) + + # the model weights should be updated to the ema weights + self.assertTrue( + torch.allclose( + model.fc.weight, fc_weight * (1 - math.pow(1 - decay, num_updates)) + ) + ) + + # simulate a train phase again + task.train = True + exponential_moving_average_hook.on_phase_start(task, local_variables) + + # the model weights should be back to the old value + self.assertTrue(torch.allclose(model.fc.weight, fc_weight)) + + def test_get_model_state_iterator(self): + device = "gpu" if torch.cuda.is_available() else "cpu" + model = TestModel().to(device=self._map_device_string(device)) + decay = 0.5 + # test that we pick up the right parameters in the iterator + for consider_bn_buffers in [True, False]: + exponential_moving_average_hook = ExponentialMovingAverageModelHook( + decay=decay, consider_bn_buffers=consider_bn_buffers, device=device + ) + iterable = exponential_moving_average_hook.get_model_state_iterator(model) + fc_found = False + bn_found = False + bn_buffer_found = False + for _, param in iterable: + if any(param is item for item in model.fc.parameters()): + fc_found = True + if any(param is item for item in model.bn.parameters()): + bn_found = True + if any(param is item for item in model.bn.buffers()): + bn_buffer_found = True + self.assertTrue(fc_found) + self.assertTrue(bn_found) + self.assertEqual(bn_buffer_found, consider_bn_buffers) + + def test_exponential_moving_average_hook(self): + device = "gpu" if torch.cuda.is_available() else "cpu" + self._test_exponential_moving_average_hook(device, device) + + @unittest.skipUnless(torch.cuda.is_available(), "This test needs a gpu to run") + def test_mixed_devices(self): + """Tests that the hook works when the model and hook's device are different""" + self._test_exponential_moving_average_hook("cpu", "gpu") + self._test_exponential_moving_average_hook("gpu", "cpu") diff --git a/test/hooks_loss_lr_meter_logging_hook_test.py b/test/hooks_loss_lr_meter_logging_hook_test.py new file mode 100644 index 0000000000..79fabc98f8 --- /dev/null +++ b/test/hooks_loss_lr_meter_logging_hook_test.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +import unittest.mock as mock +from itertools import product +from test.generic.config_utils import get_test_mlp_task_config, get_test_task_config + +from classy_vision.hooks import LossLrMeterLoggingHook +from classy_vision.optim.param_scheduler import UpdateInterval +from classy_vision.tasks import ClassyTask, build_task +from classy_vision.trainer import LocalTrainer + + +class TestLossLrMeterLoggingHook(unittest.TestCase): + @mock.patch("classy_vision.hooks.loss_lr_meter_logging_hook.get_rank") + def test_logging(self, mock_get_rank: mock.MagicMock) -> None: + """ + Test that the logging happens as expected and the loss and lr values are + correct. + """ + rank = 5 + mock_get_rank.return_value = rank + + # set up the task and state + config = get_test_task_config() + config["dataset"]["train"]["batchsize_per_replica"] = 2 + config["dataset"]["test"]["batchsize_per_replica"] = 5 + task = build_task(config) + task.prepare() + + losses = [1.2, 2.3, 3.4, 4.5] + + local_variables = {} + task.phase_idx = 0 + + loss_vals = {"train": 1.425, "test": 0.57} + + for log_freq, phase_type in product([5, None], loss_vals): + task.train = phase_type == "train" + + # create a loss lr meter hook + loss_lr_meter_hook = LossLrMeterLoggingHook(log_freq=log_freq) + + # check that _log_loss_meters() is called after on_loss_and_meter() every + # log_freq batches and after on_phase_end() + # and _log_lr() is called after on_update() every log_freq batches + # and after on_phase_end() + with mock.patch.object(loss_lr_meter_hook, "_log_loss_meters") as mock_fn: + with mock.patch.object(loss_lr_meter_hook, "_log_lr") as mock_lr_fn: + num_batches = 20 + + for i in range(num_batches): + task.losses = list(range(i)) + loss_lr_meter_hook.on_loss_and_meter(task, local_variables) + loss_lr_meter_hook.on_update(task, local_variables) + if log_freq is not None and i and i % log_freq == 0: + mock_fn.assert_called_with(task, local_variables) + mock_fn.reset_mock() + mock_lr_fn.assert_called_with(task, local_variables) + mock_lr_fn.reset_mock() + continue + mock_fn.assert_not_called() + mock_lr_fn.assert_not_called() + + loss_lr_meter_hook.on_phase_end(task, local_variables) + mock_fn.assert_called_with(task, local_variables) + if task.train: + mock_lr_fn.assert_called_with(task, local_variables) + + # test _log_loss_lr_meters() + task.losses = losses + + with self.assertLogs(): + loss_lr_meter_hook._log_loss_meters(task, local_variables) + loss_lr_meter_hook._log_lr(task, local_variables) + + task.phase_idx += 1 + + def test_logged_lr(self): + # Mock LR scheduler + def scheduler_mock(where): + return where + + mock_lr_scheduler = mock.Mock(side_effect=scheduler_mock) + mock_lr_scheduler.update_interval = UpdateInterval.STEP + config = get_test_mlp_task_config() + config["num_epochs"] = 3 + config["dataset"]["train"]["batchsize_per_replica"] = 5 + config["dataset"]["test"]["batchsize_per_replica"] = 5 + task = build_task(config) + task.optimizer.lr_scheduler = mock_lr_scheduler + trainer = LocalTrainer() + + # 2 LR updates per epoch + # At end of each epoch for train, LR is logged an additional time + lr_order = [0.0, 1 / 6, 1 / 6, 2 / 6, 3 / 6, 3 / 6, 4 / 6, 5 / 6, 5 / 6] + lr_list = [] + + def mock_log_lr(task: ClassyTask, local_variables) -> None: + lr_list.append(task.optimizer.lr) + + with mock.patch.object( + LossLrMeterLoggingHook, "_log_lr", side_effect=mock_log_lr + ): + hook = LossLrMeterLoggingHook(1) + task.set_hooks([hook]) + trainer.train(task) + self.assertEqual(lr_list, lr_order) diff --git a/test/hooks_profiler_hook_test.py b/test/hooks_profiler_hook_test.py new file mode 100644 index 0000000000..b8168ebede --- /dev/null +++ b/test/hooks_profiler_hook_test.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +import unittest.mock as mock +from test.generic.config_utils import get_test_classy_task, get_test_classy_video_task + +from classy_vision.hooks import ProfilerHook + + +class TestProfilerHook(unittest.TestCase): + @mock.patch("torch.autograd.profiler.profile", auto_spec=True) + @mock.patch("classy_vision.hooks.profiler_hook.summarize_profiler_info") + def test_profiler( + self, + mock_summarize_profiler_info: mock.MagicMock, + mock_profile_cls: mock.MagicMock, + ) -> None: + """ + Tests that a profile instance is returned by the profiler + and that the profiler actually ran. + """ + mock_summarize_profiler_info.return_value = "" + + mock_profile = mock.MagicMock() + mock_profile_returned = mock.MagicMock() + mock_profile.__enter__.return_value = mock_profile_returned + mock_profile_cls.return_value = mock_profile + + for task in [get_test_classy_task(), get_test_classy_video_task()]: + task.prepare() + local_variables = {} + + # create a model tensorboard hook + profiler_hook = ProfilerHook() + + with self.assertLogs(): + profiler_hook.on_start(task, local_variables) + + # a new profile should be created with use_cuda=True + mock_profile_cls.assert_called_once_with(use_cuda=True) + mock_profile_cls.reset_mock() + + # summarize_profiler_info should have been called once with the profile + mock_summarize_profiler_info.assert_called_once() + profile = mock_summarize_profiler_info.call_args[0][0] + mock_summarize_profiler_info.reset_mock() + self.assertEqual(profile, mock_profile_returned) diff --git a/test/hooks_time_metrics_hook_test.py b/test/hooks_time_metrics_hook_test.py new file mode 100644 index 0000000000..9b3ac0706d --- /dev/null +++ b/test/hooks_time_metrics_hook_test.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import re +import unittest +import unittest.mock as mock +from itertools import product +from test.generic.config_utils import get_test_classy_task + +from classy_vision.generic.perf_stats import PerfStats +from classy_vision.hooks import TimeMetricsHook + + +class TestTimeMetricsHook(unittest.TestCase): + @mock.patch("time.time") + @mock.patch("classy_vision.hooks.time_metrics_hook.PerfStats.report_str") + @mock.patch("classy_vision.hooks.time_metrics_hook.get_rank") + def test_time_metrics( + self, + mock_get_rank: mock.MagicMock, + mock_report_str: mock.MagicMock, + mock_time: mock.MagicMock, + ) -> None: + """ + Tests that the progress bar is created, updated and destroyed correctly. + """ + rank = 5 + mock_get_rank.return_value = rank + + mock_report_str.return_value = "" + local_variables = {} + + for log_freq, train in product([5, None], [True, False]): + # create a time metrics hook + time_metrics_hook = TimeMetricsHook(log_freq=log_freq) + + phase_type = "train" if train else "test" + + task = get_test_classy_task() + task.prepare() + task.train = train + + # on_phase_start() should set the start time and perf_stats + start_time = 1.2 + mock_time.return_value = start_time + time_metrics_hook.on_phase_start(task, local_variables) + self.assertEqual(time_metrics_hook.start_time, start_time) + self.assertTrue(isinstance(local_variables.get("perf_stats"), PerfStats)) + + # test that the code doesn't raise an exception if losses is empty + try: + time_metrics_hook.on_phase_end(task, local_variables) + except Exception as e: + self.fail("Received Exception when losses is []: {}".format(e)) + + # check that _log_performance_metrics() is called after on_loss_and_meter() + # every log_freq batches and after on_phase_end() + with mock.patch.object( + time_metrics_hook, "_log_performance_metrics" + ) as mock_fn: + num_batches = 20 + + for i in range(num_batches): + task.losses = list(range(i)) + time_metrics_hook.on_loss_and_meter(task, local_variables) + if log_freq is not None and i and i % log_freq == 0: + mock_fn.assert_called_with(task, local_variables) + mock_fn.reset_mock() + continue + mock_fn.assert_not_called() + + time_metrics_hook.on_phase_end(task, local_variables) + mock_fn.assert_called_with(task, local_variables) + + task.losses = [0.23, 0.45, 0.34, 0.67] + + end_time = 10.4 + avg_batch_time_ms = 2.3 * 1000 + mock_time.return_value = end_time + + # test _log_performance_metrics() + with self.assertLogs() as log_watcher: + time_metrics_hook._log_performance_metrics(task, local_variables) + + # there should 2 be info logs for train and 1 for test + self.assertEqual(len(log_watcher.output), 2 if train else 1) + self.assertTrue( + all( + log_record.levelno == logging.INFO + for log_record in log_watcher.records + ) + ) + match = re.search( + ( + r"Average {} batch time \(ms\) for {} batches: " + r"(?P[-+]?\d*\.\d+|\d+)" + ).format(phase_type, len(task.losses)), + log_watcher.output[0], + ) + self.assertIsNotNone(match) + self.assertAlmostEqual( + avg_batch_time_ms, float(match.group("avg_batch_time")), places=4 + ) + if train: + self.assertIn( + f"Train step time breakdown (rank {rank})", log_watcher.output[1] + ) + + # if on_phase_start() is not called, 2 warnings should be logged + # create a new time metrics hook + local_variables = {} + time_metrics_hook_new = TimeMetricsHook() + + with self.assertLogs() as log_watcher: + time_metrics_hook_new.on_phase_end(task, local_variables) + + self.assertEqual(len(log_watcher.output), 2) + self.assertTrue( + all( + log_record.levelno == logging.WARN + for log_record in log_watcher.records + ) + ) diff --git a/test/hub_classy_hub_interface_test.py b/test/hub_classy_hub_interface_test.py new file mode 100644 index 0000000000..a74869465a --- /dev/null +++ b/test/hub_classy_hub_interface_test.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import shutil +import tempfile +import unittest +from test.generic.config_utils import get_test_task_config + +import torch +from classy_vision.dataset.transforms import ClassyTransform +from classy_vision.hub import ClassyHubInterface +from classy_vision.models import ClassyModel, build_model +from classy_vision.tasks import ClassyTask, build_task +from torchvision import models, transforms + + +class TestTransform(ClassyTransform): + def __call__(self, x): + return x + + +class TestClassyHubInterface(unittest.TestCase): + def setUp(self): + # create a base directory to write image files to + self.base_dir = tempfile.mkdtemp() + self.image_path = self.base_dir + "/img.jpg" + # create an image with a non standard size + image_tensor = torch.zeros((3, 1000, 2500), dtype=torch.float) + transforms.ToPILImage()(image_tensor).save(self.image_path) + + def tearDown(self): + # delete all the temporary data created + shutil.rmtree(self.base_dir) + + def _test_predict_and_extract_features(self, hub_interface: ClassyHubInterface): + dataset = hub_interface.create_image_dataset( + [self.image_path], phase_type="test" + ) + data_iterator = hub_interface.get_data_iterator(dataset) + input = next(data_iterator) + # set the model to eval mode + hub_interface.eval() + output = hub_interface.predict(input) + self.assertIsNotNone(output) + # see the prediction for the input + hub_interface.predict(input).argmax().item() + # check extract features + output = hub_interface.extract_features(input) + self.assertIsNotNone(output) + + def _get_classy_model(self): + config = get_test_task_config() + model_config = config["model"] + return build_model(model_config) + + def _get_non_classy_model(self): + return models.resnet18(pretrained=False) + + def test_from_task(self): + config = get_test_task_config() + task = build_task(config) + hub_interface = ClassyHubInterface.from_task(task) + + self.assertIsInstance(hub_interface.task, ClassyTask) + self.assertIsInstance(hub_interface.model, ClassyModel) + + # this will pick up the transform from the task's config + self._test_predict_and_extract_features(hub_interface) + + # test that the correct transform is picked up + phase_type = "test" + test_transform = TestTransform() + task.datasets[phase_type].transform = test_transform + hub_interface = ClassyHubInterface.from_task(task) + dataset = hub_interface.create_image_dataset( + [self.image_path], phase_type=phase_type + ) + self.assertIsInstance(dataset.transform, TestTransform) + + def test_from_model(self): + for model in [self._get_classy_model(), self._get_non_classy_model()]: + hub_interface = ClassyHubInterface.from_model(model) + + self.assertIsNone(hub_interface.task) + self.assertIsInstance(hub_interface.model, ClassyModel) + + # this will pick up the transform from imagenet + self._test_predict_and_extract_features(hub_interface) diff --git a/test/losses_barron_loss_test.py b/test/losses_barron_loss_test.py new file mode 100644 index 0000000000..1667932704 --- /dev/null +++ b/test/losses_barron_loss_test.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +import torch +from classy_vision.losses import BarronLoss, build_loss + + +class TestBarronLoss(unittest.TestCase): + def _get_config(self): + return {"name": "barron", "size_average": True, "alpha": 1.0, "c": 1.0} + + def _get_outputs(self): + return torch.tensor([[2.0]]) + + def _get_targets(self): + return torch.tensor([3.0]) + + def test_build_barron(self): + config = self._get_config() + crit = build_loss(config) + self.assertTrue(isinstance(crit, BarronLoss)) + self.assertEqual(crit.size_average, config["size_average"]) + self.assertAlmostEqual(crit.alpha, config["alpha"]) + self.assertAlmostEqual(crit.c, config["c"]) + + def test_barron(self): + config = self._get_config() + crit = BarronLoss.from_config(config) + outputs = self._get_outputs() + targets = self._get_targets() + self.assertAlmostEqual(crit(outputs, targets).item(), 0.41421353816986084) + + # Alpha = 0 + config = self._get_config() + config["alpha"] = 0.0 + crit = BarronLoss.from_config(config) + outputs = self._get_outputs() + targets = self._get_targets() + self.assertAlmostEqual(crit(outputs, targets).item(), 0.40546512603759766) + + # Alpha = inf + config = self._get_config() + config["alpha"] = float("inf") + crit = BarronLoss.from_config(config) + outputs = self._get_outputs() + targets = self._get_targets() + self.assertAlmostEqual(crit(outputs, targets).item(), 0.39346933364868164) + + def test_deep_copy(self): + config = self._get_config() + crit1 = build_loss(config) + self.assertTrue(isinstance(crit1, BarronLoss)) + outputs = self._get_outputs() + targets = self._get_targets() + crit1(outputs, targets) + + crit2 = copy.deepcopy(crit1) + self.assertAlmostEqual( + crit1(outputs, targets).item(), crit2(outputs, targets).item() + ) diff --git a/test/losses_generic_utils_test.py b/test/losses_generic_utils_test.py new file mode 100644 index 0000000000..2da346da70 --- /dev/null +++ b/test/losses_generic_utils_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +from classy_vision.generic.util import convert_to_one_hot + + +class TestUtils(unittest.TestCase): + def test_single(self): + targets = torch.tensor([[4]]) + one_hot_target = convert_to_one_hot(targets, 5) + self.assertTrue(torch.allclose(one_hot_target, torch.tensor([[0, 0, 0, 0, 1]]))) + + def test_two(self): + targets = torch.tensor([[0], [1]]) + one_hot_target = convert_to_one_hot(targets, 3) + self.assertTrue( + torch.allclose(one_hot_target, torch.tensor([[1, 0, 0], [0, 1, 0]])) + ) diff --git a/test/losses_label_smoothing_cross_entropy_loss_test.py b/test/losses_label_smoothing_cross_entropy_loss_test.py new file mode 100644 index 0000000000..4666b8fd8f --- /dev/null +++ b/test/losses_label_smoothing_cross_entropy_loss_test.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +import torch +from classy_vision.losses import LabelSmoothingCrossEntropyLoss, build_loss + + +class TestLabelSmoothingCrossEntropyLoss(unittest.TestCase): + def test_build_label_smoothing_cross_entropy(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.1, + } + crit = build_loss(config) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + self.assertEqual(crit._ignore_index, -1) + + def test_smoothing_one_hot_targets(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.1, + } + crit = build_loss(config) + targets = torch.tensor([[0, 0, 0, 0, 1]]) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + valid_targets = crit.compute_valid_targets(targets, 5) + self.assertTrue( + torch.allclose(valid_targets, torch.tensor([[0.0, 0.0, 0.0, 0.0, 1.0]])) + ) + smoothed_targets = crit.smooth_targets(valid_targets, 5) + self.assertTrue( + torch.allclose( + smoothed_targets, + torch.tensor([[0.2 / 11, 0.2 / 11, 0.2 / 11, 0.2 / 11, 10.2 / 11]]), + ) + ) + + def test_smoothing_ignore_index_one_hot_targets(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.5, + } + crit = build_loss(config) + targets = torch.tensor([[-1, 0, 0, 0, 1]]) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + valid_targets = crit.compute_valid_targets(targets, 5) + self.assertTrue( + torch.allclose(valid_targets, torch.tensor([[0.0, 0.0, 0.0, 0.0, 1.0]])) + ) + smoothed_targets = crit.smooth_targets(valid_targets, 5) + self.assertTrue( + torch.allclose( + smoothed_targets, + torch.tensor([[1 / 15, 1 / 15, 1 / 15, 1 / 15, 11 / 15]]), + ) + ) + + def test_smoothing_multilabel_one_hot_targets(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.5, + } + crit = build_loss(config) + targets = torch.tensor([[1, 0, 0, 0, 1]]) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + valid_targets = crit.compute_valid_targets(targets, 5) + self.assertTrue( + torch.allclose(valid_targets, torch.tensor([[1.0, 0.0, 0.0, 0.0, 1.0]])) + ) + + smoothed_targets = crit.smooth_targets(valid_targets, 5) + self.assertTrue( + torch.allclose( + smoothed_targets, + torch.tensor([[6 / 15, 1 / 15, 1 / 15, 1 / 15, 6 / 15]]), + ) + ) + + def test_smoothing_all_ones_one_hot_targets(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.1, + } + crit = build_loss(config) + targets = torch.tensor([[1, 1, 1, 1]]) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + valid_targets = crit.compute_valid_targets(targets, 4) + self.assertTrue( + torch.allclose(valid_targets, torch.tensor([[1.0, 1.0, 1.0, 1.0]])) + ) + + smoothed_targets = crit.smooth_targets(valid_targets, 4) + self.assertTrue( + torch.allclose(smoothed_targets, torch.tensor([[0.25, 0.25, 0.25, 0.25]])) + ) + + def test_smoothing_mixed_one_hot_targets(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.5, + } + crit = build_loss(config) + targets = torch.tensor([[1, 1, 1, 1, 1], [1, 0, 0, 0, 1]]) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + valid_targets = crit.compute_valid_targets(targets, 5) + self.assertTrue( + torch.allclose( + valid_targets, + torch.tensor([[1.0, 1.0, 1.0, 1.0, 1.0], [1.0, 0.0, 0.0, 0.0, 1.0]]), + ) + ) + smoothed_targets = crit.smooth_targets(valid_targets, 5) + self.assertTrue( + torch.allclose( + smoothed_targets, + torch.tensor( + [ + [0.2, 0.2, 0.2, 0.2, 0.2], + [6 / 15, 1 / 15, 1 / 15, 1 / 15, 6 / 15], + ] + ), + ) + ) + + def test_smoothing_class_targets(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.5, + } + crit = build_loss(config) + targets = torch.tensor([4, -1]) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + valid_targets = crit.compute_valid_targets(targets, 5) + self.assertTrue( + torch.allclose( + valid_targets, + torch.tensor([[0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0, 0.0]]), + ) + ) + smoothed_targets = crit.smooth_targets(valid_targets, 5) + self.assertTrue( + torch.allclose( + smoothed_targets, + torch.tensor( + [ + [1 / 15, 1 / 15, 1 / 15, 1 / 15, 11 / 15], + [0.2, 0.2, 0.2, 0.2, 0.2], + ] + ), + ) + ) + + def test_unnormalized_label_smoothing_cross_entropy(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.5, + } + crit = LabelSmoothingCrossEntropyLoss.from_config(config) + outputs = torch.tensor([[0.0, 7.0, 0.0, 0.0, 2.0]]) + targets = torch.tensor([[0, 0, 0, 0, 1]]) + self.assertAlmostEqual(crit(outputs, targets).item(), 5.07609558) + + def test_ignore_index_label_smoothing_cross_entropy(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.2, + } + crit = LabelSmoothingCrossEntropyLoss.from_config(config) + outputs = torch.tensor([[0.0, 7.0]]) + targets = torch.tensor([[-1]]) + self.assertAlmostEqual(crit(outputs, targets).item(), 3.50090909) + + def test_class_integer_label_smoothing_cross_entropy(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.2, + } + crit = LabelSmoothingCrossEntropyLoss.from_config(config) + outputs = torch.tensor([[1.0, 2.0], [0.0, 2.0]]) + targets = torch.tensor([[0], [1]]) + self.assertAlmostEqual(crit(outputs, targets).item(), 0.76176142) + + def test_deep_copy(self): + config = { + "name": "label_smoothing_cross_entropy", + "ignore_index": -1, + "smoothing_param": 0.5, + } + crit = build_loss(config) + self.assertTrue(isinstance(crit, LabelSmoothingCrossEntropyLoss)) + outputs = torch.tensor([[0.0, 7.0, 0.0, 0.0, 2.0]]) + targets = torch.tensor([[0, 0, 0, 0, 1]]) + crit(outputs, targets) + + crit2 = copy.deepcopy(crit) + self.assertAlmostEqual(crit2(outputs, targets).item(), 5.07609558) diff --git a/test/losses_multi_output_sum_loss_test.py b/test/losses_multi_output_sum_loss_test.py new file mode 100644 index 0000000000..1bd83488fc --- /dev/null +++ b/test/losses_multi_output_sum_loss_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +from classy_vision.losses import ( + ClassyLoss, + MultiOutputSumLoss, + build_loss, + register_loss, +) + + +@register_loss("mock_1") +class MockLoss1(ClassyLoss): + def forward(self, pred, target): + return torch.tensor(1.0) + + @classmethod + def from_config(cls, config): + return cls() + + +class TestMultiOutputSumLoss(unittest.TestCase): + def test_multi_output_sum_loss(self): + config = {"name": "multi_output_sum_loss", "loss": {"name": "mock_1"}} + crit = build_loss(config) + self.assertTrue(isinstance(crit, MultiOutputSumLoss)) + + # test with a single output + output = torch.tensor([1.0, 2.3]) + target = torch.tensor(1.0) + self.assertAlmostEqual(crit(output, target).item(), 1.0) + + # test with a list of outputs + output = [torch.tensor([1.2, 3.2])] * 5 + target = torch.tensor(2.3) + self.assertAlmostEqual(crit(output, target).item(), 5.0) diff --git a/test/losses_soft_target_cross_entropy_loss_test.py b/test/losses_soft_target_cross_entropy_loss_test.py new file mode 100644 index 0000000000..47374ecdb3 --- /dev/null +++ b/test/losses_soft_target_cross_entropy_loss_test.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +import torch +from classy_vision.losses import SoftTargetCrossEntropyLoss, build_loss + + +class TestSoftTargetCrossEntropyLoss(unittest.TestCase): + def _get_config(self): + return { + "name": "soft_target_cross_entropy", + "ignore_index": -1, + "reduction": "mean", + } + + def _get_outputs(self): + return torch.tensor([[1.0, 7.0, 0.0, 0.0, 2.0]]) + + def _get_targets(self): + return torch.tensor([[1, 0, 0, 0, 1]]) + + def _get_loss(self): + return 5.51097965 + + def test_build_soft_target_cross_entropy(self): + config = self._get_config() + crit = build_loss(config) + self.assertTrue(isinstance(crit, SoftTargetCrossEntropyLoss)) + self.assertEqual(crit._ignore_index, -1) + self.assertEqual(crit._reduction, "mean") + + def test_soft_target_cross_entropy(self): + config = self._get_config() + crit = SoftTargetCrossEntropyLoss.from_config(config) + outputs = self._get_outputs() + targets = self._get_targets() + self.assertAlmostEqual(crit(outputs, targets).item(), self._get_loss()) + + # Verify ignore index works + outputs = self._get_outputs() + targets = torch.tensor([[-1, 0, 0, 0, 1]]) + self.assertAlmostEqual(crit(outputs, targets).item(), 5.01097918) + + def test_unnormalized_soft_target_cross_entropy(self): + config = { + "name": "soft_target_cross_entropy", + "ignore_index": -1, + "reduction": "mean", + "normalize_targets": None, + } + crit = SoftTargetCrossEntropyLoss.from_config(config) + outputs = self._get_outputs() + targets = self._get_targets() + self.assertAlmostEqual(crit(outputs, targets).item(), 11.0219593) + + # Verify ignore index works + outputs = self._get_outputs() + targets = torch.tensor([[-1, 0, 0, 0, 1]]) + self.assertAlmostEqual(crit(outputs, targets).item(), 5.01097965) + + def test_ignore_row(self): + # If a sample has no valid targets, it should be ignored in the reduction. + config = self._get_config() + crit = SoftTargetCrossEntropyLoss.from_config(config) + outputs = torch.tensor([[1.0, 7.0, 0.0, 0.0, 2.0], [4.0, 2.0, 1.0, 6.0, 0.5]]) + targets = torch.tensor([[1, 0, 0, 0, 1], [-1, -1, -1, -1, -1]]) + self.assertAlmostEqual(crit(outputs, targets).item(), self._get_loss()) + + def test_deep_copy(self): + config = self._get_config() + crit = build_loss(config) + self.assertTrue(isinstance(crit, SoftTargetCrossEntropyLoss)) + outputs = self._get_outputs() + targets = self._get_targets() + crit(outputs, targets) + + crit2 = copy.deepcopy(crit) + self.assertAlmostEqual(crit2(outputs, targets).item(), self._get_loss()) diff --git a/test/losses_sum_arbitrary_loss_test.py b/test/losses_sum_arbitrary_loss_test.py new file mode 100644 index 0000000000..3aa1bd0442 --- /dev/null +++ b/test/losses_sum_arbitrary_loss_test.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +import torch +from classy_vision.losses import ClassyLoss, SumArbitraryLoss, build_loss, register_loss + + +@register_loss("mock_a") +class MockLoss1(ClassyLoss): + def forward(self, pred, target): + return torch.tensor(1.0) + + @classmethod + def from_config(cls, config): + return cls() + + +@register_loss("mock_b") +class MockLoss2(ClassyLoss): + def forward(self, pred, target): + return torch.tensor(2.0) + + @classmethod + def from_config(cls, config): + return cls() + + +@register_loss("mock_c") +class MockLoss3(ClassyLoss): + def forward(self, pred, target): + return torch.tensor(3.0) + + @classmethod + def from_config(cls, config): + return cls() + + +class TestSumArbitraryLoss(unittest.TestCase): + def _get_config(self): + return { + "name": "sum_arbitrary", + "weights": [1.0, 1.0, 1.0], + "losses": [{"name": "mock_a"}, {"name": "mock_b"}, {"name": "mock_c"}], + } + + def _get_outputs(self): + return torch.tensor([[2.0, 8.0]]) + + def _get_targets(self): + return torch.tensor([1]) + + def test_build_sum_arbitrary(self): + config = self._get_config() + crit = build_loss(config) + self.assertTrue(isinstance(crit, SumArbitraryLoss)) + self.assertAlmostEqual(crit.weights, [1.0, 1.0, 1.0]) + mod_list = [MockLoss1, MockLoss2, MockLoss3] + for idx, crit_type in enumerate(mod_list): + self.assertTrue(isinstance(crit.losses[idx], crit_type)) + + def test_sum_arbitrary(self): + config = self._get_config() + crit = SumArbitraryLoss.from_config(config) + outputs = self._get_outputs() + targets = self._get_targets() + self.assertAlmostEqual(crit(outputs, targets).item(), 1.0 + 2.0 + 3.0) + + # Verify changing losses works + new_config = copy.deepcopy(config) + new_config.update( + {"losses": [{"name": "mock_a"}, {"name": "mock_b"}], "weights": [1.0, 1.0]} + ) + crit = SumArbitraryLoss.from_config(new_config) + self.assertAlmostEqual(crit(outputs, targets).item(), 1.0 + 2.0) + + # Verify changing weights works + new_config = copy.deepcopy(config) + new_config.update({"weights": [1.0, 2.0, 3.0]}) + crit = SumArbitraryLoss.from_config(new_config) + self.assertAlmostEqual( + crit(outputs, targets).item(), 1.0 + 2.0 * 2.0 + 3.0 * 3.0 + ) + + def test_deep_copy(self): + config = self._get_config() + crit1 = build_loss(config) + self.assertTrue(isinstance(crit1, SumArbitraryLoss)) + outputs = self._get_outputs() + targets = self._get_targets() + crit1(outputs, targets) + + crit2 = copy.deepcopy(crit1) + self.assertAlmostEqual( + crit1(outputs, targets).item(), crit2(outputs, targets).item() + ) diff --git a/test/losses_test.py b/test/losses_test.py new file mode 100644 index 0000000000..5f8796aa74 --- /dev/null +++ b/test/losses_test.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +from classy_vision.losses import build_loss + + +class CriterionsTest(unittest.TestCase): + """ + Test that build_transform is able to build torch losses correctly. + """ + + def _test_loss(self, config, output, target, expected_loss): + # test that we are able to build losses from torch.nn.modules.loss + # and that they work correctly + + crit = build_loss(config) + + # test that the weights are set correctly + self.assertAlmostEqual(crit.weight.numpy().tolist(), [1.0, 1.0]) + + # test that the loss is computed correctly + self.assertAlmostEqual(crit(output, target).item(), expected_loss) + + # verify ignore index works + if "ignore_index" in config: + self.assertAlmostEqual(crit(output, torch.tensor([-1])).item(), 0.0) + + def test_cross_entropy_loss(self): + """ + Test CrossEntropyLoss + """ + config = { + "name": "CrossEntropyLoss", + "weight": [1.0, 1.0], + "ignore_index": -1, + "reduction": "mean", + } + output = torch.tensor([[9.0, 1.0]]) + target = torch.tensor([1]) + expected_loss = 8.000335693359375 + self._test_loss(config, output, target, expected_loss) + + def test_bce_with_logits_loss(self): + """ + Test BCEWithLogitsLoss + """ + config = { + "name": "BCEWithLogitsLoss", + "weight": [1.0, 1.0], + "reduction": "mean", + } + output = torch.tensor([0.999, 0.999]) + target = torch.tensor([1.0, 1.0]) + expected_loss = 0.313530727260701 + self._test_loss(config, output, target, expected_loss) diff --git a/test/manual/hooks_model_complexity_hook_test.py b/test/manual/hooks_model_complexity_hook_test.py new file mode 100644 index 0000000000..248a4a3eab --- /dev/null +++ b/test/manual/hooks_model_complexity_hook_test.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import re +import unittest +from test.generic.config_utils import get_test_classy_task, get_test_model_configs + +from classy_vision.hooks import ModelComplexityHook +from classy_vision.models import build_model + + +class TestModelComplexityHook(unittest.TestCase): + def test_model_complexity(self) -> None: + """ + Test that the number of parameters and the FLOPs are calcuated correctly. + """ + model_configs = get_test_model_configs() + expected_mega_flops = [4122, 4274, 106152] + expected_params = [25557032, 25028904, 43009448] + local_variables = {} + + task = get_test_classy_task() + task.prepare() + + # create a model complexity hook + model_complexity_hook = ModelComplexityHook() + + for model_config, mega_flops, params in zip( + model_configs, expected_mega_flops, expected_params + ): + model = build_model(model_config) + + task.base_model = model + + with self.assertLogs() as log_watcher: + model_complexity_hook.on_start(task, local_variables) + + # there should be 2 log statements generated + self.assertEqual(len(log_watcher.output), 2) + + # first statement - either the MFLOPs or a warning + if mega_flops is not None: + match = re.search( + r"FLOPs for forward pass: (?P[-+]?\d*\.\d+|\d+) MFLOPs", + log_watcher.output[0], + ) + self.assertIsNotNone(match) + self.assertEqual(mega_flops, float(match.group("mega_flops"))) + else: + self.assertIn( + "Model contains unsupported modules", log_watcher.output[0] + ) + + # second statement + match = re.search( + r"Number of parameters in model: (?P[-+]?\d*\.\d+|\d+)", + log_watcher.output[1], + ) + self.assertIsNotNone(match) + self.assertEqual(params, float(match.group("params"))) diff --git a/test/manual/hooks_model_tensorboard_hook_test.py b/test/manual/hooks_model_tensorboard_hook_test.py new file mode 100644 index 0000000000..dc01b40753 --- /dev/null +++ b/test/manual/hooks_model_tensorboard_hook_test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +import unittest.mock as mock +from test.generic.config_utils import get_test_classy_task, get_test_model_configs + +from classy_vision.hooks import ModelTensorboardHook +from classy_vision.models import build_model +from tensorboardX import SummaryWriter + + +class TestModelTensorboardHook(unittest.TestCase): + @mock.patch("classy_vision.hooks.model_tensorboard_hook.is_master") + def test_writer(self, mock_is_master_func: mock.MagicMock) -> None: + """ + Tests that the tensorboard writer calls SummaryWriter with the model + iff is_master() is True. + """ + mock_summary_writer = mock.create_autospec(SummaryWriter, instance=True) + + task = get_test_classy_task() + task.prepare() + + for master in [False, True]: + mock_is_master_func.return_value = master + model_configs = get_test_model_configs() + local_variables = {} + + for model_config in model_configs: + model = build_model(model_config) + task.base_model = model + + # create a model tensorboard hook + model_tensorboard_hook = ModelTensorboardHook(mock_summary_writer) + + with self.assertLogs(): + model_tensorboard_hook.on_start(task, local_variables) + + if master: + # SummaryWriter should have been init-ed with the correct + # add_graph should be called once with model as the first arg + mock_summary_writer.add_graph.assert_called_once() + self.assertEqual( + mock_summary_writer.add_graph.call_args[0][0], model + ) + else: + # add_graph shouldn't be called since is_master() is False + mock_summary_writer.add_graph.assert_not_called() + mock_summary_writer.reset_mock() diff --git a/test/manual/hooks_progress_bar_hook_test.py b/test/manual/hooks_progress_bar_hook_test.py new file mode 100644 index 0000000000..ad3b8df6a7 --- /dev/null +++ b/test/manual/hooks_progress_bar_hook_test.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +import unittest.mock as mock +from test.generic.config_utils import get_test_classy_task + +import progressbar +from classy_vision.hooks import ProgressBarHook + + +class TestProgressBarHook(unittest.TestCase): + @mock.patch("classy_vision.hooks.progress_bar_hook.progressbar") + @mock.patch("classy_vision.hooks.progress_bar_hook.is_master") + def test_progress_bar( + self, mock_is_master: mock.MagicMock, mock_progressbar_pkg: mock.MagicMock + ) -> None: + """ + Tests that the progress bar is created, updated and destroyed correctly. + """ + mock_progress_bar = mock.create_autospec(progressbar.ProgressBar, instance=True) + mock_progressbar_pkg.ProgressBar.return_value = mock_progress_bar + + mock_is_master.return_value = True + + local_variables = {} + + task = get_test_classy_task() + task.prepare() + task.advance_phase() + + num_batches = task.num_batches_per_phase + # make sure we are checking at least one batch + self.assertGreater(num_batches, 0) + + # create a progress bar hook + progress_bar_hook = ProgressBarHook() + + # progressbar.ProgressBar should be init-ed with num_batches + progress_bar_hook.on_phase_start(task, local_variables) + mock_progressbar_pkg.ProgressBar.assert_called_once_with(num_batches) + mock_progress_bar.start.assert_called_once_with() + mock_progress_bar.start.reset_mock() + mock_progressbar_pkg.ProgressBar.reset_mock() + + # on_update should update the progress bar correctly + for i in range(num_batches): + progress_bar_hook.on_update(task, local_variables) + mock_progress_bar.update.assert_called_once_with(i + 1) + mock_progress_bar.update.reset_mock() + + # check that even if on_update is called again, the progress bar is + # only updated with num_batches + for _ in range(num_batches): + progress_bar_hook.on_update(task, local_variables) + mock_progress_bar.update.assert_called_once_with(num_batches) + mock_progress_bar.update.reset_mock() + + # finish should be called on the progress bar + progress_bar_hook.on_phase_end(task, local_variables) + mock_progress_bar.finish.assert_called_once_with() + mock_progress_bar.finish.reset_mock() + + # check that even if the progress bar isn't created, the code doesn't + # crash + progress_bar_hook = ProgressBarHook() + try: + progress_bar_hook.on_update(task, local_variables) + progress_bar_hook.on_phase_end(task, local_variables) + except Exception as e: + self.fail( + "Received Exception when on_phase_start() isn't called: {}".format(e) + ) + mock_progressbar_pkg.ProgressBar.assert_not_called() + + # check that a progress bar is not created if is_master() returns False + mock_is_master.return_value = False + progress_bar_hook = ProgressBarHook() + try: + progress_bar_hook.on_phase_start(task, local_variables) + progress_bar_hook.on_update(task, local_variables) + progress_bar_hook.on_phase_end(task, local_variables) + except Exception as e: + self.fail("Received Exception when is_master() is False: {}".format(e)) + self.assertIsNone(progress_bar_hook.progress_bar) + mock_progressbar_pkg.ProgressBar.assert_not_called() diff --git a/test/manual/hooks_tensorboard_plot_hook_test.py b/test/manual/hooks_tensorboard_plot_hook_test.py new file mode 100644 index 0000000000..d98e28ae35 --- /dev/null +++ b/test/manual/hooks_tensorboard_plot_hook_test.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging +import shutil +import tempfile +import unittest +import unittest.mock as mock +from itertools import product +from test.generic.config_utils import get_test_mlp_task_config, get_test_task_config + +from classy_vision.hooks import TensorboardPlotHook +from classy_vision.optim.param_scheduler import UpdateInterval +from classy_vision.tasks import build_task +from classy_vision.trainer import LocalTrainer +from tensorboardX import SummaryWriter + + +class TestTensorboardPlotHook(unittest.TestCase): + def setUp(self) -> None: + self.base_dir = tempfile.mkdtemp() + + def tearDown(self) -> None: + shutil.rmtree(self.base_dir) + + @mock.patch("classy_vision.hooks.tensorboard_plot_hook.is_master") + def test_writer(self, mock_is_master_func: mock.MagicMock) -> None: + """ + Tests that the tensorboard writer writes the correct scalars to SummaryWriter + iff is_master() is True. + """ + for phase_idx, master in product([0, 1, 2], [True, False]): + train, phase_type = ( + (True, "train") if phase_idx % 2 == 0 else (False, "test") + ) + mock_is_master_func.return_value = master + + # set up the task and state + config = get_test_task_config() + config["dataset"]["train"]["batchsize_per_replica"] = 2 + config["dataset"]["test"]["batchsize_per_replica"] = 5 + task = build_task(config) + task.prepare() + task.phase_idx = phase_idx + task.train = train + + losses = [1.23, 4.45, 12.3, 3.4] + + local_variables = {} + + summary_writer = SummaryWriter(self.base_dir) + # create a spy on top of summary_writer + summary_writer = mock.MagicMock(wraps=summary_writer) + + # create a loss lr tensorboard hook + tensorboard_plot_hook = TensorboardPlotHook(summary_writer) + + # test that the hook logs a warning and doesn't write anything to + # the writer if on_phase_start() is not called for initialization + # before on_update() is called. + with self.assertLogs() as log_watcher: + tensorboard_plot_hook.on_update(task, local_variables) + + self.assertTrue( + len(log_watcher.records) == 1 + and log_watcher.records[0].levelno == logging.WARN + and "learning_rates is not initialized" in log_watcher.output[0] + ) + + # test that the hook logs a warning and doesn't write anything to + # the writer if on_phase_start() is not called for initialization + # if on_phase_end() is called. + with self.assertLogs() as log_watcher: + tensorboard_plot_hook.on_phase_end(task, local_variables) + + self.assertTrue( + len(log_watcher.records) == 1 + and log_watcher.records[0].levelno == logging.WARN + and "learning_rates is not initialized" in log_watcher.output[0] + ) + summary_writer.add_scalar.reset_mock() + + # run the hook in the correct order + tensorboard_plot_hook.on_phase_start(task, local_variables) + + for loss in losses: + task.losses.append(loss) + tensorboard_plot_hook.on_update(task, local_variables) + + tensorboard_plot_hook.on_phase_end(task, local_variables) + + if master: + # add_scalar() should have been called with the right scalars + if train: + loss_key = f"{phase_type}_loss" + learning_rate_key = f"{phase_type}_learning_rate_updates" + summary_writer.add_scalar.assert_any_call( + loss_key, mock.ANY, global_step=mock.ANY, walltime=mock.ANY + ) + summary_writer.add_scalar.assert_any_call( + learning_rate_key, + mock.ANY, + global_step=mock.ANY, + walltime=mock.ANY, + ) + avg_loss_key = f"avg_{phase_type}_loss" + summary_writer.add_scalar.assert_any_call( + avg_loss_key, mock.ANY, global_step=mock.ANY + ) + for meter in task.meters: + for name in meter.value: + meter_key = f"{phase_type}_{meter.name}_{name}" + summary_writer.add_scalar.assert_any_call( + meter_key, mock.ANY, global_step=mock.ANY + ) + else: + # add_scalar() shouldn't be called since is_master() is False + summary_writer.add_scalar.assert_not_called() + summary_writer.add_scalar.reset_mock() + + def test_logged_lr(self): + # Mock LR scheduler + def scheduler_mock(where): + return where + + mock_lr_scheduler = mock.Mock(side_effect=scheduler_mock) + mock_lr_scheduler.update_interval = UpdateInterval.STEP + + # Mock Logging + class DummySummaryWriter(object): + def __init__(self): + self.scalar_logs = {} + + def add_scalar(self, key, value, global_step=None, walltime=None) -> None: + self.scalar_logs[key] = self.scalar_logs.get(key, []) + [value] + + config = get_test_mlp_task_config() + config["num_epochs"] = 3 + config["dataset"]["train"]["batchsize_per_replica"] = 5 + config["dataset"]["test"]["batchsize_per_replica"] = 5 + task = build_task(config) + + writer = DummySummaryWriter() + hook = TensorboardPlotHook(writer) + task.set_hooks([hook]) + task.optimizer.lr_scheduler = mock_lr_scheduler + + trainer = LocalTrainer() + trainer.train(task) + + # We have 10 samples, batch size is 5. Each epoch is done in two steps. + self.assertEqual( + writer.scalar_logs["train_learning_rate_updates"], + [0, 1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6], + ) diff --git a/test/manual/hooks_visdom_hook_test.py b/test/manual/hooks_visdom_hook_test.py new file mode 100644 index 0000000000..f44096da2a --- /dev/null +++ b/test/manual/hooks_visdom_hook_test.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest +import unittest.mock as mock +from itertools import product +from test.generic.config_utils import get_test_task_config + +from classy_vision.hooks import VisdomHook +from classy_vision.tasks import build_task +from visdom import Visdom + + +class TestVisdomHook(unittest.TestCase): + @mock.patch("classy_vision.hooks.visdom_hook.is_master") + @mock.patch("classy_vision.hooks.visdom_hook.Visdom", autospec=True) + def test_visdom( + self, mock_visdom_cls: mock.MagicMock, mock_is_master: mock.MagicMock + ) -> None: + """ + Tests that visdom is populated with plots. + """ + mock_visdom = mock.create_autospec(Visdom, instance=True) + mock_visdom_cls.return_value = mock_visdom + + local_variables = {} + + # set up the task and state + config = get_test_task_config() + config["dataset"]["train"]["batchsize_per_replica"] = 2 + config["dataset"]["test"]["batchsize_per_replica"] = 5 + task = build_task(config) + task.prepare() + + losses = [1.2, 2.3, 1.23, 2.33] + loss_vals = {"train": 0.8825, "test": 0.353} + + task.losses = losses + + visdom_server = "localhost" + visdom_port = 8097 + + for master, visdom_conn in product([False, True], [False, True]): + mock_is_master.return_value = master + mock_visdom.check_connection.return_value = visdom_conn + + # create a visdom hook + visdom_hook = VisdomHook(visdom_server, visdom_port) + + mock_visdom_cls.assert_called_once() + mock_visdom_cls.reset_mock() + + counts = {"train": 0, "test": 0} + count = 0 + + for phase_idx in range(10): + train = phase_idx % 2 == 0 + task.train = train + phase_type = "train" if train else "test" + + counts[phase_type] += 1 + count += 1 + + # test that the metrics don't change if losses is empty and that + # visdom.line() is not called + task.losses = [] + original_metrics = copy.deepcopy(visdom_hook.metrics) + visdom_hook.on_phase_end(task, local_variables) + self.assertDictEqual(original_metrics, visdom_hook.metrics) + mock_visdom.line.assert_not_called() + + # test that the metrics are updated correctly when losses + # is non empty + task.losses = [loss * count for loss in losses] + visdom_hook.on_phase_end(task, local_variables) + + # every meter should be present and should have the correct length + for meter in task.meters: + for key in meter.value: + key = phase_type + "_" + meter.name + "_" + key + self.assertTrue( + key in visdom_hook.metrics + and type(visdom_hook.metrics[key]) == list + and len(visdom_hook.metrics[key]) == counts[phase_type] + ) + + # the loss metric should be calculated correctly + loss_key = phase_type + "_loss" + self.assertTrue( + loss_key in visdom_hook.metrics + and type(visdom_hook.metrics[loss_key]) == list + and len(visdom_hook.metrics[loss_key]) == counts[phase_type] + ) + self.assertAlmostEqual( + visdom_hook.metrics[loss_key][-1], + loss_vals[phase_type] * count, + places=4, + ) + + # the lr metric should be correct + lr_key = phase_type + "_learning_rate" + self.assertTrue( + lr_key in visdom_hook.metrics + and type(visdom_hook.metrics[lr_key]) == list + and len(visdom_hook.metrics[lr_key]) == counts[phase_type] + ) + self.assertAlmostEqual( + visdom_hook.metrics[lr_key][-1], task.optimizer.lr, places=4 + ) + + if master and not train and visdom_conn: + # visdom.line() should be called once + mock_visdom.line.assert_called_once() + mock_visdom.line.reset_mock() + else: + # visdom.line() should not be called + mock_visdom.line.assert_not_called() diff --git a/test/manual/models_classy_vision_model_test.py b/test/manual/models_classy_vision_model_test.py new file mode 100644 index 0000000000..58857d40c7 --- /dev/null +++ b/test/manual/models_classy_vision_model_test.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest +from collections import defaultdict +from test.generic.config_utils import get_test_model_configs +from test.generic.utils import compare_model_state + +import torch +from classy_vision.heads import build_head +from classy_vision.models import ClassyModel, build_model + + +class TestClassyModel(unittest.TestCase): + model_configs = get_test_model_configs() + + def _get_config(self, model_config): + return { + "name": "classification_task", + "num_epochs": 12, + "loss": {"name": "test_loss"}, + "dataset": { + "name": "imagenet", + "batchsize_per_replica": 8, + "use_pairs": False, + "num_samples_per_phase": None, + "use_shuffle": {"train": True, "test": False}, + }, + "meters": [], + "model": model_config, + "optimizer": {"name": "test_opt"}, + } + + def _compare_model_state(self, state, state2): + compare_model_state(self, state, state2) + + def test_build_model(self): + for cfg in self.model_configs: + config = self._get_config(cfg) + model = build_model(config["model"]) + self.assertTrue(isinstance(model, ClassyModel)) + self.assertTrue( + type(model.input_shape) == tuple and len(model.input_shape) == 3 + ) + self.assertTrue( + type(model.output_shape) == tuple and len(model.output_shape) == 2 + ) + self.assertTrue(type(model.model_depth) == int) + + def test_get_set_state(self): + config = self._get_config(self.model_configs[0]) + model = build_model(config["model"]) + fake_input = torch.Tensor(1, 3, 224, 224).float() + model.eval() + state = model.get_classy_state() + with torch.no_grad(): + output = model(fake_input) + + model2 = build_model(config["model"]) + model2.set_classy_state(state) + + # compare the states + state2 = model2.get_classy_state() + self._compare_model_state(state, state2) + + model2.eval() + with torch.no_grad(): + output2 = model2(fake_input) + self.assertTrue(torch.allclose(output, output2)) + + # test deep_copy by assigning a deep copied state to model2 + # and then changing the original model's state + state = model.get_classy_state(deep_copy=True) + + model3 = build_model(config["model"]) + state3 = model3.get_classy_state() + + # assign model2's state to model's and also re-assign model's state + model2.set_classy_state(state) + model.set_classy_state(state3) + + # compare the states + state2 = model2.get_classy_state() + self._compare_model_state(state, state2) + + def test_get_set_head_states(self): + config = copy.deepcopy(self._get_config(self.model_configs[0])) + head_configs = config["model"]["heads"] + config["model"]["heads"] = [] + model = build_model(config["model"]) + trunk_state = model.get_classy_state() + + heads = defaultdict(dict) + for head_config in head_configs: + head = build_head(head_config) + heads[head_config["fork_block"]][head.unique_id] = head + model.set_heads(heads) + model_state = model.get_classy_state() + + # the heads should be the same as we set + self.assertEqual(len(heads), len(model.get_heads())) + for block_name, hs in model.get_heads().items(): + self.assertEqual(hs, heads[block_name]) + + model._clear_heads() + self._compare_model_state(model.get_classy_state(), trunk_state) + + model.set_heads(heads) + self._compare_model_state(model.get_classy_state(), model_state) diff --git a/test/meters_accuracy_meter_test.py b/test/meters_accuracy_meter_test.py new file mode 100644 index 0000000000..055b95a1b0 --- /dev/null +++ b/test/meters_accuracy_meter_test.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from test.generic.meter_test_utils import ClassificationMeterTest + +import torch +from classy_vision import meters +from classy_vision.meters import AccuracyMeter + + +class TestAccuracyMeter(ClassificationMeterTest): + def test_accuracy_meter_registry(self): + accuracy_meter = meters.build_meter({"name": "accuracy", "topk": [1, 2]}) + self.assertTrue(isinstance(accuracy_meter, AccuracyMeter)) + + def test_single_meter_update_and_reset(self): + """ + This test verifies that the meter works as expected on a single + update + reset + same single update. + """ + meter = AccuracyMeter(topk=[1, 2]) + + # Batchsize = 3, num classes = 3, score is a value in {1, 2, + # 3}...3 is the highest score + model_output = torch.tensor([[3, 2, 1], [3, 1, 2], [1, 3, 2]]) + + # Class 0 is the correct class for sample 1, class 2 for sample 2, etc + target = torch.tensor([0, 1, 2]) + + # Only the first sample has top class correct, first and third + # sample have correct class in top 2 + expected_value = {"top_1": 1 / 3.0, "top_2": 2 / 3.0} + + self.meter_update_and_reset_test(meter, model_output, target, expected_value) + + def test_double_meter_update_and_reset(self): + meter = AccuracyMeter(topk=[1, 2]) + + # Batchsize = 3, num classes = 3, score is a value in {1, 2, + # 3}...3 is the highest score...two batches in this test + model_outputs = [ + torch.tensor([[3, 2, 1], [3, 1, 2], [1, 3, 2]]), + torch.tensor([[3, 2, 1], [1, 3, 2], [1, 3, 2]]), + ] + + # Class 0 is the correct class for sample 1, class 2 for + # sample 2, etc, in both batches + targets = [torch.tensor([0, 1, 2]), torch.tensor([0, 1, 2])] + + # First batch has top-1 accuracy of 1/3.0, top-2 accuracy of 2/3.0 + # Second batch has top-1 accuracy of 2/3.0, top-2 accuracy of 3/3.0 + expected_value = {"top_1": 3 / 6.0, "top_2": 5 / 6.0} + + self.meter_update_and_reset_test(meter, model_outputs, targets, expected_value) + + def test_meter_invalid_model_output(self): + meter = AccuracyMeter(topk=[1, 2]) + # This model output has 3 dimensions instead of expected 2 + model_output = torch.tensor( + [[[3, 2, 1], [1, 2, 3]], [[-1, -3, -4], [-10, -90, -100]]] + ) + target = torch.tensor([0, 1, 2]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_invalid_target(self): + meter = AccuracyMeter(topk=[1, 2]) + model_output = torch.tensor([[3, 2, 1], [3, 1, 2], [1, 3, 2]]) + # Target has 2 dimensions instead of expected 1 + target = torch.tensor([[0, 1, 2], [0, 1, 2]]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_invalid_topk(self): + meter = AccuracyMeter(topk=[1, 5]) + model_output = torch.tensor([[3, 2, 1], [3, 1, 2], [1, 3, 2]]) + target = torch.tensor([0, 1, 2]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_get_set_classy_state_test(self): + # In this test we update meter0 with model_output0 & target0 + # and we update meter1 with model_output1 & target1 then + # transfer the state from meter1 to meter0 and validate they + # give same expected value. + # Expected value is the expected value of meter1 + meters = [AccuracyMeter(topk=[1, 2]), AccuracyMeter(topk=[1, 2])] + + # Batchsize = 3, num classes = 3, score is a value in {1, 2, + # 3}...3 is the highest score + model_outputs = [ + torch.tensor([[1, 2, 3], [1, 2, 3], [2, 3, 1]]), + torch.tensor([[3, 2, 1], [3, 1, 2], [1, 3, 2]]), + ] + + # Class 0 is the correct class for sample 1, class 2 for sample 2, etc + targets = [torch.tensor([0, 1, 2]), torch.tensor([0, 1, 2])] + + # Value for second update + expected_value = {"top_1": 1 / 3.0, "top_2": 2 / 3.0} + + self.meter_get_set_classy_state_test( + meters, model_outputs, targets, expected_value + ) + + def test_meter_distributed(self): + # Meter0 will execute on one process, Meter1 on the other + meters = [AccuracyMeter(topk=[1, 2]), AccuracyMeter(topk=[1, 2])] + + # Batchsize = 3, num classes = 3, score is a value in {1, 2, + # 3}...3 is the highest score + model_outputs = [ + torch.tensor([[3, 2, 1], [3, 1, 2], [1, 3, 2]]), # Meter 0 + torch.tensor([[3, 2, 1], [1, 3, 2], [1, 3, 2]]), # Meter 1 + torch.tensor([[3, 2, 1], [3, 1, 2], [1, 3, 2]]), # Meter 0 + torch.tensor([[3, 2, 1], [1, 3, 2], [1, 3, 2]]), # Meter 1 + ] + + # Class 0 is the correct class for sample 1, class 2 for sample 2, etc + targets = [ + torch.tensor([0, 1, 2]), # Meter 0 + torch.tensor([0, 1, 2]), # Meter 1 + torch.tensor([0, 1, 2]), # Meter 0 + torch.tensor([0, 1, 2]), # Meter 1 + ] + + # In first two updates there are 3 correct top-2, 5 correct in top 2 + # The same occurs in the second two updates and is added to first + expected_values = [ + {"top_1": 3 / 6.0, "top_2": 5 / 6.0}, # After one update to each meter + {"top_1": 6 / 12.0, "top_2": 10 / 12.0}, # After two updates to each meter + ] + + self.meter_distributed_test(meters, model_outputs, targets, expected_values) diff --git a/test/meters_precision_meter_test.py b/test/meters_precision_meter_test.py new file mode 100644 index 0000000000..d87533b57a --- /dev/null +++ b/test/meters_precision_meter_test.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from test.generic.meter_test_utils import ClassificationMeterTest + +import torch +from classy_vision import meters +from classy_vision.meters import PrecisionAtKMeter + + +class TestPrecisionAtKMeter(ClassificationMeterTest): + def test_precision_meter_registry(self): + meter = meters.build_meter({"name": "precision_at_k", "topk": [1, 3]}) + self.assertTrue(isinstance(meter, PrecisionAtKMeter)) + + def test_single_meter_update_and_reset(self): + """ + This test verifies that the meter works as expected on a single + update + reset + same single update. + """ + meter = PrecisionAtKMeter(topk=[1, 2]) + + # Batchsize = 3, num classes = 3, score is probability of class + model_output = torch.tensor( + [ + [0.2, 0.4, 0.4], # top-1: 1/2, top-2: 1/2 + [0.2, 0.65, 0.15], # top-1: 1, top-2: 1/0 + [0.33, 0.33, 0.34], # top-1: 2, top-2: 2/0?1 + ] + ) + + # One-hot encoding, 1 = positive for class + # sample-1: 1, sample-2: 0, sample-3: 0,1,2 + target = torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]) + + # Note for ties, we select randomly, so we should not use ambiguous ties + expected_value = {"top_1": 2 / 3.0, "top_2": 4 / 6.0} + + self.meter_update_and_reset_test(meter, model_output, target, expected_value) + + def test_double_meter_update_and_reset(self): + meter = PrecisionAtKMeter(topk=[1, 2]) + + # Batchsize = 3, num classes = 3, score is probability of class + model_outputs = [ + torch.tensor([[0.3, 0.4, 0.3], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]]), + torch.tensor([[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]]), + ] + + # One-hot encoding, 1 = positive for class + # batch-1: sample-1: 1, sample-2: 0, sample-3: 0,1,2 + # batch-2: sample-1: 1, sample-2: 1, sample-3: 1 + targets = [ + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]), + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), + ] + + # First batch has top-1 precision of 2/3.0, top-2 precision of 4/6.0 + # Second batch has top-1 precision of 2/3.0, top-2 precision of 2/6.0 + expected_value = {"top_1": 4 / 6.0, "top_2": 6 / 12.0} + + self.meter_update_and_reset_test(meter, model_outputs, targets, expected_value) + + def test_meter_invalid_model_output(self): + meter = PrecisionAtKMeter(topk=[1, 2]) + # This model output has 3 dimensions instead of expected 2 + model_output = torch.tensor( + [[[0.33, 0.33, 0.34], [1, 2, 3]], [[-1, -3, -4], [-10, -90, -100]]] + ) + target = torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_invalid_target(self): + meter = PrecisionAtKMeter(topk=[1, 2]) + model_output = torch.tensor( + [ + [0.2, 0.4, 0.4], # top-1: 1/2, top-2: 1/2 + [0.2, 0.65, 0.15], # top-1: 1, top-2: 1/0 + [0.33, 0.33, 0.34], # top-1: 2, top-2: 2/0/1 + ] + ) + # Target shape does not match model shape + target = torch.tensor([0, 1, 2]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_invalid_topk(self): + meter = PrecisionAtKMeter(topk=[1, 5]) + model_output = torch.tensor( + [ + [0.2, 0.4, 0.4], # top-1: 1/2, top-2: 1/2 + [0.2, 0.65, 0.15], # top-1: 1, top-2: 1/0 + [0.33, 0.33, 0.34], # top-1: 2, top-2: 2/0/1 + ] + ) + target = torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_get_set_classy_state_test(self): + # In this test we update meter0 with model_output0 & target0 + # and we update meter1 with model_output1 & target1 then + # transfer the state from meter1 to meter0 and validate they + # give same expected value. + # + # Expected value is the expected value of meter1 For this test + # to work, top-1 / top-2 values of meter0 / meter1 should be + # different + meters = [PrecisionAtKMeter(topk=[1, 2]), PrecisionAtKMeter(topk=[1, 2])] + model_outputs = [ + torch.tensor([[0.05, 0.4, 0.05], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]]), + torch.tensor([[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]]), + ] + targets = [ + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 0]]), + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), + ] + + # Second update's expected value + expected_value = {"top_1": 2 / 3.0, "top_2": 2 / 6.0} + + self.meter_get_set_classy_state_test( + meters, model_outputs, targets, expected_value + ) + + def test_meter_distributed(self): + # Meter0 will execute on one process, Meter1 on the other + meters = [PrecisionAtKMeter(topk=[1, 2]), PrecisionAtKMeter(topk=[1, 2])] + + # Batchsize = 3, num classes = 3, score is probability of class + model_outputs = [ + torch.tensor( + [[0.3, 0.4, 0.3], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]] + ), # Meter 0 + torch.tensor( + [[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]] + ), # Meter 1 + torch.tensor( + [[0.3, 0.4, 0.3], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]] + ), # Meter 0 + torch.tensor( + [[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]] + ), # Meter 1 + ] + + # Class 0 is the correct class for sample 1, class 2 for sample 2, etc + targets = [ + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]), # Meter 0 + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), # Meter 1 + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]), # Meter 0 + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), # Meter 1 + ] + + # In first two updates there are 4 correct top-1, 6 correct in top 2 + # The same occurs in the second two updates and is added to first + expected_values = [ + {"top_1": 4 / 6.0, "top_2": 6 / 12.0}, # After one update to each meter + {"top_1": 8 / 12.0, "top_2": 12 / 24.0}, # After two updates to each meter + ] + + self.meter_distributed_test(meters, model_outputs, targets, expected_values) + + def test_non_onehot_target(self): + """ + This test verifies that the meter works as expected on a single + update + reset + same single update. + """ + meter = PrecisionAtKMeter(topk=[1, 2], target_is_one_hot=False, num_classes=3) + + # Batchsize = 2, num classes = 3, score is probability of class + model_outputs = [ + torch.tensor([[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]]), + torch.tensor([[0.2, 0.4, 0.4], [0.2, 0.65, 0.15], [0.1, 0.8, 0.1]]), + ] + + # One-hot encoding, 1 = positive for class + targets = [ + torch.tensor([[1], [1], [1]]), # [[0, 1, 0], [0, 1, 0], [0, 1, 0]] + torch.tensor([[0], [1], [2]]), # [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + ] + + # Note for ties, we select randomly, so we should not use ambiguous ties + # First batch has top-1 precision of 2/3.0, top-2 precision of 2/6.0 + # Second batch has top-1 precision of 1/3.0, top-2 precision of 1/6.0 + expected_value = {"top_1": 3 / 6.0, "top_2": 3 / 12.0} + + self.meter_update_and_reset_test(meter, model_outputs, targets, expected_value) diff --git a/test/meters_recall_meter_test.py b/test/meters_recall_meter_test.py new file mode 100644 index 0000000000..93789b9d65 --- /dev/null +++ b/test/meters_recall_meter_test.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from test.generic.meter_test_utils import ClassificationMeterTest + +import torch +from classy_vision import meters +from classy_vision.meters import RecallAtKMeter + + +class TestRecallAtKMeter(ClassificationMeterTest): + def test_recall_meter_registry(self): + meter = meters.build_meter({"name": "recall_at_k", "topk": [1, 3]}) + self.assertTrue(isinstance(meter, RecallAtKMeter)) + + def test_single_meter_update_and_reset(self): + """ + This test verifies that the meter works as expected on a single + update + reset + same single update. + """ + meter = RecallAtKMeter(topk=[1, 2]) + + # Batchsize = 3, num classes = 3, score is probability of class + model_output = torch.tensor( + [ + [0.2, 0.4, 0.4], # top-1: 1/2, top-2: 1/2 + [0.2, 0.65, 0.15], # top-1: 1, top-2: 1/0 + [0.33, 0.33, 0.34], # top-1: 2, top-2: 2/0?1 + ] + ) + + # One-hot encoding, 1 = positive for class + # sample-1: 1, sample-2: 0, sample-3: 0,1,2 + target = torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]) + + # Note for ties, we select randomly, so we should not use ambiguous ties + expected_value = {"top_1": 2 / 5.0, "top_2": 4 / 5.0} + + self.meter_update_and_reset_test(meter, model_output, target, expected_value) + + def test_double_meter_update_and_reset(self): + meter = RecallAtKMeter(topk=[1, 2]) + + # Batchsize = 3, num classes = 3, score is probability of class + model_outputs = [ + torch.tensor([[0.3, 0.4, 0.3], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]]), + torch.tensor([[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]]), + ] + + # One-hot encoding, 1 = positive for class + # batch-1: sample-1: 1, sample-2: 0, sample-3: 0,1,2 + # batch-2: sample-1: 1, sample-2: 1, sample-3: 1 + targets = [ + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]), + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), + ] + + # First batch has top-1 recall of 2/5.0, top-2 recall of 4/5.0 + # Second batch has top-1 recall of 2/3.0, top-2 recall of 2/3.0 + expected_value = {"top_1": 4 / 8.0, "top_2": 6 / 8.0} + + self.meter_update_and_reset_test(meter, model_outputs, targets, expected_value) + + def test_meter_invalid_model_output(self): + meter = RecallAtKMeter(topk=[1, 2]) + # This model output has 3 dimensions instead of expected 2 + model_output = torch.tensor( + [[[0.33, 0.33, 0.34], [1, 2, 3]], [[-1, -3, -4], [-10, -90, -100]]] + ) + target = torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_invalid_target(self): + meter = RecallAtKMeter(topk=[1, 2]) + model_output = torch.tensor( + [ + [0.2, 0.4, 0.4], # top-1: 1/2, top-2: 1/2 + [0.2, 0.65, 0.15], # top-1: 1, top-2: 1/0 + [0.33, 0.33, 0.34], # top-1: 2, top-2: 2/0/1 + ] + ) + # Target shape does not match model shape + target = torch.tensor([0, 1, 2]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_invalid_topk(self): + meter = RecallAtKMeter(topk=[1, 5]) + model_output = torch.tensor( + [ + [0.2, 0.4, 0.4], # top-1: 1/2, top-2: 1/2 + [0.2, 0.65, 0.15], # top-1: 1, top-2: 1/0 + [0.33, 0.33, 0.34], # top-1: 2, top-2: 2/0/1 + ] + ) + target = torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_get_set_classy_state_test(self): + # In this test we update meter0 with model_output0 & target0 + # and we update meter1 with model_output1 & target1 then + # transfer the state from meter1 to meter0 and validate they + # give same expected value. + # + # Expected value is the expected value of meter1 For this test + # to work, top-1 / top-2 values of meter0 / meter1 should be + # different + meters = [RecallAtKMeter(topk=[1, 2]), RecallAtKMeter(topk=[1, 2])] + model_outputs = [ + torch.tensor([[0.05, 0.4, 0.05], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]]), + torch.tensor([[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]]), + ] + targets = [ + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 0]]), + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), + ] + + # Second update's expected value + expected_value = {"top_1": 2 / 3.0, "top_2": 2 / 3.0} + + self.meter_get_set_classy_state_test( + meters, model_outputs, targets, expected_value + ) + + def test_meter_distributed(self): + # Meter0 will execute on one process, Meter1 on the other + meters = [RecallAtKMeter(topk=[1, 2]), RecallAtKMeter(topk=[1, 2])] + + # Batchsize = 3, num classes = 3, score is probability of class + model_outputs = [ + torch.tensor( + [[0.3, 0.4, 0.3], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]] + ), # Meter 0 + torch.tensor( + [[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]] + ), # Meter 1 + torch.tensor( + [[0.3, 0.4, 0.3], [0.2, 0.65, 0.15], [0.33, 0.33, 0.34]] + ), # Meter 0 + torch.tensor( + [[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]] + ), # Meter 1 + ] + + # Class 0 is the correct class for sample 1, class 2 for sample 2, etc + targets = [ + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]), # Meter 0 + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), # Meter 1 + torch.tensor([[0, 1, 0], [1, 0, 0], [1, 1, 1]]), # Meter 0 + torch.tensor([[0, 1, 0], [0, 1, 0], [0, 1, 0]]), # Meter 1 + ] + + # In first two updates there are 4 correct top-1 out of 8 + # total, 6 correct in top 2 out of 8. The same occurs in the + # second two updates and is added to first + expected_values = [ + {"top_1": 4 / 8.0, "top_2": 6 / 8.0}, # After one update to each meter + {"top_1": 8 / 16.0, "top_2": 12 / 16.0}, # After two updates to each meter + ] + + self.meter_distributed_test(meters, model_outputs, targets, expected_values) + + def test_non_onehot_target(self): + """ + This test verifies that the meter works as expected on a single + update + reset + same single update. + """ + meter = RecallAtKMeter(topk=[1, 2], target_is_one_hot=False, num_classes=3) + + # Batchsize = 2, num classes = 3, score is probability of class + model_outputs = [ + torch.tensor([[0.05, 0.4, 0.05], [0.15, 0.65, 0.2], [0.4, 0.2, 0.4]]), + torch.tensor([[0.2, 0.4, 0.4], [0.2, 0.65, 0.15], [0.1, 0.8, 0.1]]), + ] + + # One-hot encoding, 1 = positive for class + targets = [ + torch.tensor([[1], [1], [1]]), # [[0, 1, 0], [0, 1, 0], [0, 1, 0]] + torch.tensor([[0], [1], [2]]), # [[1, 0, 0], [0, 1, 0], [0, 0, 1]] + ] + + # Note for ties, we select randomly, so we should not use ambiguous ties + # First batch has top-1 recall of 2/3.0, top-2 recall of 2/6.0 + # Second batch has top-1 recall of 1/3.0, top-2 recall of 4/6.0 + expected_value = {"top_1": 3 / 6.0, "top_2": 6 / 12.0} + + self.meter_update_and_reset_test(meter, model_outputs, targets, expected_value) diff --git a/test/meters_video_accuracy_meter_test.py b/test/meters_video_accuracy_meter_test.py new file mode 100644 index 0000000000..c40b962c2e --- /dev/null +++ b/test/meters_video_accuracy_meter_test.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from test.generic.meter_test_utils import ClassificationMeterTest + +import torch +from classy_vision import meters +from classy_vision.meters import VideoAccuracyMeter + + +class TestVideoAccuracyMeter(ClassificationMeterTest): + def test_accuracy_meter_registry(self): + accuracy_meter = meters.build_meter( + { + "name": "video_accuracy", + "topk": [1, 2], + "clips_per_video_train": 1, + "clips_per_video_test": 2, + } + ) + self.assertTrue(isinstance(accuracy_meter, VideoAccuracyMeter)) + + def test_single_meter_update_and_reset(self): + """ + This test verifies that the meter works as expected on a single + update + reset + same single update. + """ + meter = VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ) + # Batchsize = 3, num classes = 3, clips_per_video is 2, + # score is a value in {1, 2, 3} + model_output = torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ) + # Class 0 is the correct class for video 1, class 2 for video 2, and + # class 1 for video + target = torch.tensor([0, 0, 1, 1, 2, 2]) + + # Only the first sample has top class correct, first and third + # sample have correct class in top 2 + expected_value = {"top_1": 1 / 3.0, "top_2": 3 / 3.0} + + self.meter_update_and_reset_test( + meter, model_output, target, expected_value, is_train=False + ) + + def test_double_meter_update_and_reset(self): + meter = VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ) + # Batchsize = 3, num classes = 3, clips_per_video is 2, + # score is a value in {1, 2, 3}. + # Data of two batch is provided + model_outputs = [ + torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ), + torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ), + ] + # Class 0 is the correct class for video 1, class 2 for video 2, and + # class 1 for video, in both batches + targets = [torch.tensor([0, 0, 1, 1, 2, 2]), torch.tensor([0, 0, 1, 1, 2, 2])] + + # First batch has top-1 accuracy of 1/3.0, top-2 accuracy of 2/3.0 + # Second batch has top-1 accuracy of 2/3.0, top-2 accuracy of 3/3.0 + expected_value = {"top_1": 2 / 6.0, "top_2": 6 / 6.0} + + self.meter_update_and_reset_test( + meter, model_outputs, targets, expected_value, is_train=False + ) + + def test_meter_invalid_model_output(self): + meter = VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ) + # This model output has 3 dimensions instead of expected 2 + model_output = torch.tensor( + [[[3, 2, 1], [1, 2, 3]], [[-1, -3, -4], [-10, -90, -100]]], + dtype=torch.float, + ) + target = torch.tensor([0, 1, 2]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_invalid_target(self): + meter = VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ) + model_output = torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ) + # Target has 2 dimensions instead of expected 1 + target = torch.tensor([[0, 1, 2], [0, 1, 2]]) + + self.meter_invalid_meter_input_test(meter, model_output, target) + # Target of clips from the same video is not consistent + target = torch.tensor([0, 2, 1, 1, 2, 2]) + + self.meter_invalid_update_test(meter, model_output, target, is_train=False) + + def test_meter_invalid_topk(self): + meter = VideoAccuracyMeter( + topk=[1, 5], clips_per_video_train=1, clips_per_video_test=2 + ) + model_output = torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ) + target = torch.tensor([0, 1, 2]) + self.meter_invalid_meter_input_test(meter, model_output, target) + + def test_meter_get_set_classy_state_test(self): + # In this test we update meter0 with model_output0 & target0 + # and we update meter1 with model_output1 & target1 then + # transfer the state from meter1 to meter0 and validate they + # give same expected value. + # Expected value is the expected value of meter1 + meters = [ + VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ), + VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ), + ] + # Batchsize = 3, num classes = 3, score is a value in {1, 2, + # 3}...3 is the highest score + model_outputs = [ + torch.tensor( + [[1, 2, 3], [1, 1, 3], [2, 2, 1], [3, 2, 1], [2, 2, 2], [2, 3, 1]], + dtype=torch.float, + ), + torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ), + ] + # Class 2 is the correct class for sample 1, class 0 for sample 2, etc + targets = [torch.tensor([0, 0, 1, 1, 2, 2]), torch.tensor([0, 0, 1, 1, 2, 2])] + # Value for second update + expected_value = {"top_1": 1 / 3.0, "top_2": 3 / 3.0} + + self.meter_get_set_classy_state_test( + meters, model_outputs, targets, expected_value, is_train=False + ) + + def test_meter_distributed(self): + # Meter0 will execute on one process, Meter1 on the other + meters = [ + VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ), + VideoAccuracyMeter( + topk=[1, 2], clips_per_video_train=1, clips_per_video_test=2 + ), + ] + + # Batchsize = 3, num classes = 3, score is a value in {1, 2, + # 3}...3 is the highest score + model_outputs = [ + torch.tensor( + [[1, 2, 3], [1, 1, 3], [2, 2, 1], [3, 2, 1], [2, 2, 2], [2, 3, 1]], + dtype=torch.float, + ), # Meter 0 + torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ), # Meter 1 + torch.tensor( + [[1, 2, 3], [1, 1, 3], [2, 2, 1], [3, 2, 1], [2, 2, 2], [2, 3, 1]], + dtype=torch.float, + ), # Meter 0 + torch.tensor( + [[3, 2, 1], [3, 1, 2], [1, 2, 2], [1, 2, 3], [2, 2, 2], [1, 3, 2]], + dtype=torch.float, + ), # Meter 1 + ] + + # For meter 0, class 2 is the correct class for sample 1, class 0 for sample 2, + # etc + targets = [ + torch.tensor([0, 0, 1, 1, 2, 2]), # Meter 0 + torch.tensor([0, 0, 1, 1, 2, 2]), # Meter 1 + torch.tensor([0, 0, 1, 1, 2, 2]), # Meter 0 + torch.tensor([0, 0, 1, 1, 2, 2]), # Meter 1 + ] + + # In first two updates there are 3 correct top-2, 5 correct in top 2 + # The same occurs in the second two updates and is added to first + expected_values = [ + {"top_1": 1 / 6.0, "top_2": 4 / 6.0}, # After one update to each meter + {"top_1": 2 / 12.0, "top_2": 8 / 12.0}, # After two updates to each meter + ] + + self.meter_distributed_test( + meters, model_outputs, targets, expected_values, is_train=False + ) diff --git a/test/models_classy_model_test.py b/test/models_classy_model_test.py new file mode 100644 index 0000000000..44d24a320a --- /dev/null +++ b/test/models_classy_model_test.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os +import shutil +import tempfile +import unittest +from test.generic.config_utils import get_test_task_config + +import torch +import torch.nn as nn + +from classy_vision.generic.util import load_checkpoint +from classy_vision.hooks import CheckpointHook +from classy_vision.models import ClassyModel, register_model +from classy_vision.tasks import build_task + + +@register_model("my_test_model") +class MyTestModel(ClassyModel): + def __init__(self): + super().__init__() + self.linear = nn.Linear(10, 5) + + def forward(self, x): + return self.linear(x) + + @classmethod + def from_config(cls, config): + return cls() + + +class TestClassyModel(unittest.TestCase): + def setUp(self) -> None: + self.base_dir = tempfile.mkdtemp() + + def tearDown(self) -> None: + shutil.rmtree(self.base_dir) + + def test_from_checkpoint(self): + config = get_test_task_config() + config["model"] = {"name": "my_test_model"} + task = build_task(config) + task.prepare() + + local_variables = {} + checkpoint_folder = self.base_dir + "/checkpoint_end_test/" + device = "cpu" + input_args = {"config": config} + + # Simulate training by setting the model parameters to zero + for param in task.model.parameters(): + param.data.zero_() + + checkpoint_hook = CheckpointHook( + checkpoint_folder, input_args, phase_types=["train"] + ) + + # Create checkpoint dir, save checkpoint + os.mkdir(checkpoint_folder) + checkpoint_hook.on_start(task, local_variables) + + task.train = True + checkpoint_hook.on_phase_end(task, local_variables) + + # Model should be checkpointed. load and compare + checkpoint = load_checkpoint(checkpoint_folder, device) + + model = ClassyModel.from_checkpoint(checkpoint) + self.assertTrue(isinstance(model, MyTestModel)) + + # All parameters must be zero + for param in model.parameters(): + self.assertTrue(torch.all(param.data == 0)) diff --git a/test/models_classy_model_wrapper_test.py b/test/models_classy_model_wrapper_test.py new file mode 100644 index 0000000000..48053d0410 --- /dev/null +++ b/test/models_classy_model_wrapper_test.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest +from test.generic.config_utils import get_fast_test_task_config + +import torch +import torch.nn as nn +from classy_vision.models import ClassyModel +from classy_vision.models.classy_model_wrapper import ClassyModelWrapper +from classy_vision.tasks import build_task +from classy_vision.trainer import LocalTrainer +from torchvision import models + + +class TestModel(nn.Module): + def __init__(self): + super().__init__() + self.linear = nn.Linear(10, 5) + + def forward(self, x): + return self.linear(x) + + def extract_features(self, x): + return torch.cat([x, x], dim=1) + + +class TestClassyModelWrapper(unittest.TestCase): + def test_classy_model_wrapper(self): + model = TestModel() + classy_model = ClassyModelWrapper(model) + # test that the returned object is an instance of ClassyModel + self.assertIsInstance(classy_model, ClassyModel) + + # test that forward works correctly + input = torch.zeros((100, 10)) + output = classy_model(input) + self.assertEqual(output.shape, (100, 5)) + + # test that extract_features works correctly + input = torch.zeros((100, 10)) + output = classy_model.extract_features(input) + self.assertEqual(output.shape, (100, 20)) + + # test that get_classy_state and set_classy_state work + nn.init.constant_(classy_model.model.linear.weight, 1) + weights = copy.deepcopy(classy_model.model.linear.weight.data) + state_dict = classy_model.get_classy_state(deep_copy=True) + nn.init.constant_(classy_model.model.linear.weight, 0) + classy_model.set_classy_state(state_dict) + self.assertTrue(torch.allclose(weights, classy_model.model.linear.weight.data)) + + def test_classy_model_wrapper_properties(self): + # test that the properties work correctly when passed to the wrapper + model = TestModel() + num_classes = 5 + input_shape = (10,) + output_shape = (num_classes,) + model_depth = 1 + classy_model = ClassyModelWrapper( + model, + input_shape=input_shape, + output_shape=output_shape, + model_depth=model_depth, + ) + self.assertEqual(classy_model.input_shape, input_shape) + self.assertEqual(classy_model.output_shape, output_shape) + self.assertEqual(classy_model.model_depth, model_depth) + + def test_train_step(self): + # test that the model can be run in a train step + model = models.resnet34(pretrained=False) + classy_model = ClassyModelWrapper(model) + + config = get_fast_test_task_config() + task = build_task(config) + task.set_model(classy_model) + trainer = LocalTrainer() + trainer.train(task) diff --git a/test/models_densenet_test.py b/test/models_densenet_test.py new file mode 100644 index 0000000000..eba18a1bed --- /dev/null +++ b/test/models_densenet_test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +from test.generic.utils import compare_model_state + +import torch +from classy_vision.models import build_model + + +MODELS = { + "small_densenet": { + "name": "densenet", + "num_blocks": [1, 1, 1, 1], + "num_classes": 1000, + "init_planes": 4, + "growth_rate": 32, + "expansion": 4, + "final_bn_relu": True, + "small_input": True, + } +} + + +class TestDensenet(unittest.TestCase): + def _test_model(self, model_config): + """This test will build Densenet models, run a forward pass and + verify output shape, and then verify that get / set state + works. + + I do this in one test so that we construct the model a minimum + number of times. + """ + model = build_model(model_config) + + # Verify forward pass works + input = torch.ones([1, 3, 32, 32]) + output = model.forward(input) + self.assertEqual(output.size(), (1, 1000)) + + # Verify get_set_state + new_model = build_model(model_config) + state = model.get_classy_state() + new_model.set_classy_state(state) + new_state = new_model.get_classy_state() + + compare_model_state(self, state, new_state, check_heads=True) + + def test_small_resnet(self): + self._test_model(MODELS["small_densenet"]) diff --git a/test/models_mlp_test.py b/test/models_mlp_test.py new file mode 100644 index 0000000000..262f84270b --- /dev/null +++ b/test/models_mlp_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest + +import torch +from classy_vision.models import ClassyModel, build_model + + +class TestMLPModel(unittest.TestCase): + def test_build_model(self): + config = {"name": "mlp", "input_dim": 3, "output_dim": 1, "hidden_dims": [2]} + model = build_model(config) + self.assertTrue(isinstance(model, ClassyModel)) + self.assertEqual(model.model_depth, 2) + + tensor = torch.tensor([[1, 2, 3]], dtype=torch.float) + output = model.forward(tensor) + self.assertEqual(output.shape, torch.Size([1, 1])) + + tensor = torch.tensor([[1, 2, 3], [1, 2, 3]], dtype=torch.float) + output = model.forward(tensor) + self.assertEqual(output.shape, torch.Size([2, 1])) diff --git a/test/models_resnext3d_test.py b/test/models_resnext3d_test.py new file mode 100644 index 0000000000..f575ab1533 --- /dev/null +++ b/test/models_resnext3d_test.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest +from test.generic.utils import compare_model_state + +import torch +from classy_vision.models import ClassyModel, build_model + + +class TestResNeXt3D(unittest.TestCase): + def setUp(self): + model_config_template = { + "name": "resnext3d", + "input_key": "video", + "clip_crop_size": 112, + "skip_transformation_type": "postactivated_shortcut", + "frames_per_clip": 32, + "input_planes": 3, + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 3, + "stage_planes": 64, + "num_groups": 1, + "width_per_group": 16, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "in_plane": 512, + "pool_size": (2, 7, 7), + "activation_func": "softmax", + "num_classes": 2, + } + ], + } + pbt = "postactivated_bottleneck_transformation" + model_config_variants = [ + # ResNeXt3D-34 + { + "residual_transformation_type": "basic_transformation", + "num_blocks": [3, 4, 6, 3], + }, + # ResNeXt3D-50 + {"residual_transformation_type": pbt, "num_blocks": [3, 4, 6, 3]}, + # ResNeXt3D-101 + {"residual_transformation_type": pbt, "num_blocks": [3, 4, 23, 3]}, + ] + + self.model_configs = [] + for variant in model_config_variants: + model_config = copy.deepcopy(model_config_template) + model_config.update(variant) + + block_idx = model_config["num_blocks"][-1] + # attach the head at the last block + model_config["heads"][0]["fork_block"] = "pathway0-stage4-block%d" % ( + block_idx - 1 + ) + + self.model_configs.append(model_config) + + self.batchsize = 1 + + self.forward_pass_configs = { + "train": { + # input shape: N x C x T x H x W + "input": {"video": torch.rand(self.batchsize, 3, 16, 112, 112)}, + "model": { + "stem_maxpool": False, + "stage_temporal_stride": [1, 2, 2, 2], + "stage_spatial_stride": [1, 2, 2, 2], + }, + }, + "test": { + "input": {"video": torch.rand(self.batchsize, 3, 16, 256, 320)}, + "model": { + "stem_maxpool": True, + "stage_temporal_stride": [1, 2, 2, 2], + "stage_spatial_stride": [1, 2, 2, 2], + }, + }, + } + + def test_build_model(self): + for model_config in self.model_configs: + model = build_model(model_config) + self.assertTrue(isinstance(model, ClassyModel)) + self.assertTrue( + type(model.output_shape) == tuple and len(model.output_shape) == 2 + ) + self.assertTrue(type(model.model_depth) == int) + + def test_forward_pass(self): + for split, split_config in self.forward_pass_configs.items(): + for model_config in self.model_configs: + forward_pass_model_config = copy.deepcopy(model_config) + forward_pass_model_config.update(split_config["model"]) + + num_classes = forward_pass_model_config["heads"][0]["num_classes"] + + model = build_model(forward_pass_model_config) + model.train(split == "train") + + out = model(split_config["input"]) + + self.assertEqual(out.size(), (self.batchsize, num_classes)) + + def test_set_classy_state_plain(self): + # We use the same model architecture to save and load a model state. + # This is a plain use case of `set_classy_state` method + for model_config in self.model_configs: + model = build_model(model_config) + model_state = model.get_classy_state() + + model2 = build_model(model_config) + model2.set_classy_state(model_state) + model2_state = model2.get_classy_state() + compare_model_state(self, model_state, model2_state) + + def _get_model_config_weight_inflation(self): + model_2d_config = { + "name": "resnext3d", + "frames_per_clip": 1, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "postactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 1, + "stem_spatial_kernel": 7, + "stem_maxpool": True, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[1], [1], [1], [1]], + "temporal_conv_1x1": [True, True, True, True], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 1000, + "zero_init_residual_transform": True, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [1, 7, 7], + "activation_func": "softmax", + "num_classes": 1000, + "fork_block": "pathway0-stage4-block2", + "in_plane": 2048, + "use_dropout": False, + } + ], + } + + model_3d_config = { + "name": "resnext3d", + "frames_per_clip": 8, + "input_planes": 3, + "clip_crop_size": 224, + "skip_transformation_type": "postactivated_shortcut", + "residual_transformation_type": "postactivated_bottleneck_transformation", + "num_blocks": [3, 4, 6, 3], + "input_key": "video", + "stem_name": "resnext3d_stem", + "stem_planes": 64, + "stem_temporal_kernel": 5, + "stem_spatial_kernel": 7, + "stem_maxpool": True, + "stage_planes": 256, + "stage_temporal_kernel_basis": [[3], [3, 1], [3, 1], [1, 3]], + "temporal_conv_1x1": [True, True, True, True], + "stage_temporal_stride": [1, 1, 1, 1], + "stage_spatial_stride": [1, 2, 2, 2], + "num_groups": 1, + "width_per_group": 64, + "num_classes": 1000, + "freeze_trunk": False, + "zero_init_residual_transform": True, + "heads": [ + { + "name": "fully_convolutional_linear", + "unique_id": "default_head", + "pool_size": [8, 7, 7], + "activation_func": "softmax", + "num_classes": 1000, + "fork_block": "pathway0-stage4-block2", + "in_plane": 2048, + "use_dropout": True, + } + ], + } + return model_2d_config, model_3d_config + + def test_set_classy_state_weight_inflation(self): + # Get model state from a 2D ResNet model, inflate the 2D conv weights, + # and use them to initialize 3D conv weights. This is an advanced use of + # `set_classy_state` method. + model_2d_config, model_3d_config = self._get_model_config_weight_inflation() + model_2d = build_model(model_2d_config) + model_2d_state = model_2d.get_classy_state() + + model_3d = build_model(model_3d_config) + model_3d.set_classy_state(model_2d_state) + model_3d_state = model_3d.get_classy_state() + + for name, weight_2d in model_2d_state["model"]["trunk"].items(): + weight_3d = model_3d_state["model"]["trunk"][name] + if weight_2d.dim() == 5: + # inflation only applies to conv weights + self.assertEqual(weight_3d.dim(), 5) + if weight_2d.shape[2] == 1 and weight_3d.shape[2] > 1: + weight_2d_inflated = ( + weight_2d.repeat(1, 1, weight_3d.shape[2], 1, 1) + / weight_3d.shape[2] + ) + self.assertTrue(torch.equal(weight_3d, weight_2d_inflated)) + + def test_set_classy_state_weight_inflation_inconsistent_kernel_size(self): + # Get model state from a 2D ResNet model, inflate the 2D conv weights, + # and use them to initialize 3D conv weights. + model_2d_config, model_3d_config = self._get_model_config_weight_inflation() + # Modify conv kernel size in the stem layer of 2D model to 5, which is + # inconsistent with the kernel size 7 used in 3D model. + model_2d_config["stem_spatial_kernel"] = 5 + model_2d = build_model(model_2d_config) + model_2d_state = model_2d.get_classy_state() + model_3d = build_model(model_3d_config) + with self.assertRaises(AssertionError): + model_3d.set_classy_state(model_2d_state) diff --git a/test/models_resnext_test.py b/test/models_resnext_test.py new file mode 100644 index 0000000000..c246e977d0 --- /dev/null +++ b/test/models_resnext_test.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +from test.generic.utils import compare_model_state + +import torch +from classy_vision.models import build_model + + +MODELS = { + "small_resnext": { + "name": "resnext", + "num_blocks": [1, 1, 1, 1], + "init_planes": 4, + "reduction": 4, + "base_width_and_cardinality": [2, 32], + "small_input": True, + "zero_init_bn_residuals": True, + "basic_layer": True, + "final_bn_relu": True, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-0", + "in_plane": 128, + } + ], + }, + "small_resnet": { + "name": "resnet", + "num_blocks": [1, 1, 1, 1], + "init_planes": 4, + "reduction": 4, + "small_input": True, + "zero_init_bn_residuals": True, + "basic_layer": True, + "final_bn_relu": True, + "heads": [ + { + "name": "fully_connected", + "unique_id": "default_head", + "num_classes": 1000, + "fork_block": "block3-0", + "in_plane": 128, + } + ], + }, +} + + +class TestResnext(unittest.TestCase): + def _test_model(self, model_config): + """This test will build ResNeXt-* models, run a forward pass and + verify output shape, and then verify that get / set state + works. + + I do this in one test so that we construct the model a minimum + number of times. + """ + model = build_model(model_config) + + # Verify forward pass works + input = torch.ones([1, 3, 32, 32]) + output = model.forward(input) + self.assertEqual(output.size(), (1, 1000)) + + # Verify get_set_state + new_model = build_model(model_config) + state = model.get_classy_state() + new_model.set_classy_state(state) + new_state = new_model.get_classy_state() + + compare_model_state(self, state, new_state, check_heads=True) + + def test_small_resnext(self): + self._test_model(MODELS["small_resnext"]) + + def test_small_resnet(self): + self._test_model(MODELS["small_resnet"]) diff --git a/test/optim_param_scheduler_composite_test.py b/test/optim_param_scheduler_composite_test.py new file mode 100644 index 0000000000..a5e6357b71 --- /dev/null +++ b/test/optim_param_scheduler_composite_test.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.composite_scheduler import ( + CompositeParamScheduler, + UpdateInterval, +) + + +class TestCompositeScheduler(unittest.TestCase): + _num_epochs = 10 + + def _get_valid_long_config(self): + return { + "name": "composite", + "schedulers": [ + {"name": "constant", "value": 0.1}, + {"name": "constant", "value": 0.2}, + {"name": "constant", "value": 0.3}, + {"name": "constant", "value": 0.4}, + ], + "lengths": [0.2, 0.4, 0.1, 0.3], + } + + def _get_lengths_sum_less_one_config(self): + return { + "name": "composite", + "schedulers": [ + {"name": "constant", "value": 0.1}, + {"name": "constant", "value": 0.2}, + ], + "lengths": [0.7, 0.2999], + } + + def _get_valid_mixed_config(self): + return { + "name": "composite", + "schedulers": [ + {"name": "step", "values": [0.1, 0.2, 0.3, 0.4, 0.5], "num_epochs": 10}, + {"name": "cosine", "start_lr": 0.42, "end_lr": 0.0001}, + ], + "lengths": [0.5, 0.5], + } + + def _get_valid_linear_config(self): + return { + "name": "composite", + "schedulers": [ + {"name": "linear", "start_lr": 0.0, "end_lr": 0.5}, + {"name": "linear", "start_lr": 0.5, "end_lr": 1.0}, + ], + "lengths": [0.5, 0.5], + "interval_scaling": ["rescaled", "rescaled"], + } + + def test_invalid_config(self): + config = self._get_valid_mixed_config() + bad_config = copy.deepcopy(config) + + # No schedulers + bad_config["schedulers"] = [] + bad_config["lengths"] = [] + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + # Size of schedulers and lengths doesn't match + bad_config["schedulers"] = copy.deepcopy(config["schedulers"]) + bad_config["lengths"] = copy.deepcopy(config["lengths"]) + bad_config["schedulers"].append(bad_config["schedulers"][-1]) + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + # Sum of lengths < 1 + bad_config["schedulers"] = copy.deepcopy(config["schedulers"]) + bad_config["lengths"][-1] -= 0.1 + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + # Sum of lengths > 1 + bad_config["lengths"] = copy.deepcopy(config["lengths"]) + bad_config["lengths"][-1] += 0.1 + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + # Bad value for update_interval + bad_config["lengths"] = copy.deepcopy(config["lengths"]) + bad_config["update_interval"] = "epochs" + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + # Bad value for composition_mode + del bad_config["update_interval"] + bad_config["interval_scaling"] = ["rescaled", "rescaleds"] + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + # Wrong number composition modes + del bad_config["interval_scaling"] + bad_config["interval_scaling"] = ["rescaled"] + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + # Missing required parameters + del bad_config["interval_scaling"] + bad_config["lengths"] = config["lengths"] + del bad_config["lengths"] + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + bad_config["lengths"] = config["lengths"] + del bad_config["schedulers"] + with self.assertRaises(AssertionError): + CompositeParamScheduler.from_config(bad_config) + + def test_long_scheduler(self): + config = self._get_valid_long_config() + + scheduler = CompositeParamScheduler.from_config(config) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [0.1, 0.1, 0.2, 0.2, 0.2, 0.2, 0.3, 0.4, 0.4, 0.4] + + self.assertEqual(schedule, expected_schedule) + + def test_scheduler_lengths_within_epsilon_of_one(self): + config = self._get_lengths_sum_less_one_config() + scheduler = CompositeParamScheduler.from_config(config) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2] + self.assertEqual(schedule, expected_schedule) + + def test_scheduler_update_interval(self): + config = self._get_valid_mixed_config() + + # Check default + scheduler = CompositeParamScheduler.from_config(config) + self.assertEqual(scheduler.update_interval, UpdateInterval.STEP) + + # Check step + step_config = copy.deepcopy(config) + step_config["update_interval"] = "step" + scheduler = build_param_scheduler(step_config) + self.assertEqual(scheduler.update_interval, UpdateInterval.STEP) + + # Check epoch + epoch_config = copy.deepcopy(config) + epoch_config["update_interval"] = "epoch" + scheduler = build_param_scheduler(epoch_config) + self.assertEqual(scheduler.update_interval, UpdateInterval.EPOCH) + + def test_build_composite_scheduler(self): + config = self._get_valid_mixed_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, CompositeParamScheduler)) + + def test_scheduler_with_mixed_types(self): + config = self._get_valid_mixed_config() + scheduler_0 = build_param_scheduler(config["schedulers"][0]) + scheduler_1 = build_param_scheduler(config["schedulers"][1]) + + # Check scaled + config["interval_scaling"] = ["rescaled", "rescaled"] + scheduler = CompositeParamScheduler.from_config(config) + scaled_schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [ + round(scheduler_0(epoch_num / self._num_epochs), 4) + for epoch_num in range(0, self._num_epochs, 2) + ] + [ + round(scheduler_1(epoch_num / self._num_epochs), 4) + for epoch_num in range(0, self._num_epochs, 2) + ] + self.assertEqual(scaled_schedule, expected_schedule) + + # Check fixed + config["interval_scaling"] = ["fixed", "fixed"] + scheduler = CompositeParamScheduler.from_config(config) + fixed_schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [ + round(scheduler_0(epoch_num / self._num_epochs), 4) + for epoch_num in range(0, int(self._num_epochs / 2)) + ] + [ + round(scheduler_1(epoch_num / self._num_epochs), 4) + for epoch_num in range(int(self._num_epochs / 2), self._num_epochs) + ] + self.assertEqual(fixed_schedule, expected_schedule) + + # Check that default is rescaled + del config["interval_scaling"] + scheduler = CompositeParamScheduler.from_config(config) + schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + self.assertEqual(scaled_schedule, schedule) + # Check warmup of rescaled then fixed + config["interval_scaling"] = ["rescaled", "fixed"] + scheduler = CompositeParamScheduler.from_config(config) + fixed_schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [ + round(scheduler_0(epoch_num / self._num_epochs), 4) + for epoch_num in range(0, int(self._num_epochs), 2) + ] + [ + round(scheduler_1(epoch_num / self._num_epochs), 4) + for epoch_num in range(int(self._num_epochs / 2), self._num_epochs) + ] + self.assertEqual(fixed_schedule, expected_schedule) + + def test_linear_scheduler_no_gaps(self): + config = self._get_valid_linear_config() + + # Check rescaled + scheduler = CompositeParamScheduler.from_config(config) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] + self.assertEqual(expected_schedule, schedule) + + # Check fixed composition gives same result as only 1 scheduler + config["schedulers"][1] = config["schedulers"][0] + config["interval_scaling"] = ["fixed", "fixed"] + scheduler = CompositeParamScheduler.from_config(config) + linear_scheduler = build_param_scheduler(config["schedulers"][0]) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [ + linear_scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + self.assertEqual(expected_schedule, schedule) diff --git a/test/optim_param_scheduler_constant_test.py b/test/optim_param_scheduler_constant_test.py new file mode 100644 index 0000000000..eb1ff8aa36 --- /dev/null +++ b/test/optim_param_scheduler_constant_test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.constant_scheduler import ( + ConstantParamScheduler, +) + + +class TestFixedScheduler(unittest.TestCase): + _num_epochs = 12 + + def _get_valid_config(self): + return {"name": "constant", "num_epochs": self._num_epochs, "value": 0.1} + + def test_invalid_config(self): + # Invalid num epochs + config = self._get_valid_config() + + bad_config = copy.deepcopy(config) + del bad_config["value"] + with self.assertRaises(AssertionError): + ConstantParamScheduler.from_config(bad_config) + + def test_scheduler(self): + config = self._get_valid_config() + + scheduler = ConstantParamScheduler.from_config(config) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] + + self.assertEqual(schedule, expected_schedule) + # The input for the scheduler should be in the interval [0;1), open + with self.assertRaises(RuntimeError): + scheduler(1) + + def test_build_constant_scheduler(self): + config = self._get_valid_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, ConstantParamScheduler)) diff --git a/test/optim_param_scheduler_cosine_test.py b/test/optim_param_scheduler_cosine_test.py new file mode 100644 index 0000000000..a93028174a --- /dev/null +++ b/test/optim_param_scheduler_cosine_test.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.cosine_scheduler import CosineParamScheduler + + +class TestCosineScheduler(unittest.TestCase): + _num_epochs = 10 + + def _get_valid_decay_config(self): + return {"name": "cosine", "start_lr": 0.1, "end_lr": 0} + + def _get_valid_decay_config_intermediate_values(self): + return [0.0976, 0.0905, 0.0794, 0.0655, 0.05, 0.0345, 0.0206, 0.0095, 0.0024] + + def test_invalid_config(self): + # Invalid num epochs + config = self._get_valid_decay_config() + + bad_config = copy.deepcopy(config) + # Invalid Base lr + del bad_config["start_lr"] + with self.assertRaises(AssertionError): + CosineParamScheduler.from_config(bad_config) + + # Invalid end_lr + bad_config["start_lr"] = config["start_lr"] + del bad_config["end_lr"] + with self.assertRaises(AssertionError): + CosineParamScheduler.from_config(bad_config) + + def test_scheduler_as_decay(self): + config = self._get_valid_decay_config() + + scheduler = CosineParamScheduler.from_config(config) + schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [ + config["start_lr"] + ] + self._get_valid_decay_config_intermediate_values() + + self.assertEqual(schedule, expected_schedule) + + def test_scheduler_as_warmup(self): + config = self._get_valid_decay_config() + # Swap start and end lr to change to warmup + tmp = config["start_lr"] + config["start_lr"] = config["end_lr"] + config["end_lr"] = tmp + + scheduler = CosineParamScheduler.from_config(config) + schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + # Schedule should be decay reversed + expected_schedule = [config["start_lr"]] + list( + reversed(self._get_valid_decay_config_intermediate_values()) + ) + + self.assertEqual(schedule, expected_schedule) + + def test_scheduler_warmup_decay_match(self): + decay_config = self._get_valid_decay_config() + decay_scheduler = CosineParamScheduler.from_config(decay_config) + + warmup_config = copy.deepcopy(decay_config) + # Swap start and end lr to change to warmup + tmp = warmup_config["start_lr"] + warmup_config["start_lr"] = warmup_config["end_lr"] + warmup_config["end_lr"] = tmp + warmup_scheduler = CosineParamScheduler.from_config(warmup_config) + + decay_schedule = [ + round(decay_scheduler(epoch_num / 1000), 8) for epoch_num in range(1, 1000) + ] + warmup_schedule = [ + round(warmup_scheduler(epoch_num / 1000), 8) for epoch_num in range(1, 1000) + ] + + self.assertEqual(decay_schedule, list(reversed(warmup_schedule))) + + def test_build_cosine_scheduler(self): + config = self._get_valid_decay_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, CosineParamScheduler)) diff --git a/test/optim_param_scheduler_linear_test.py b/test/optim_param_scheduler_linear_test.py new file mode 100644 index 0000000000..9c38a9a667 --- /dev/null +++ b/test/optim_param_scheduler_linear_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.linear_scheduler import LinearParamScheduler + + +class TestLienarScheduler(unittest.TestCase): + _num_epochs = 10 + + def _get_valid_intermediate(self): + return [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09] + + def _get_valid_config(self): + return {"name": "linear", "start_lr": 0.0, "end_lr": 0.1} + + def test_invalid_config(self): + config = self._get_valid_config() + + bad_config = copy.deepcopy(config) + # No start lr + del bad_config["start_lr"] + with self.assertRaises(AssertionError): + LinearParamScheduler.from_config(bad_config) + + # No end lr + bad_config["start_lr"] = config["start_lr"] + del bad_config["end_lr"] + with self.assertRaises(AssertionError): + LinearParamScheduler.from_config(bad_config) + + def test_scheduler(self): + config = self._get_valid_config() + + # Check as warmup + scheduler = LinearParamScheduler.from_config(config) + schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [config["start_lr"]] + self._get_valid_intermediate() + self.assertEqual(schedule, expected_schedule) + + # Check as decay + tmp = config["start_lr"] + config["start_lr"] = config["end_lr"] + config["end_lr"] = tmp + scheduler = LinearParamScheduler.from_config(config) + schedule = [ + round(scheduler(epoch_num / self._num_epochs), 4) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [config["start_lr"]] + list( + reversed(self._get_valid_intermediate()) + ) + self.assertEqual(schedule, expected_schedule) + + def test_build_linear_scheduler(self): + config = self._get_valid_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, LinearParamScheduler)) diff --git a/test/optim_param_scheduler_multi_step_test.py b/test/optim_param_scheduler_multi_step_test.py new file mode 100644 index 0000000000..389edc5e59 --- /dev/null +++ b/test/optim_param_scheduler_multi_step_test.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.multi_step_scheduler import ( + MultiStepParamScheduler, +) + + +class TestMultiStepParamScheduler(unittest.TestCase): + _num_epochs = 12 + + def _get_valid_config(self): + return { + "name": "multistep", + "num_epochs": self._num_epochs, + "values": [0.1, 0.01, 0.001, 0.0001], + "milestones": [4, 6, 8], + } + + def test_invalid_config(self): + # Invalid num epochs + config = self._get_valid_config() + + bad_config = copy.deepcopy(config) + bad_config["num_epochs"] = -1 + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + # Invalid values + bad_config["num_epochs"] = config["num_epochs"] + del bad_config["values"] + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + bad_config["values"] = {"a": "b"} + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + bad_config["values"] = [] + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + # Invalid drop epochs + bad_config["values"] = config["values"] + bad_config["milestones"] = {"a": "b"} + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + # Too many + bad_config["milestones"] = [3, 6, 8, 12] + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + # Too few + bad_config["milestones"] = [3, 6] + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + # Exceeds num_epochs + bad_config["milestones"] = [3, 6, 12] + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + # Out of order + bad_config["milestones"] = [3, 8, 6] + with self.assertRaises(AssertionError): + MultiStepParamScheduler.from_config(bad_config) + + def _test_config_scheduler(self, config, expected_schedule): + scheduler = MultiStepParamScheduler.from_config(config) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + self.assertEqual(schedule, expected_schedule) + + def test_scheduler(self): + config = self._get_valid_config() + expected_schedule = [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.01, + 0.01, + 0.001, + 0.001, + 0.0001, + 0.0001, + 0.0001, + 0.0001, + ] + self._test_config_scheduler(config, expected_schedule) + + def test_default_config(self): + config = self._get_valid_config() + default_config = copy.deepcopy(config) + # Default equispaced drop_epochs behavior + del default_config["milestones"] + expected_schedule = [ + 0.1, + 0.1, + 0.1, + 0.01, + 0.01, + 0.01, + 0.001, + 0.001, + 0.001, + 0.0001, + 0.0001, + 0.0001, + ] + self._test_config_scheduler(default_config, expected_schedule) + + def test_build_non_equi_step_scheduler(self): + config = self._get_valid_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, MultiStepParamScheduler)) diff --git a/test/optim_param_scheduler_polynomial_test.py b/test/optim_param_scheduler_polynomial_test.py new file mode 100644 index 0000000000..70e8971334 --- /dev/null +++ b/test/optim_param_scheduler_polynomial_test.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.polynomial_decay_scheduler import ( + PolynomialDecayParamScheduler, +) + + +class TestPolynomialScheduler(unittest.TestCase): + _num_epochs = 10 + + def _get_valid_config(self): + return { + "name": "polynomial", + "num_epochs": self._num_epochs, + "base_lr": 0.1, + "power": 1, + } + + def test_invalid_config(self): + # Invalid num epochs + config = self._get_valid_config() + + # Invalid Base lr + bad_config = copy.deepcopy(config) + del bad_config["base_lr"] + with self.assertRaises(AssertionError): + PolynomialDecayParamScheduler.from_config(bad_config) + + # Invalid Power + bad_config = copy.deepcopy(config) + del bad_config["power"] + with self.assertRaises(AssertionError): + PolynomialDecayParamScheduler.from_config(bad_config) + + def test_scheduler(self): + config = self._get_valid_config() + + scheduler = PolynomialDecayParamScheduler.from_config(config) + schedule = [ + round(scheduler(epoch_num / self._num_epochs), 2) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [0.1, 0.09, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.02, 0.01] + + self.assertEqual(schedule, expected_schedule) + + def test_build_polynomial_scheduler(self): + config = self._get_valid_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, PolynomialDecayParamScheduler)) diff --git a/test/optim_param_scheduler_step_test.py b/test/optim_param_scheduler_step_test.py new file mode 100644 index 0000000000..26a342206f --- /dev/null +++ b/test/optim_param_scheduler_step_test.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.step_scheduler import StepParamScheduler + + +class TestStepScheduler(unittest.TestCase): + _num_epochs = 12 + + def _get_valid_config(self): + return { + "name": "step", + "num_epochs": self._num_epochs, + "values": [0.1, 0.01, 0.001, 0.0001], + } + + def test_invalid_config(self): + # Invalid num epochs + config = self._get_valid_config() + + bad_config = copy.deepcopy(config) + bad_config["num_epochs"] = -1 + with self.assertRaises(AssertionError): + StepParamScheduler.from_config(bad_config) + + # Invalid Values + bad_config["num_epochs"] = config["num_epochs"] + del bad_config["values"] + with self.assertRaises(AssertionError): + StepParamScheduler.from_config(bad_config) + + bad_config["values"] = {"a": "b"} + with self.assertRaises(AssertionError): + StepParamScheduler.from_config(bad_config) + + bad_config["values"] = [] + with self.assertRaises(AssertionError): + StepParamScheduler.from_config(bad_config) + + def test_scheduler(self): + config = self._get_valid_config() + + scheduler = StepParamScheduler.from_config(config) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [ + 0.1, + 0.1, + 0.1, + 0.01, + 0.01, + 0.01, + 0.001, + 0.001, + 0.001, + 0.0001, + 0.0001, + 0.0001, + ] + + self.assertEqual(schedule, expected_schedule) + + def test_build_step_scheduler(self): + config = self._get_valid_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, StepParamScheduler)) diff --git a/test/optim_param_scheduler_step_with_fixed_gamma_test.py b/test/optim_param_scheduler_step_with_fixed_gamma_test.py new file mode 100644 index 0000000000..41eb44681e --- /dev/null +++ b/test/optim_param_scheduler_step_with_fixed_gamma_test.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest + +from classy_vision.optim.param_scheduler import build_param_scheduler +from classy_vision.optim.param_scheduler.step_with_fixed_gamma_scheduler import ( + StepWithFixedGammaParamScheduler, +) + + +class TestStepWithFixedGammaScheduler(unittest.TestCase): + _num_epochs = 12 + + def _get_valid_config(self): + return { + "name": "step_with_fixed_gamma", + "base_lr": 1, + "gamma": 0.1, + "num_decays": 3, + "num_epochs": self._num_epochs, + } + + def test_invalid_config(self): + config = self._get_valid_config() + + # Invalid num epochs + bad_config = copy.deepcopy(config) + bad_config["num_epochs"] = -1 + with self.assertRaises(AssertionError): + StepWithFixedGammaParamScheduler.from_config(bad_config) + + # Invalid num_decays + bad_config["num_decays"] = 0 + with self.assertRaises(AssertionError): + StepWithFixedGammaParamScheduler.from_config(bad_config) + + # Invalid base_lr + bad_config = copy.deepcopy(config) + bad_config["base_lr"] = -0.01 + with self.assertRaises(AssertionError): + StepWithFixedGammaParamScheduler.from_config(bad_config) + + # Invalid gamma + bad_config = copy.deepcopy(config) + bad_config["gamma"] = [2] + with self.assertRaises(AssertionError): + StepWithFixedGammaParamScheduler.from_config(bad_config) + + def test_scheduler(self): + config = self._get_valid_config() + + scheduler = StepWithFixedGammaParamScheduler.from_config(config) + schedule = [ + scheduler(epoch_num / self._num_epochs) + for epoch_num in range(self._num_epochs) + ] + expected_schedule = [ + 1, + 1, + 1, + 0.1, + 0.1, + 0.1, + 0.01, + 0.01, + 0.01, + 0.001, + 0.001, + 0.001, + ] + + for param, expected_param in zip(schedule, expected_schedule): + self.assertAlmostEqual(param, expected_param) + + def test_build_step_with_fixed_gamma_scheduler(self): + config = self._get_valid_config() + scheduler = build_param_scheduler(config) + self.assertTrue(isinstance(scheduler, StepWithFixedGammaParamScheduler)) diff --git a/test/optim_param_scheduler_test.py b/test/optim_param_scheduler_test.py new file mode 100644 index 0000000000..26f6cca605 --- /dev/null +++ b/test/optim_param_scheduler_test.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +from unittest.mock import Mock + +from classy_vision.dataset import build_dataset +from classy_vision.hooks import ClassyHook +from classy_vision.losses import build_loss +from classy_vision.models import build_model +from classy_vision.optim import build_optimizer +from classy_vision.optim.param_scheduler import UpdateInterval +from classy_vision.tasks import ClassificationTask, ClassyTask +from classy_vision.trainer import LocalTrainer + + +class TestParamSchedulerIntegration(unittest.TestCase): + def _get_config(self): + return { + "loss": {"name": "CrossEntropyLoss"}, + "dataset": { + "train": { + "name": "synthetic_image", + "split": "train", + "num_classes": 2, + "crop_size": 20, + "class_ratio": 0.5, + "num_samples": 10, + "seed": 0, + "batchsize_per_replica": 5, + "use_shuffle": True, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + "test": { + "name": "synthetic_image", + "split": "test", + "num_classes": 2, + "crop_size": 20, + "class_ratio": 0.5, + "num_samples": 10, + "seed": 0, + "batchsize_per_replica": 5, + "use_shuffle": False, + "transforms": [ + { + "name": "apply_transform_to_key", + "transforms": [ + {"name": "ToTensor"}, + { + "name": "Normalize", + "mean": [0.485, 0.456, 0.406], + "std": [0.229, 0.224, 0.225], + }, + ], + "key": "input", + } + ], + }, + }, + "model": { + "name": "mlp", + # 3x20x20 = 1200 + "input_dim": 1200, + "output_dim": 1000, + "hidden_dims": [10], + }, + "meters": {"accuracy": {"topk": [1]}}, + "optimizer": { + "name": "sgd", + "num_epochs": 10, + "lr": 0.1, + "weight_decay": 1e-4, + "momentum": 0.9, + }, + } + + def _build_task(self, num_epochs): + config = self._get_config() + config["optimizer"]["num_epochs"] = num_epochs + task = ( + ClassificationTask() + .set_num_epochs(num_epochs) + .set_loss(build_loss(config["loss"])) + .set_model(build_model(config["model"])) + .set_optimizer(build_optimizer(config["optimizer"])) + ) + for phase_type in ["train", "test"]: + dataset = build_dataset(config["dataset"][phase_type]) + task.set_dataset(dataset, phase_type) + + self.assertTrue(task is not None) + return task + + def test_param_scheduler_epoch(self): + task = self._build_task(num_epochs=3) + + where_list = [] + + def scheduler_mock(where): + where_list.append(where) + return 0.1 + + mock = Mock(side_effect=scheduler_mock) + mock.update_interval = UpdateInterval.EPOCH + task.optimizer.lr_scheduler = mock + + trainer = LocalTrainer() + trainer.train(task) + + self.assertEqual(where_list, [0, 1 / 3, 2 / 3]) + + def test_param_scheduler_step(self): + task = self._build_task(num_epochs=3) + + where_list = [] + + def scheduler_mock(where): + where_list.append(where) + return 0.1 + + mock = Mock(side_effect=scheduler_mock) + mock.update_interval = UpdateInterval.STEP + task.optimizer.lr_scheduler = mock + + trainer = LocalTrainer() + trainer.train(task) + + # We have 10 samples, batch size is 5. Each epoch is done in two steps. + self.assertEqual(where_list, [0, 1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6]) + + def test_hook(self): + task = self._build_task(num_epochs=3) + + lr_list = [] + + class TestHook(ClassyHook): + on_rendezvous = ClassyHook._noop + on_start = ClassyHook._noop + on_phase_start = ClassyHook._noop + on_sample = ClassyHook._noop + on_forward = ClassyHook._noop + on_loss_and_meter = ClassyHook._noop + on_backward = ClassyHook._noop + on_phase_end = ClassyHook._noop + on_end = ClassyHook._noop + + def on_update(self, task: ClassyTask, local_variables) -> None: + lr_list.append(task.optimizer.lr) + + task.set_hooks([TestHook()]) + + def scheduler_mock(where): + return where + + mock = Mock(side_effect=scheduler_mock) + mock.update_interval = UpdateInterval.STEP + task.optimizer.lr_scheduler = mock + + trainer = LocalTrainer() + trainer.train(task) + + # We have 10 samples, batch size is 5. Each epoch is done in two steps. + self.assertEqual(lr_list, [0, 1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6]) diff --git a/test/optim_rmsprop_test.py b/test/optim_rmsprop_test.py new file mode 100644 index 0000000000..f2735617d8 --- /dev/null +++ b/test/optim_rmsprop_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +from test.generic.optim_test_util import TestOptimizer + +from classy_vision.optim.rmsprop import RMSProp + + +class TestRMSPropOptimizer(TestOptimizer, unittest.TestCase): + def _get_config(self): + return { + "name": "rmsprop", + "num_epochs": 90, + "lr": 0.1, + "momentum": 0.9, + "weight_decay": 0.0001, + "alpha": 0.9, + "eps": 1e-8, + "centered": False, + } + + def _instance_to_test(self): + return RMSProp diff --git a/test/optim_sgd_test.py b/test/optim_sgd_test.py new file mode 100644 index 0000000000..b6147acc53 --- /dev/null +++ b/test/optim_sgd_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +from test.generic.optim_test_util import TestOptimizer + +from classy_vision.optim.sgd import SGD + + +class TestSGDOptimizer(TestOptimizer, unittest.TestCase): + def _get_config(self): + return { + "name": "sgd", + "num_epochs": 90, + "lr": 0.1, + "momentum": 0.9, + "weight_decay": 0.0001, + "nesterov": False, + } + + def _instance_to_test(self): + return SGD diff --git a/test/suites.py b/test/suites.py new file mode 100644 index 0000000000..8834f2d797 --- /dev/null +++ b/test/suites.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +"""Various test loaders.""" + +import os +import random +import unittest +from itertools import chain + + +def _circleci_parallelism(suite): + """Allow for parallelism in CircleCI for speedier tests..""" + if int(os.environ.get("CIRCLE_NODE_TOTAL", 0)) <= 1: + # either not running on circleci, or we're not using parallelism. + return suite + # tests are automatically sorted by discover, so we will get the same ordering + # on all hosts. + total = int(os.environ["CIRCLE_NODE_TOTAL"]) + index = int(os.environ["CIRCLE_NODE_INDEX"]) + + # right now each test is corresponds to a /file/. Certain files are slower than + # others, so we want to flatten it + tests = [testfile._tests for testfile in suite._tests] + tests = list(chain.from_iterable(tests)) + random.Random(42).shuffle(tests) + tests = [t for i, t in enumerate(tests) if i % total == index] + return unittest.TestSuite(tests) + + +def unittests(): + """ + Short tests. + + Runs on CircleCI on every commit. Returns everything in the tests root directory. + """ + test_loader = unittest.TestLoader() + test_suite = test_loader.discover("test", pattern="*_test.py") + test_suite = _circleci_parallelism(test_suite) + return test_suite + + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + runner.run(unittests()) diff --git a/test/tasks_classification_task_test.py b/test/tasks_classification_task_test.py new file mode 100644 index 0000000000..6338a11282 --- /dev/null +++ b/test/tasks_classification_task_test.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +from test.generic.config_utils import get_fast_test_task_config, get_test_task_config +from test.generic.utils import compare_model_state, compare_samples, compare_states + +import torch +from classy_vision.dataset import build_dataset +from classy_vision.hooks import LossLrMeterLoggingHook +from classy_vision.losses import build_loss +from classy_vision.models import build_model +from classy_vision.optim import build_optimizer +from classy_vision.tasks import ClassificationTask, build_task +from classy_vision.trainer import LocalTrainer + + +class TestClassificationTask(unittest.TestCase): + def _compare_model_state(self, model_state_1, model_state_2, check_heads=True): + compare_model_state(self, model_state_1, model_state_2, check_heads) + + def _compare_samples(self, sample_1, sample_2): + compare_samples(self, sample_1, sample_2) + + def _compare_states(self, state_1, state_2, check_heads=True): + compare_states(self, state_1, state_2) + + def test_build_task(self): + config = get_test_task_config() + task = build_task(config) + self.assertTrue(isinstance(task, ClassificationTask)) + + def test_get_state(self): + config = get_test_task_config() + loss = build_loss(config["loss"]) + task = ( + ClassificationTask() + .set_num_epochs(1) + .set_loss(loss) + .set_model(build_model(config["model"])) + .set_optimizer(build_optimizer(config["optimizer"])) + ) + for phase_type in ["train", "test"]: + dataset = build_dataset(config["dataset"][phase_type]) + task.set_dataset(dataset, phase_type) + + task.prepare(num_dataloader_workers=1, pin_memory=False) + + task = build_task(config) + task.prepare(num_dataloader_workers=1, pin_memory=False) + + def test_get_set_state(self): + """ + Tests the {set, get}_classy_state methods by running train_steps + to make sure the train_steps run the same way. + """ + config = get_fast_test_task_config() + task = build_task(config).set_hooks([LossLrMeterLoggingHook()]) + task_2 = build_task(config).set_hooks([LossLrMeterLoggingHook()]) + + use_gpu = torch.cuda.is_available() + local_variables = {} + + # prepare the tasks for the right device + task.prepare(use_gpu=use_gpu) + task_2.prepare(use_gpu=use_gpu) + + # test in both train and test mode + for _ in range(2): + task.advance_phase() + + # task 2 should have the same state + task_2.set_classy_state(task.get_classy_state(deep_copy=True)) + self._compare_states(task.get_classy_state(), task_2.get_classy_state()) + + # this tests that both states' iterators return the same samples + sample = next(task.get_data_iterator()) + sample_2 = next(task_2.get_data_iterator()) + self._compare_samples(sample, sample_2) + + # test that the train step runs the same way on both states + # and the loss remains the same + task.train_step(use_gpu, local_variables) + task_2.train_step(use_gpu, local_variables) + self._compare_states(task.get_classy_state(), task_2.get_classy_state()) + + @unittest.skipUnless(torch.cuda.is_available(), "This test needs a gpu to run") + def test_get_set_state_different_devices(self): + config = get_fast_test_task_config() + task = build_task(config) + task_2 = build_task(config) + + for use_gpu in [True, False]: + task.prepare(use_gpu=use_gpu) + task_2.prepare(use_gpu=not use_gpu) + + task_2.set_classy_state(task.get_classy_state(deep_copy=True)) + + # the parameters are in different devices + with self.assertRaises(Exception): + self._compare_states(task.get_classy_state(), task_2.get_classy_state()) + + # prepare the task for the right device + task_2.prepare(use_gpu=use_gpu) + self._compare_states(task.get_classy_state(), task_2.get_classy_state()) + + # we should be able to run the trainer using state from a different device + trainer = LocalTrainer(use_gpu=use_gpu) + trainer.train(task_2) diff --git a/test/tasks_fine_tuning_task_test.py b/test/tasks_fine_tuning_task_test.py new file mode 100644 index 0000000000..e0c4031ad2 --- /dev/null +++ b/test/tasks_fine_tuning_task_test.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import unittest +from test.generic.config_utils import get_fast_test_task_config +from test.generic.utils import compare_model_state + +from classy_vision.generic.util import get_checkpoint_dict +from classy_vision.tasks import FineTuningTask, build_task +from classy_vision.trainer import LocalTrainer + + +class TestFineTuningTask(unittest.TestCase): + def _compare_model_state(self, state_1, state_2, check_heads=True): + return compare_model_state(self, state_1, state_2, check_heads=check_heads) + + def _get_fine_tuning_config(self, head_num_classes=1000): + config = get_fast_test_task_config(head_num_classes=head_num_classes) + config["name"] = "fine_tuning" + config["num_epochs"] = 10 + return config + + def _get_pre_train_config(self, head_num_classes=1000): + config = get_fast_test_task_config(head_num_classes=head_num_classes) + config["num_epochs"] = 10 + return config + + def test_build_task(self): + config = self._get_fine_tuning_config() + task = build_task(config) + self.assertIsInstance(task, FineTuningTask) + + def test_prepare(self): + pre_train_config = self._get_pre_train_config() + pre_train_task = build_task(pre_train_config) + pre_train_task.prepare() + checkpoint = get_checkpoint_dict(pre_train_task, {}) + + fine_tuning_config = self._get_fine_tuning_config() + fine_tuning_task = build_task(fine_tuning_config) + # cannot prepare a fine tuning task without a pre training checkpoint + with self.assertRaises(Exception): + fine_tuning_task.prepare() + + fine_tuning_task.set_pretrained_checkpoint(checkpoint) + fine_tuning_task.prepare() + + # test a fine tuning task with incompatible heads + fine_tuning_config = self._get_fine_tuning_config(head_num_classes=10) + fine_tuning_task = build_task(fine_tuning_config) + fine_tuning_task.set_pretrained_checkpoint(checkpoint) + # cannot prepare a fine tuning task with a pre training checkpoint which + # has incompatible heads + with self.assertRaises(Exception): + fine_tuning_task.prepare() + + fine_tuning_task.set_pretrained_checkpoint(checkpoint).set_reset_heads(True) + fine_tuning_task.prepare() + + def test_train(self): + pre_train_config = self._get_pre_train_config(head_num_classes=1000) + pre_train_task = build_task(pre_train_config) + trainer = LocalTrainer() + trainer.train(pre_train_task) + checkpoint = get_checkpoint_dict(pre_train_task, {}) + + for reset_heads, heads_num_classes in [(False, 1000), (True, 200)]: + for freeze_trunk in [True, False]: + fine_tuning_config = self._get_fine_tuning_config( + head_num_classes=heads_num_classes + ) + fine_tuning_task = build_task(fine_tuning_config) + fine_tuning_task = ( + fine_tuning_task.set_pretrained_checkpoint( + copy.deepcopy(checkpoint) + ) + .set_reset_heads(reset_heads) + .set_freeze_trunk(freeze_trunk) + ) + # run in test mode to compare the model state + fine_tuning_task.set_test_only(True) + trainer.train(fine_tuning_task) + self._compare_model_state( + pre_train_task.model.get_classy_state(), + fine_tuning_task.model.get_classy_state(), + check_heads=not reset_heads, + ) + # run in train mode to check accuracy + fine_tuning_task.set_test_only(False) + trainer.train(fine_tuning_task) + if freeze_trunk: + # if trunk is frozen the states should be the same + self._compare_model_state( + pre_train_task.model.get_classy_state(), + fine_tuning_task.model.get_classy_state(), + check_heads=False, + ) + else: + # trunk isn't frozen, the states should be different + with self.assertRaises(Exception): + self._compare_model_state( + pre_train_task.model.get_classy_state(), + fine_tuning_task.model.get_classy_state(), + check_heads=False, + ) + + accuracy = fine_tuning_task.meters[0].value["top_1"] + self.assertAlmostEqual(accuracy, 1.0) diff --git a/test/trainer_distributed_trainer_test.py b/test/trainer_distributed_trainer_test.py new file mode 100644 index 0000000000..d1966ab2bc --- /dev/null +++ b/test/trainer_distributed_trainer_test.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import copy +import json +import os +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path +from test.generic.config_utils import get_test_mlp_task_config + +import torch + + +class TestDistributedTrainer(unittest.TestCase): + def setUp(self): + config = get_test_mlp_task_config() + invalid_config = copy.deepcopy(config) + invalid_config["name"] = "invalid_task" + self.config_files = {} + for config_key, config in [ + ("config", config), + ("invalid_config", invalid_config), + ]: + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: + json.dump(config, f) + f.flush() + self.config_files[config_key] = f.name + self.path = Path(__file__).parent.absolute() + + def tearDown(self): + for config_file in self.config_files.values(): + os.unlink(config_file) + + def test_training(self): + """Checks we can train a small MLP model.""" + + num_processes = 2 + device = "gpu" if torch.cuda.is_available() else "cpu" + + for config_key, expected_success in [ + ("invalid_config", False), + ("config", True), + ]: + cmd = f"""{sys.executable} -m torch.distributed.launch \ + --nnodes=1 \ + --nproc_per_node={num_processes} \ + --master_addr=127.0.0.1 \ + --master_port=29500 \ + --use_env \ + {self.path}/../classy_train.py \ + --device={device} \ + --config={self.config_files[config_key]} \ + --num_workers=4 \ + --log_freq=100 + """ + result = subprocess.run(cmd, shell=True) + success = result.returncode == 0 + self.assertEqual(success, expected_success) diff --git a/test/trainer_local_trainer_test.py b/test/trainer_local_trainer_test.py new file mode 100644 index 0000000000..6b6e313c28 --- /dev/null +++ b/test/trainer_local_trainer_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import unittest +from test.generic.config_utils import get_test_mlp_task_config + +from classy_vision.dataset import build_dataset +from classy_vision.hooks import LossLrMeterLoggingHook +from classy_vision.losses import build_loss +from classy_vision.meters import AccuracyMeter +from classy_vision.models import build_model +from classy_vision.optim import build_optimizer +from classy_vision.tasks import ClassificationTask +from classy_vision.trainer import LocalTrainer + + +class TestLocalTrainer(unittest.TestCase): + def test_training(self): + """Checks we can train a small MLP model.""" + config = get_test_mlp_task_config() + task = ( + ClassificationTask() + .set_num_epochs(10) + .set_loss(build_loss(config["loss"])) + .set_model(build_model(config["model"])) + .set_optimizer(build_optimizer(config["optimizer"])) + .set_meters([AccuracyMeter(topk=[1])]) + .set_hooks([LossLrMeterLoggingHook()]) + ) + for split in ["train", "test"]: + dataset = build_dataset(config["dataset"][split]) + task.set_dataset(dataset, split) + + self.assertTrue(task is not None) + + trainer = LocalTrainer() + trainer.train(task) + accuracy = task.meters[0].value["top_1"] + self.assertAlmostEqual(accuracy, 1.0) diff --git a/tutorials/classy_dataset.ipynb b/tutorials/classy_dataset.ipynb new file mode 100644 index 0000000000..69e50e6116 --- /dev/null +++ b/tutorials/classy_dataset.ipynb @@ -0,0 +1,615 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "# Creating a custom dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial we will learn how to do the following: \n", + "\n", + "1. Create a custom dataset within Classy Vision\n", + "2. Integrate a dataset with Classy Vision's configuration system\n", + "3. Iterate over the samples contained in a dataset\n", + "4. Using transforms with Classy Vision\n", + "5. Create a ImageNet dataset, using standard transforms, using torchvision\n", + "\n", + "If you haven't already read our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial, we recommend starting there before reading this tutorial." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Create a custom dataset within Classy Vision\n", + "\n", + "Creating a dataset for use / using an existing dataset in Classy Vision is as easy as it is in PyTorch, it only requires wrapping the dataset in our dataloading class, ClassyDataset.\n", + "\n", + "First, specify a dataset with a `__getitem__` and `__len__` function, the same as required by torch.utils.data.Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch.utils.data\n", + "import torch\n", + "\n", + "class MyDataset(torch.utils.data.Dataset):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.length = 100\n", + " \n", + " def __getitem__(self, idx):\n", + " assert idx >= 0 and idx < self.length, \\\n", + " \"Provided index {} must be in range [0, {}).\".format(idx, self.length)\n", + " return torch.rand(3, 100, 100)\n", + " \n", + " def __len__(self):\n", + " return self.length" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now for most training tasks we want to be able to configure the batchsize on the fly, transform samples, shuffle the dataset, maybe limit the number of samples to shorten a training run, and then construct an iterator for the training loop. ClassyDataset is a simple wrapper that provides this functionality. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.dataset import ClassyDataset\n", + "\n", + "class MyClassyDataset(ClassyDataset):\n", + " def __init__(self, split, batchsize_per_replica, shuffle, transform, num_samples):\n", + " dataset = MyDataset()\n", + " super().__init__(dataset, split, batchsize_per_replica, shuffle, transform, num_samples)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's that easy! Later in the tutorial we will see how to use the iterator, but before moving on, let's talk about what each of these arguments does.\n", + "- __split__: is a string specifying the split of the data, typically either \"train\" or \"test\". This is optional, not needed for many datasets.\n", + "- __batchsize_per_replica__: the batchsize per trainer (so if you have 8 GPUs with 1 trainer processes and a batchsize_per_replica of 32, then your batchsize for single update is 8 * 32 = 256).\n", + "- __shuffle__: If true, then shuffle the dataset before each epoch.\n", + "- __transform__: A callable applied to each sample before returning. Note that this can get tricky since many datasets (e.g. torchvision datasets) return complex samples containing both the image / video content and a label and possibly additional metadata. We pass the _whole_ sample to the transform, so it needs to know how to parse the sample...more on this later.\n", + "- __num_samples__: Not needed in the standard use cases, but this allows a user to adjust the length of samples retrieved in an epoch, can be convenient for debugging via config (e.g. setting num_samples = 10 will speed up training). By default this is set to None and iteration proceeds over the whole dataset.\n", + "\n", + "To get started with a basic task just do:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.tasks import ClassificationTask\n", + "\n", + "my_dataset = MyClassyDataset(\n", + " split=\"train\", \n", + " batchsize_per_replica=10, \n", + " shuffle=True, \n", + " transform=None, \n", + " num_samples=None,\n", + ")\n", + "\n", + "# Note, the \"train\" here is the phase type, which is unrelated to the split name.\n", + "# It tells the task to set the model in train mode / do a backwards pass, etc using\n", + "# this dataset...the split argument helps the dataset decide which training data to load.\n", + "my_task = ClassificationTask().set_dataset(my_dataset, \"train\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For more details on training a model, please see our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Integrating a dataset with Classy Vision's configuration system\n", + "\n", + "Classy Vision is also able to read a configuration file and instantiate the dataset. This is useful to keep your experiments organized and reproducible. For that, you have to:\n", + "\n", + "- Implement a from_config method\n", + "- Add the register_model decorator to MyClassyDataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.dataset import ClassyDataset, register_dataset\n", + "from classy_vision.dataset.transforms import build_transforms\n", + "\n", + "@register_dataset(\"my_dataset\")\n", + "class MyClassyDataset(ClassyDataset):\n", + " def __init__(self, split, batchsize_per_replica, shuffle, transform, num_samples):\n", + " dataset = MyDataset()\n", + " super().__init__(dataset, split, batchsize_per_replica, shuffle, transform, num_samples)\n", + " \n", + " @classmethod\n", + " def from_config(cls, config):\n", + " transform = build_transforms(config[\"transforms\"])\n", + " return cls(\n", + " split=config[\"split\"],\n", + " batchsize_per_replica=config[\"batchsize_per_replica\"],\n", + " shuffle=config[\"shuffle\"],\n", + " transform=transform,\n", + " num_samples=config[\"num_samples\"],\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can start using this dataset in our configurations. The string argument passed to the register_dataset is a unique identifier for this model (if you try to register two models with the same name, it will throw an error):\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.dataset import build_dataset\n", + "import torch\n", + "\n", + "dataset_config = {\n", + " \"name\": \"my_dataset\",\n", + " \"split\": \"train\",\n", + " \"batchsize_per_replica\": 10,\n", + " \"shuffle\": True,\n", + " \"transforms\": [{\"name\": \"Normalize\", \"mean\": [0.485, 0.456, 0.406], \"std\": [0.229, 0.224, 0.225]}],\n", + " \"num_samples\": None,\n", + "}\n", + "my_dataset = build_dataset(dataset_config)\n", + "assert isinstance(my_dataset, MyClassyDataset)\n", + "\n", + "sample = my_dataset[0]\n", + "print(sample.size())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Iterate over the samples contained in a dataset\n", + "\n", + "As mentioned above, the ClassyDataset class adds several pieces of basic logic for constructing a torch.utils.data.Dataloader for your dataset. ClassyDataset supports local and distributed training out-of-box by internally using a PyTorch DistributedSampler for sampling the dataset along with the PyTorch Dataloader for batching and parallelizing sample retrieval. To get an iterable for epoch 0, do the following:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.dataset import build_dataset\n", + "import torch\n", + "\n", + "dataset_config = {\n", + " \"name\": \"my_dataset\",\n", + " \"split\": \"train\",\n", + " \"batchsize_per_replica\": 10,\n", + " \"shuffle\": True,\n", + " \"transforms\": [],\n", + " \"num_samples\": None,\n", + "}\n", + "my_dataset = build_dataset(dataset_config)\n", + "assert isinstance(my_dataset, MyClassyDataset)\n", + "\n", + "# multiprocessing_context can be set to \"spawn\", \"forkserver\", \"fork\" or None.\n", + "# If None is used, then the dataloader inherits the context of the parent thread.\n", + "# If num_workers is 0, then multiprocessing is not used by the dataloader\n", + "#\n", + "# A warning, while fork is fast and simple to get started with, it \n", + "# is unsafe to use with threading and can lead to difficult to debug errors.\n", + "# Spawn / forkserver are threadsafe, but they come with additional startup costs.\n", + "iterator = my_dataset.iterator(\n", + " shuffle_seed=0,\n", + " epoch=0,\n", + " num_workers=0, # 0 indicates to do dataloading on the master process\n", + " pin_memory=False,\n", + " multiprocessing_context=None,\n", + ")\n", + "assert isinstance(iterator, torch.utils.data.DataLoader)\n", + "\n", + "# Iterate over all 100 samples.\n", + "for sample in iter(iterator):\n", + " # Do stuff with sample...\n", + " # Note that size now has an extra dimension representing the batchsize\n", + " assert sample.size() == torch.Size([10, 3, 100, 100])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also provide a custom iterator function if you would like to return a custom iterator or a custom sampler. Please see the ClassyDataset code for more details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Using transforms with Classy Vision\n", + "\n", + "You may have noticed in the configuration section that we did something slightly more complicated with the transform configuration. In particular, just like our datasets / models etc, we have a registration / build mechanism for transforms so that transforms can be specified via config. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Transform example using Classy Vision's synthetic image dataset\n", + "We also automatically register torchvision transforms, so let's start with an example of how to specify torchvision transforms and the synthetic image dataset we provide for testing / proto-typing.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torchvision.transforms as transforms\n", + "from classy_vision.dataset import build_dataset\n", + "from classy_vision.dataset.classy_synthetic_image import SyntheticImageDataset\n", + "from classy_vision.dataset.transforms import build_transforms\n", + "\n", + "# Declarative approach\n", + "\n", + "# Transform to be applied to image\n", + "image_transform = transforms.Compose([\n", + " transforms.Resize(256),\n", + " transforms.CenterCrop(224),\n", + " transforms.ToTensor(),\n", + " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n", + "])\n", + "\n", + "decl_dataset = SyntheticImageDataset(\n", + " batchsize_per_replica=10,\n", + " shuffle=True,\n", + " transform=image_transform,\n", + " num_samples=100,\n", + " crop_size=320,\n", + " class_ratio=4,\n", + " seed=0,\n", + ")\n", + "\n", + "# FAILS!!!!\n", + "# decl_dataset[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This fails! Why?\n", + "\n", + "It fails because most datasets don't return just an image, they return image or video content data, label data, and (potentially) sample metadata. In Classy Vision, the sample format is specified by the task and our classification_task expects a dict with input / target keys.\n", + "\n", + "For example, the sample format for the SyntheticImageDataset looks like:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`{\"input\": , \"target\": }`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For our transforms to work, we need to specify which key to apply the transform to." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torchvision.transforms as transforms\n", + "from classy_vision.dataset import build_dataset\n", + "from classy_vision.dataset.classy_synthetic_image import SyntheticImageDataset\n", + "from classy_vision.dataset.transforms import build_transforms, ApplyTransformToKey\n", + "\n", + "# Declarative approach\n", + "\n", + "# Transform to be applied to image\n", + "image_transform = transforms.Compose([\n", + " transforms.Resize(256),\n", + " transforms.CenterCrop(224),\n", + " transforms.ToTensor(),\n", + " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n", + "])\n", + "\n", + "# Transform wrapper that says which key to apply the transform to\n", + "transform = ApplyTransformToKey(\n", + " transform=image_transform,\n", + " key=\"input\",\n", + ")\n", + "\n", + "decl_dataset = SyntheticImageDataset(\n", + " batchsize_per_replica=10,\n", + " shuffle=True,\n", + " transform=transform,\n", + " num_samples=100,\n", + " crop_size=320,\n", + " class_ratio=4,\n", + " seed=0,\n", + ")\n", + "\n", + "# Success!!!!\n", + "decl_dataset[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's see how to do the same thing via a config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Note that this cell won't work until we fix the synthetic dataset from_config function\n", + "\n", + "from classy_vision.dataset import build_dataset\n", + "\n", + "# Configuration approach\n", + "config = {\n", + " \"name\": \"synthetic_image\",\n", + " \"batchsize_per_replica\": 10,\n", + " \"use_shuffle\": True,\n", + " \"transforms\": [\n", + " {\n", + " \"name\": \"apply_transform_to_key\",\n", + " \"transforms\": [\n", + " {\"name\": \"Resize\", \"size\": 256},\n", + " {\"name\": \"CenterCrop\", \"size\": 224},\n", + " {\"name\": \"ToTensor\"},\n", + " {\"name\": \"Normalize\", \"mean\": [0.485, 0.456, 0.406], \"std\": [0.229, 0.224, 0.225]},\n", + " ],\n", + " \"key\": \"input\",\n", + " },\n", + " ],\n", + " \"num_samples\": 100,\n", + " \"crop_size\": 320,\n", + " \"class_ratio\": 4,\n", + " \"seed\": 0\n", + "}\n", + "\n", + "config_dataset = build_dataset(config)\n", + "\n", + "# Sample should be the same as that provided by the decl_dataset\n", + "assert torch.allclose(config_dataset[0][\"input\"], decl_dataset[0][\"input\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Transform example for a torchvision dataset\n", + "Torchvision has a different sample format using tuples for images: \n", + "\n", + "`(, )`\n", + "\n", + "The ApplyTransformToKey will still work (the key in this case is '0'), but for our classification tasks, we also want a sample that is a dict with \"input\"/\"target\" keys. \n", + "\n", + "Because this is a common dataset format, we provide a convenience transform called \"GenericImageTransform\" which applies a specified transform to the torchvision tuple image key and then maps the whole sample to a dict. This is just a convenience transform, we can also do this using raw composable blocks, but it makes things more verbose.\n", + "\n", + "All of the transforms in the next cell have the same effect on an image:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from torchvision.transforms import Compose\n", + "from classy_vision.dataset.transforms import build_transforms\n", + "from classy_vision.dataset.transforms.util import GenericImageTransform\n", + "\n", + "# Declarative\n", + "image_transform = transforms.Compose([\n", + " transforms.Resize(256),\n", + " transforms.CenterCrop(224),\n", + " transforms.ToTensor(),\n", + " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n", + "])\n", + "decl_transform = GenericImageTransform(transform=image_transform)\n", + "\n", + "# Configuration with helper function\n", + "transform_config = [{\n", + " \"name\": \"generic_image_transform\",\n", + " \"transforms\": [\n", + " {\"name\": \"Resize\", \"size\": 256},\n", + " {\"name\": \"CenterCrop\", \"size\": 224},\n", + " {\"name\": \"ToTensor\"},\n", + " {\"name\": \"Normalize\", \"mean\": [0.485, 0.456, 0.406], \"std\": [0.229, 0.224, 0.225]},\n", + " ], \n", + "}]\n", + "config_helper_transform = build_transforms(transform_config)\n", + "\n", + "# Configuration using raw, composable functions:\n", + "transform_config = [\n", + " {\"name\": \"tuple_to_map\", \"list_of_map_keys\": [\"input\", \"target\"]},\n", + " {\n", + " \"name\": \"apply_transform_to_key\",\n", + " \"transforms\": [\n", + " {\"name\": \"Resize\", \"size\": 256},\n", + " {\"name\": \"CenterCrop\", \"size\": 224},\n", + " {\"name\": \"ToTensor\"},\n", + " {\"name\": \"Normalize\", \"mean\": [0.485, 0.456, 0.406], \"std\": [0.229, 0.224, 0.225]},\n", + " ], \n", + " \"key\": \"input\",\n", + " },\n", + "]\n", + "config_raw_transform = build_transforms(transform_config)\n", + "\n", + "# These transforms are all functionally the same" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Create a Classy Imagenet\n", + "\n", + "Now, to complete this tutorial, we show our code for creating an ImageNet dataset in classy vision using the pre-existing torchvision dataset. Code very similar to this (+ some typing and helper functions) is in the datasets folder of the base Classy Vision repository.\n", + "\n", + "Note, we do not distribute any of the underlying dataset data with Classy Vision. Before this will work, you will need to download a torchvision compatible copy of the Imagenet dataset yourself." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.dataset import ClassyDataset, register_dataset\n", + "from classy_vision.dataset.transforms import ClassyTransform, build_transforms\n", + "from torchvision.datasets.imagenet import ImageNet\n", + " \n", + " \n", + "@register_dataset(\"example_imagenet\")\n", + "class ExampleImageNetDataset(ClassyDataset):\n", + " def __init__(\n", + " self,\n", + " split,\n", + " batchsize_per_replica,\n", + " shuffle,\n", + " transform,\n", + " num_samples,\n", + " root, # Root directory for your Imagenet dataset\n", + " ): \n", + " # Create torchvision dataset\n", + " dataset = ImageNet(root=root, split=split)\n", + " super().__init__(\n", + " dataset, split, batchsize_per_replica, shuffle, transform, num_samples\n", + " ) \n", + "\n", + " @classmethod\n", + " def from_config(cls, config):\n", + " batchsize_per_replica = config.get(\"batchsize_per_replica\")\n", + " shuffle = config.get(\"use_shuffle\")\n", + " num_samples = config.get(\"num_samples\")\n", + " transform_config = config.get(\"transforms\")\n", + " split = config.get(\"split\")\n", + " root = config.get(\"root\")\n", + " download = config.get(\"download\")\n", + " \n", + " transform = build_transforms(transform_config)\n", + " return cls(\n", + " split=split,\n", + " batchsize_per_replica=batchsize_per_replica,\n", + " shuffle=shuffle,\n", + " transform=transform,\n", + " num_samples=num_samples,\n", + " root=root,\n", + " download=download,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "In this tutorial we have seen how to create a custom dataset using ClassyDataset, how to integrate this dataset with the configuration system, how to iterate over samples / use multiple workers, how to use transforms in the configuration system and finally we showed an example of how to use a torchvision dataset in Classy Vision. \n", + "\n", + "For more details on how to use the dataset for training, please see [Getting started](https://classyvision.ai/tutorials/getting_started)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "disseminate_notebook_id": { + "notebook_id": "1152520058270736" + }, + "disseminate_notebook_info": { + "bento_version": "20191118-000256", + "description": "Test of Classy Dataset tutorial\n\nv3", + "hide_code": false, + "hipster_group": "", + "kernel_build_info": { + "deps": [ + "//fblearner/flow/projects/vision/classy_vision:classy_vision_workflow_lib" + ], + "external_deps": [] + }, + "no_uii": true, + "notebook_number": "179685", + "others_can_edit": true, + "reviewers": "", + "revision_id": "833561470396445", + "tags": "classy_vision", + "tasks": "", + "title": "classy_dataset" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/classy_loss.ipynb b/tutorials/classy_loss.ipynb new file mode 100644 index 0000000000..db5695967a --- /dev/null +++ b/tutorials/classy_loss.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating a custom loss" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Loss functions are crucial because they define the objective to optimize for during training. Classy Vision can work directly with loss functions defined in [PyTorch](https://pytorch.org/docs/stable/_modules/torch/nn/modules/loss.html) without the need for any wrapper classes, but during research it's common to create custom losses with hyperparameters. Using `ClassyLoss` you can expose these hyperparameters via a configuration file.\n", + "\n", + "This tutorial will demonstrate: \n", + "1. How to create a custom loss within Classy Vision; \n", + "2. How to integrate your loss with Classy Vision's configuration system;\n", + "3. How to use a ClassyLoss independently, without other Classy Vision abstractions.\n", + "\n", + "## 1. Defining a loss\n", + "\n", + "Creating a new loss in Classy Vision is as simple as adding a new loss within PyTorch. The loss has to derive from `ClassyLoss` (which inherits from [`torch.nn.Module`](https://pytorch.org/docs/stable/nn.html#module)), and implement a `forward` method.\n", + "\n", + "> **Note**: The forward method should take the right arguments depending on the task the loss will be used for. For instance, a `ClassificationTask` passes the `output` and `target` to `forward`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.losses import ClassyLoss\n", + "\n", + "class MyLoss(ClassyLoss):\n", + " def __init__(self, alpha):\n", + " super().__init__()\n", + " self.alpha = alpha\n", + " \n", + " def forward(self, output, target):\n", + " return (output - target).pow(2) * self.alpha" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can start using this loss for training. Take a look at our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial for more details on how to train a model from a Jupyter notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.tasks import ClassificationTask\n", + "\n", + "my_loss = MyLoss(alpha=5)\n", + "my_task = ClassificationTask().set_loss(my_loss)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Integrate it with the configuration system\n", + "\n", + "To be able to use the registration mechanism to be able to pick up the loss from a configuration, we need to do two additional things -\n", + "- Implement a `from_config` method\n", + "- Add the `register_loss` decorator to `MyLoss`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.losses import ClassyLoss, register_loss\n", + "\n", + "@register_loss(\"my_loss\")\n", + "class MyLoss(ClassyLoss):\n", + " def __init__(self, alpha):\n", + " super().__init__()\n", + " self.alpha = alpha\n", + "\n", + " @classmethod\n", + " def from_config(cls, config):\n", + " if \"alpha\" not in config:\n", + " raise ValueError('Need \"alpha\" in config for MyLoss')\n", + " return cls(alpha=config[\"alpha\"])\n", + " \n", + " def forward(self, output, target):\n", + " return (output - target).pow(2).sum() * self.alpha" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can start using this loss in our configurations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.losses import build_loss\n", + "import torch\n", + "\n", + "loss_config = {\n", + " \"name\": \"my_loss\",\n", + " \"alpha\": 5\n", + "}\n", + "my_loss = build_loss(loss_config)\n", + "assert isinstance(my_loss, MyLoss)\n", + "\n", + "# ClassyLoss inherits from torch.nn.Module, so it works as expected\n", + "with torch.no_grad():\n", + " y_hat, target = torch.rand((1, 10)), torch.rand((1, 10))\n", + " print(my_loss(y_hat, target))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that your loss is integrated with the configuration system, you can train it using `classy_train.py` as described in the [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial, no further changes are needed! Just make sure the code defining your model is in the `losses` folder of your classy project." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Conclusion\n", + "\n", + "In this tutorial, we learned how to make your loss compatible with Classy Vision and how to integrate it with the configuration system. Refer to our documentation to learn more about [ClassyLoss](https://classyvision.ai/api/losses.html)." + ] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/classy_model.ipynb b/tutorials/classy_model.ipynb new file mode 100644 index 0000000000..bdef9bef82 --- /dev/null +++ b/tutorials/classy_model.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating a custom model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial will demonstrate: (1) how to create a custom model within Classy Vision; (2) how to integrate your model with Classy Vision's configuration system; (3) how to use the model for training and inference;\n", + "\n", + "## 1. Defining a model\n", + "\n", + "Creating a new model in Classy Vision is the simple as creating one within PyTorch. The model needs to derive from `ClassyModel` and implement a `forward` method to perform inference. `ClassyModel` inherits from [`torch.nn.Module`](https://pytorch.org/docs/stable/nn.html#module), so it works exactly as you would expect." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch.nn as nn\n", + "\n", + "from classy_vision.models import ClassyModel\n", + "\n", + "\n", + "class MyModel(ClassyModel):\n", + " def __init__(self, num_classes):\n", + " super().__init__()\n", + " \n", + " # Average all the pixels, generate one output per class\n", + " self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n", + " num_channels = 3\n", + " self.fc = nn.Linear(num_channels, num_classes)\n", + " \n", + " def forward(self, x):\n", + " # perform average pooling\n", + " out = self.avgpool(x)\n", + "\n", + " # reshape the output and apply the fc layer\n", + " out = out.reshape(out.size(0), -1)\n", + " out = self.fc(out)\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can start using this model for training. Take a look at our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial for more details on how to train a model from a Jupyter notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.tasks import ClassificationTask\n", + "\n", + "my_model = MyModel(num_classes=1000)\n", + "my_task = ClassificationTask().set_model(my_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Integrate it with the configuration system\n", + "\n", + "Classy Vision is also able to read a configuration file and instantiate the model. This is useful to keep your experiments organized and reproducible. For that, you have to:\n", + "\n", + "- Implement a `from_config` method\n", + "- Add the `register_model` decorator to `MyModel`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch.nn as nn\n", + "\n", + "from classy_vision.models import ClassyModel, register_model\n", + "\n", + "\n", + "@register_model(\"my_model\")\n", + "class MyModel(ClassyModel):\n", + " def __init__(self, num_classes):\n", + " super().__init__()\n", + " \n", + " # Average all the pixels, generate one output per class\n", + " self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n", + " num_channels = 3\n", + " self.fc = nn.Linear(num_channels, num_classes)\n", + "\n", + " @classmethod\n", + " def from_config(cls, config):\n", + " # This method takes a configuration dictionary \n", + " # and returns an instance of the class. In this case, \n", + " # we'll let the number of classes be configurable.\n", + " return cls(num_classes=config[\"num_classes\"])\n", + " \n", + " def forward(self, x):\n", + " # perform average pooling\n", + " out = self.avgpool(x)\n", + "\n", + " # reshape the output and apply the fc layer\n", + " out = out.reshape(out.size(0), -1)\n", + " out = self.fc(out)\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can start using this model in our configurations. The argument passed to `register_model` is used to identify the model class in the configuration:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.models import build_model\n", + "import torch\n", + "\n", + "model_config = {\n", + " \"name\": \"my_model\",\n", + " \"num_classes\": 3\n", + "}\n", + "my_model = build_model(model_config)\n", + "assert isinstance(my_model, MyModel)\n", + "\n", + "# my_model inherits from torch.nn.Module, so inference works as usual:\n", + "x = torch.rand((1, 3, 200, 200))\n", + "with torch.no_grad():\n", + " print(my_model(x))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that your model is integrated with the configuration system, you can train it using `classy_train.py` as described in the [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial, no further changes are needed! Just make sure the code defining your model is in the `models` folder of your classy project." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Conclusion\n", + "\n", + "In this tutorial, we learned how to make your model compatible with Classy Vision and how to integrate it with the configuration system. Refer to our documentation to learn more about [ClassyModel](https://classyvision.ai/api/models.html)." + ] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/fine_tuning.ipynb b/tutorials/fine_tuning.ipynb new file mode 100644 index 0000000000..b55eec15f9 --- /dev/null +++ b/tutorials/fine_tuning.ipynb @@ -0,0 +1,693 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fine tuning a model\n", + "\n", + "Fine tuning is a form of transfer learning: when you only have a small labeled dataset for a specific task, you can pick up a model trained for a different task and fine-tune it for your specific dataset. These pre-trained models are usually trained on much larger datasets, which helps improving performance. \n", + "\n", + "In this tutorial we'll look into how to pick up a pre-trained model and fine tune it for a different task. In part (1) we'll train a model and save it to a checkpoint file. In part (2), we'll load the checkpoint file and run the fine-tuning. Feel free to skip part (1) if you already have a checkpoint file to begin with." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Training a model\n", + "Let us begin by pre-training a model using a head with 1000 classes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to train for 4 epochs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "num_epochs = 4" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will be using synthetic train and test datasets for this example. The transforms used are from torchvision and are applied to the input value in the sample (rather than the target)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.dataset import SyntheticImageDataset\n", + "\n", + "train_dataset = SyntheticImageDataset.from_config({\n", + " \"batchsize_per_replica\": 32,\n", + " \"num_samples\": 2000,\n", + " \"crop_size\": 224,\n", + " \"class_ratio\": 0.5,\n", + " \"seed\": 0,\n", + " \"use_shuffle\": True,\n", + " \"split\": \"train\",\n", + " \"transforms\": [{\n", + " \"name\": \"apply_transform_to_key\",\n", + " \"transforms\": [\n", + " {\"name\": \"ToTensor\"},\n", + " {\"name\": \"Normalize\", \"mean\": [0.485, 0.456, 0.406], \"std\": [0.229, 0.224, 0.225]}\n", + " ],\n", + " \"key\": \"input\"\n", + " }]\n", + "})\n", + "test_dataset = SyntheticImageDataset.from_config({\n", + " \"batchsize_per_replica\": 32,\n", + " \"num_samples\": 200,\n", + " \"crop_size\": 224,\n", + " \"class_ratio\": 0.5,\n", + " \"seed\": 0,\n", + " \"use_shuffle\": False,\n", + " \"split\": \"test\",\n", + " \"transforms\": [{\n", + " \"name\": \"apply_transform_to_key\",\n", + " \"transforms\": [\n", + " {\"name\": \"ToTensor\"},\n", + " {\"name\": \"Normalize\", \"mean\": [0.485, 0.456, 0.406], \"std\": [0.229, 0.224, 0.225]}\n", + " ],\n", + " \"key\": \"input\"\n", + " }]\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us create a ResNet 50 model now." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.models import ResNet\n", + "\n", + "model = ResNet.from_config({\n", + " \"num_blocks\": [3, 4, 6, 3],\n", + " \"small_input\": False,\n", + " \"zero_init_bn_residuals\": True\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we will create a head with 1000 classes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.heads import FullyConnectedHead\n", + "\n", + "head = FullyConnectedHead(unique_id=\"default_head\", num_classes=1000, in_plane=2048)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us attach the head to the final block of the model.\n", + "\n", + "For ResNet 50, we want to attach to the `3`rd block in the `4`th layer (based on `[3, 4, 6, 3]`). The blocks use 0 indexing, so this maps to `\"block3-2\"`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "model.set_heads({\"block3-2\": {head.unique_id: head}})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use a cross entropy loss from Pytorch." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from torch.nn.modules.loss import CrossEntropyLoss\n", + "\n", + "loss = CrossEntropyLoss()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the optimizer, we will be using SGD." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.optim import build_optimizer\n", + "\n", + "\n", + "optimizer = build_optimizer({\n", + " \"name\": \"sgd\",\n", + " \"lr\": {\"name\": \"step\", \"values\": [0.1, 0.01]},\n", + " \"weight_decay\": 1e-4,\n", + " \"momentum\": 0.9,\n", + " \"num_epochs\": num_epochs\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to track the top-1 and top-5 accuracies of the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.meters import AccuracyMeter\n", + "\n", + "meters = [AccuracyMeter(topk=[1, 5])]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create a directory to save the checkpoints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "\n", + "pretrain_checkpoint_dir = f\"/tmp/checkpoint_{time.time()}\"\n", + "os.mkdir(pretrain_checkpoint_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add `LossLrMeterLoggingHook` to monitor the loss and `CheckpointHook` to save the checkpoints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.hooks import CheckpointHook, LossLrMeterLoggingHook, ProgressBarHook\n", + "\n", + "hooks = [\n", + " LossLrMeterLoggingHook(),\n", + " CheckpointHook(pretrain_checkpoint_dir, input_args={})\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have all the components ready to setup our pre-training task which trains for 4 epochs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.tasks import ClassificationTask\n", + "\n", + "pretrain_task = (\n", + " ClassificationTask()\n", + " .set_num_epochs(num_epochs)\n", + " .set_loss(loss)\n", + " .set_model(model)\n", + " .set_optimizer(optimizer)\n", + " .set_meters(meters)\n", + " .set_hooks(hooks)\n", + " .set_dataset(train_dataset, \"train\")\n", + " .set_dataset(test_dataset, \"test\")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us train using a local trainer instance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.trainer import LocalTrainer\n", + "\n", + "trainer = LocalTrainer()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can start training!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "trainer.train(pretrain_task)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Training is done! Let us now load the saved checkpoint, we will use this later for fine tuning." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.generic.util import load_checkpoint\n", + "\n", + "pretrained_checkpoint = load_checkpoint(pretrain_checkpoint_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Fine-tuning the model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The original model was trained for 1000 classes. Let's fine-tune it for a problem with only 2 classes. To keep things fast we'll run a single epoch:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "num_epochs = 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can re-use the same synthetic datasets as before." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us again create a ResNet 50 model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.models import ResNet\n", + "\n", + "model = ResNet.from_config({\n", + " \"num_blocks\": [3, 4, 6, 3],\n", + " \"small_input\": False,\n", + " \"zero_init_bn_residuals\": True\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For fine tuning, we will create a head with just 2 classes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.heads import FullyConnectedHead\n", + "\n", + "head = FullyConnectedHead(unique_id=\"default_head\", num_classes=2, in_plane=2048)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us attach the head to the final block of the model, like before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "model.set_heads({\"block3-2\": {head.unique_id: head}})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the optimizer, we will be using RMSProp this time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.optim import build_optimizer\n", + "\n", + "\n", + "optimizer = build_optimizer({\n", + " \"name\": \"rmsprop\",\n", + " \"lr\": {\"name\": \"step\", \"values\": [0.1, 0.01]},\n", + " \"weight_decay\": 1e-4,\n", + " \"momentum\": 0.9,\n", + " \"alpha\": 0.9,\n", + " \"eps\": 1e-3,\n", + " \"num_epochs\": num_epochs\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to track the top-1 accuracy of the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.meters import AccuracyMeter\n", + "\n", + "meters = [AccuracyMeter(topk=[1])]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will create a new directory to save the checkpoints for our fine tuning run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "\n", + "fine_tuning_checkpoint_dir = f\"/tmp/checkpoint_{time.time()}\"\n", + "os.mkdir(fine_tuning_checkpoint_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Hooks are also the same as before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.hooks import CheckpointHook, LossLrMeterLoggingHook\n", + "\n", + "hooks = [\n", + " LossLrMeterLoggingHook(),\n", + " CheckpointHook(fine_tuning_checkpoint_dir, input_args={})\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can setup our fine tuning task." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.tasks import FineTuningTask\n", + "\n", + "fine_tuning_task = (\n", + " FineTuningTask()\n", + " .set_num_epochs(num_epochs)\n", + " .set_loss(loss)\n", + " .set_model(model)\n", + " .set_optimizer(optimizer)\n", + " .set_meters(meters)\n", + " .set_hooks(hooks)\n", + " .set_dataset(train_dataset, \"train\")\n", + " .set_dataset(test_dataset, \"test\")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since this is a fine tuning task, there are some other configurations which need to be done." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We don't want to re-train the trunk, so we will be freezing it. This is optional." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "fine_tuning_task.set_freeze_trunk(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to start training the heads from scratch, so we will be resetting them. This is required in this example since the pre-trained heads are not compatible with the heads in fine tuning (they have different number of classes). Otherwise, this is also optional." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "fine_tuning_task.set_reset_heads(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need to give our task the pre-trained checkpoint, which it'll need to start pre-training on." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "fine_tuning_task.set_pretrained_checkpoint(pretrained_checkpoint)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us fine tune!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "trainer.train(fine_tuning_task)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Conclusion\n", + "\n", + "In this tutorial, we learned how to load a pre-trained model in Classy Vision and how to fine-tune it for a different task. We did that by using the `FineTuningTask` abstraction, which lets you load the pretrained model weights, attaching a new head to the model and optionally freeze the weights of the original model. \n", + "\n", + "To learn more about about fine-tuning, check out our documentation for [FineTuningTask](https://classyvision.ai/api/tasks.html#classy_vision.tasks.FineTuningTask) and [ClassyHead](https://classyvision.ai/api/heads.html)" + ] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "kernelspec": { + "display_name": "Classy Vision", + "language": "python", + "name": "bento_kernel_classy_vision" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5+" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/getting_started.ipynb b/tutorials/getting_started.ipynb new file mode 100644 index 0000000000..21f247ba20 --- /dev/null +++ b/tutorials/getting_started.ipynb @@ -0,0 +1,559 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Getting started with Classy Vision" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Classy Vision is an end-to-end framework for image and video classification. Classy Vision makes it easy to write and launch distributed training jobs.\n", + "\n", + "In this tutorial, we will cover:\n", + "1. How to start a new project;\n", + "2. How to launch a single node training run; \n", + "3. How to launch a distributed training run; \n", + "4. How to visualize results with Tensorboard; \n", + "5. How to load checkpoints and interact with the trained model; \n", + "6. How to start training from a Jupyter notebook;\n", + "7. How to train a ResNet 50 model on ImageNet;\n", + "\n", + "## 0. Setup\n", + "\n", + "Make sure you have Classy Vision installed. To install it, run this in your terminal:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! pip install classy_vision" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you would like to use GPUs for training, make sure your environment has a working version of PyTorch with CUDA:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import torch\n", + "torch.cuda.is_available()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The cell above should output `True`. Check out [this link](https://pytorch.org/get-started/locally/) for more details on how to install PyTorch. For this tutorial, we will be using [Tensorboard](https://www.tensorflow.org/tensorboard). Install it with the following (on your terminal):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! pip install tensorboard tensorboardX" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Start a new project\n", + "\n", + "To start, let's create a new project. Run this in your terminal:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! classy-project my-project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "% cd my-project" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To launch a training run on the current machine, run the following:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! ./classy_train.py --config configs/template_config.json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's it! You've launched your first training run. This trained a small MLP model on a dataset made of random noise, which is not that useful. The `classy-project` utility creates the scaffolding for you project, and you should modify it according to your needs. We'll learn how to customize your runs on the next few tutorials.\n", + "\n", + "Let's take a look at what `classy-project` has created for us:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! find . | grep -v \\.pyc | sort" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's what each folder means:\n", + "\n", + " * `configs`: stores your experiment configurations. Keeping all your experiments as separate configuration files helps making your research reproducible;\n", + " * `models`: code for your custom model architectures;\n", + " * `losses`: code for your custom loss functions;\n", + " * `datasets`: code for your custom datasets;\n", + " * `classy_train.py`: script to execute a training job; This uses the Classy Vision library to configure the job and execute it, and you might change it according to your needs;\n", + " * `template_config.json`: experiment configuration file. This file is read by `classy_train.py` to configure your training job and launch it." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a peek at the configuration file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! cat configs/template_config.json" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That file can be shared with other researchers whenever you want them to reproduce your experiments. We generate `json` files by default, but `YAML` will be officially supported soon.\n", + "\n", + "## 2. Distributed training\n", + "\n", + "`classy_train.py` can also be called from `torch.distributed.launch`, similar to regular PyTorch distributed scripts:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! python -m torch.distributed.launch --use_env --nproc_per_node=2 ./classy_train.py --config configs/template_config.json --distributed_backend ddp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you have two GPUs on your current machine, that command will launch one process per GPU and start a [DistributedDataParallel](https://pytorch.org/tutorials/intermediate/ddp_tutorial.html) training run. \n", + "\n", + "## 3. Tensorboard integration\n", + "\n", + "[Tensorboard](https://www.tensorflow.org/tensorboard) is a very useful tool for visualizing training progress. Classy Vision works with tensorboard out-of-the-box, just make sure you have it installed as described in the Setup section. By default `classy_train.py` will output tensorboard data in a subdirectory of your project directory (typically named `output_/tensorboard`), so in our case we can just launch tensorboard in the current working directory:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "scrolled": true + }, + "outputs": [], + "source": [ + "%load_ext tensorboard\n", + "%tensorboard --logdir ." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also customize the tensorboard output directory by editing `classy_train.py`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Loading checkpoints\n", + "\n", + "Now that we've run `classy_train.py`, let's see how to load the resulting model. At the end of execution, `classy_train.py` will print the checkpoint directory used for that run. Each run will output to a different directory, typically named `output_/checkpoints`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.generic.util import load_checkpoint\n", + "from classy_vision.models import ClassyModel\n", + "\n", + "# This is important: importing models here will register your custom models with Classy Vision\n", + "# so that it can instantiate them appropriately from the checkpoint file\n", + "# See more information at https://classyvision.ai/api/models.html#classy_vision.models.register_model\n", + "import models\n", + "\n", + "# Update this with your actual directory:\n", + "checkpoint_dir = './output_/checkpoints'\n", + "checkpoint_data = load_checkpoint(checkpoint_dir)\n", + "model = ClassyModel.from_checkpoint(checkpoint_data)\n", + "model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's it! You can now use that model for inference as usual.\n", + "\n", + "## 5. Resuming from checkpoints\n", + "\n", + "Resuming from a checkpoint is as simple as training: `classy_train.py` takes a `--checkpoint_folder` argument, which specifies the checkpoint to resume from:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "! ./classy_train.py --config configs/template_config.json --checkpoint_folder ./output_/checkpoints" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Interactive development\n", + "\n", + "Training scripts and configuration files are useful for running large training jobs on a GPU cluster (see our [AWS tutorial](https://classyvision.ai/tutorials/ray_aws)), but a lot of day-to-day work happens interactively within Jupyter notebooks. Classy Vision is designed as a library that can be used without our built-in training scripts. Let's take a look at how to do the same training run as before, but within Jupyter instead of using `classy_train.py`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import classy_vision" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from datasets.my_dataset import MyDataset\n", + "from models.my_model import MyModel\n", + "from losses.my_loss import MyLoss\n", + "from classy_vision.dataset.transforms import GenericImageTransform\n", + "from torchvision import transforms\n", + "\n", + "train_dataset = MyDataset(\n", + " batchsize_per_replica=32,\n", + " shuffle=False,\n", + " transform=GenericImageTransform(\n", + " transform=transforms.Compose(\n", + " [\n", + " transforms.RandomResizedCrop(224),\n", + " transforms.RandomHorizontalFlip(),\n", + " transforms.ToTensor(),\n", + " transforms.Normalize(\n", + " mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]\n", + " ),\n", + " ]\n", + " )\n", + " ),\n", + " num_samples=100,\n", + " crop_size=224,\n", + " class_ratio=0.5,\n", + " seed=0,\n", + ")\n", + "\n", + "test_dataset = MyDataset(\n", + " batchsize_per_replica=32,\n", + " shuffle=False,\n", + " transform=GenericImageTransform(\n", + " transform=transforms.Compose(\n", + " [\n", + " transforms.Resize(256),\n", + " transforms.CenterCrop(224),\n", + " transforms.ToTensor(),\n", + " transforms.Normalize(\n", + " mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]\n", + " ),\n", + " ]\n", + " )\n", + " ),\n", + " num_samples=100,\n", + " crop_size=224,\n", + " class_ratio=0.5,\n", + " seed=0,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from classy_vision.tasks import ClassificationTask\n", + "from classy_vision.optim import SGD\n", + "from classy_vision.optim.param_scheduler import ConstantParamScheduler\n", + "\n", + "model = MyModel()\n", + "loss = MyLoss()\n", + "\n", + "optimizer = SGD(\n", + " lr_scheduler=ConstantParamScheduler(0.01)\n", + ")\n", + "\n", + "from classy_vision.trainer import LocalTrainer\n", + "\n", + "task = ClassificationTask() \\\n", + " .set_model(model) \\\n", + " .set_dataset(train_dataset, \"train\") \\\n", + " .set_dataset(test_dataset, \"test\") \\\n", + " .set_loss(loss) \\\n", + " .set_optimizer(optimizer) \\\n", + " .set_num_epochs(1)\n", + "\n", + "trainer = LocalTrainer()\n", + "trainer.train(task)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's it! Your model is trained now and ready for inference:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import torch\n", + "x = torch.randn((1, 3, 224, 224))\n", + "with torch.no_grad():\n", + " y_hat = model(x)\n", + "\n", + "y_hat" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Training a ResNet 50 on ImageNet\n", + "\n", + "We have looked at training models using synthetic data so far. A more typical workflow involves training a model on a real world dataset like [ImageNet](http://image-net.org/), which we will cover in this section.\n", + "\n", + "To be able to train using ImageNet, first download the dataset archives from http://image-net.org/. Then, extract the data to a format expected by [`torchvision.datasets.ImageFolder`](https://pytorch.org/docs/stable/torchvision/datasets.html#imagefolder) inside subdirectories for the individual splits (`train` and `val`). We can then pass the root path containing these archives to the [`ImageNetDataset`](https://classyvision.ai/api/dataset.html#classy_vision.dataset.ImageNetDataset).\n", + "\n", + "The following configuration can be used to train a ResNet 50 on ImageNet to `76.2%` top-1 accuracy in 90 epochs. The optimizer configuration uses SGD with momentum, gradual learning rate warm up for the first 5 epochs and 1/10 learning rate drops at epochs 30, 60 and 80. The learning rate is calculated for a setup with 32 GPUs and can be scaled based on the overall batch size [1]." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "config = {\n", + " \"name\": \"classification_task\",\n", + " \"num_epochs\": 90,\n", + " \"loss\": {\n", + " \"name\": \"CrossEntropyLoss\"\n", + " },\n", + " \"dataset\": {\n", + " \"train\": {\n", + " \"name\": \"classy_imagenet\",\n", + " \"split\": \"train\",\n", + " \"batchsize_per_replica\": 32,\n", + " \"num_samples\": None,\n", + " \"use_shuffle\": True,\n", + " \"root\": \"/path/to/imagenet/\" # replace with path to the extracted dataset\n", + " },\n", + " \"test\": {\n", + " \"name\": \"classy_imagenet\",\n", + " \"split\": \"val\",\n", + " \"batchsize_per_replica\": 32,\n", + " \"num_samples\": None,\n", + " \"use_shuffle\": False,\n", + " \"root\": \"/path/to/imagenet/\" # replace with path to the extracted dataset\n", + " }\n", + " },\n", + " \"meters\": {\n", + " \"accuracy\": {\n", + " \"topk\": [1, 5]\n", + " }\n", + " },\n", + " \"model\": {\n", + " \"name\": \"resnet\",\n", + " \"num_blocks\": [3, 4, 6, 3],\n", + " \"small_input\": False,\n", + " \"zero_init_bn_residuals\": True,\n", + " \"heads\": [\n", + " {\n", + " \"name\": \"fully_connected\",\n", + " \"unique_id\": \"default_head\",\n", + " \"num_classes\": 1000,\n", + " \"fork_block\": \"block3-2\",\n", + " \"in_plane\": 2048\n", + " }\n", + " ]\n", + " },\n", + " \"optimizer\": {\n", + " \"name\": \"sgd\",\n", + " \"lr\": {\n", + " \"name\": \"composite\",\n", + " \"schedulers\": [\n", + " {\"name\": \"linear\", \"start_lr\": 0.1, \"end_lr\": 0.4},\n", + " {\"name\": \"multistep\", \"values\": [0.4, 0.04, 0.004, 0.0004], \"milestones\": [30, 60, 80]}\n", + " ],\n", + " \"update_interval\": \"epoch\",\n", + " \"interval_scaling\": [\"rescaled\", \"fixed\"],\n", + " \"lengths\": [0.0555, 0.9445]\n", + " },\n", + " \"weight_decay\": 1e-4,\n", + " \"momentum\": 0.9\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Conclusion\n", + "\n", + "In this tutorial, we learned how to start a new project using Classy Vision, how to perform tranining locally and how to do multi-gpu training on a single machine. We also saw how to use Tensorboard to visualize training progress, how to load models from checkpoints and how resume training from a checkpoint file. We also went over how to use the ImageNet dataset to train a ResNet 50. In the next tutorials, we'll look into how to add custom datasets, models and loss functions to Classy Vision so you can adapt it to your needs, and how to launch distributed training on multiple nodes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. References\n", + "\n", + "[1] Goyal, Priya, et al. \"Accurate, large minibatch sgd: Training imagenet in 1 hour.\" arXiv preprint arXiv:1706.02677 (2017)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5+" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/pet_aws.ipynb b/tutorials/pet_aws.ipynb new file mode 100644 index 0000000000..e243228b16 --- /dev/null +++ b/tutorials/pet_aws.ipynb @@ -0,0 +1,295 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Elastic training with Classy Vision\n", + "\n", + "This tutorial will demonstrate how to launch an training job on Amazon Web Services ([AWS](https://aws.amazon.com/)) using [PyTorch Elastic](https://github.com/pytorch/elastic) and Classy Vision.\n", + "\n", + "## Prerequisites\n", + "\n", + "1. Familiarity with basic AWS (EC2, Auto Scaling Groups, S3, EFS).\n", + "2. (suggested) install and setup [`awscli`](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html).\n", + "3. Basic knowledge of containers (we use Docker in our examples).\n", + "\n", + "## 1. Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Download the PyTorch Elastic repository and install it. Run in your terminal:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! git clone https://github.com/pytorch/elastic.git\n", + "! pip install torchelastic" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Install the required dependencies for AWS:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "% cd elastic/aws\n", + "! pip install -r requirements.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Make sure you are familiar with the following AWS resources:\n", + "\n", + " 1. EC2 [instance profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html)\n", + " 2. EC2 [key pair](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html)\n", + " 3. [Subnet(s)](https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html#create-default-subnet)\n", + " 4. [Security group](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html#DefaultSecurityGroup)\n", + " 5. EFS volume\n", + " 6. S3 bucket\n", + " \n", + "[Install](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html)\n", + " the AWS Session Manager plugin." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Create the cluster\n", + "\n", + "`petctl` is a commandline tool that helps run distributed jobs written with torchelastic on EC2 instances. It's available in the `aws` directory of the `torchelastic` repo. To get started, run this on your terminal:\n", + "\n", + "```bash\n", + "python3 petctl.py setup\n", + "```\n", + "\n", + "This will bootstrap all the AWS resources required to run a torchelastic\n", + "job. For details take a look at the CloudFormation [template](cfn/setup.yml) .\n", + "\n", + "Use `--s3_bucket` and `--efs_id` to use an existing S3 bucket and EFS \n", + "file system. Otherwise an S3 bucket and EFS volume will be created.\n", + "\n", + "> **IMPORTANT** when specifying `--efs_id` you MUST ensure that NO mount targets\n", + "exist on the EFS file system. torchelastic's cfn stack will attempt to create\n", + "mount targets for the subnets it creates and WILL FAIL if the file system already\n", + "has mount targets on a different VPC. For more information refer to \n", + "the [EFS docs](https://docs.aws.amazon.com/efs/latest/ug/accessing-fs.html). \n", + "\n", + "**TIP:** If the stack creation fails, log into the CloudFormation console, inspect\n", + "the failure reason, address the failure, then manually delete the stack and re-run\n", + "`petctl configure`.\n", + "\n", + "If you are familiar with AWS or already have the resources specified in the \n", + "**Requirements** section above, then you can follow the [Manual Setup](https://github.com/pytorch/elastic/blob/master/aws/README.md) instructions\n", + "in the `torchelastic` repository. Simply copy the sample specs file and fill\n", + "in the template, then run `python petctl.py configure`. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Create your Classy Vision project\n", + "\n", + "If you already have a Classy Vision project to use with `torchelastic`, great! You only need to modify `classy_train.py` to use an `ElasticTrainer` instead of a `DistributedTrainer`. See our [getting started](https://classyvision.ai/tutorials/getting_started) tutorial for more details about `classy_train.py`.\n", + "\n", + "To make things easier, we provided an example of how to use `ElasticTrainer`: it's under `./examples/classy_vision/main.py` in the `torchelastic` repo. You can start by copying that file and use it to replace `classy_train.py`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 4. Start training\n", + "\n", + "Normally you would run the training script directly to start training. For elastic training, we'll use `petctl` to launch it. Here's how you launch our example script in your terminal:\n", + "\n", + "``` bash\n", + "python3 aws/petctl.py run_job --size 2 --min_size 2 --max_size 2 --name ${USER}-job examples/classy_vision/main.py -- --config_file classy-vision://configs/resnet50_synthetic_image_classy_config.json --num_workers 0```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the example above, the named arguments, such as, `--size` , `--min_size`, and\n", + "`--max_size` are parameters to the `run_job` sub-command of `petctl`. In the example\n", + "above, we created an **elastic** job where the initial worker `--size=2`, we are\n", + "allowed to scale down to `--min_size` and up to `--max_size`. This is used by\n", + "torchelastic's rendezvous algorithm to determine how many nodes to admit on each\n", + "re-rendezvous before considering the group *final* and start the `train_step`.\n", + "\n", + "Because all the size parameters are the same in this case, that means we are disabling elasticity. You might want to do that for reproducibility reasons, for instance. Training this way still provides benefits, since `torchelastic` increases robustness: when a node fails, we can start a new node and resume training from the last minibatch, without reverting back to the previous checkpoint. \n", + "\n", + "The other positional arguments have the form:\n", + "\n", + "```\n", + "[local script] -- [script args ...]\n", + " -- or -- \n", + "[local directory] -- [script] [script args...]\n", + "```\n", + "\n", + "If the first positional argument is a path to a script file, then the script\n", + "is uploaded to S3 and the script arguments specified after the `--` delimiter\n", + "are passed through to the script.\n", + "\n", + "If the first positional argument is a directory, then a tarball of the directory\n", + "is created and uploaded to S3 and is extracted on the worker-side. In this case\n", + "the first argument after the `--` delimiter is the path to the script **relative** to the\n", + "specified directory and the rest of the arguments after the delimiter is passed \n", + "to the script.\n", + "\n", + "\n", + "In our example we specified\n", + "```\n", + "petctl.py run_job [...] classy_vision/main.py --config_file [...]\n", + "```\n", + "\n", + "We could have decided to specify the directory instead\n", + "```\n", + "petctl.py run_job [...] classy_vision -- main.py --config_file [...]\n", + "```\n", + "\n", + "**TIP 1:** Besides a local script or directory you can run with scripts or `tar` files\n", + "that have already been uploaded to S3 or directly point it to a file or directory\n", + "on the container.\n", + "``` bash\n", + "python3 petctl.py run_job [...] s3://my-bucket/my_script.py\n", + "python3 petctl.py run_job [...] s3://my-bucket/my_dir.tar.gz -- my_script.py\n", + "\n", + "# or\n", + "python3 petctl.py run_job [...] docker:///abs/path/in/container/dir -- my_script.py\n", + "python3 petctl.py run_job [...] docker://rel/path/in/container/dir/my_script.py\n", + "```\n", + "\n", + "**TIP 2:** To iterate quickly, simply make changes to your local script and\n", + "upload the script to S3 using\n", + "```bash \n", + "python3 petctl.py upload examples/imagenet/main.py s3://// \n", + "```\n", + "\n", + "**TIP 3:** Use the EFS volume attached on `/mnt/efs/fs1` on all the workers to \n", + "save input data, checkpoints and job output.\n", + "\n", + "Once the `run_job` command returns log into the EC2 console, you will see two\n", + "Auto Scaling Groups\n", + "1. etcd server \n", + "2. workers\n", + "\n", + "## 5. Inspect the logs\n", + "Log into the AWS CloudWatch Logs console. You should see a log group called\n", + "`torchelastic/$USER`. Under it there will be a log stream per instance with the \n", + "name `$job_name/$instance_id` (e.g. `my_job/i0b938EXAMPLE`).\n", + "\n", + "#### Troubleshooting\n", + "To SSH onto the worker nodes to debug/inspect the worker process use AWS \n", + "Session Manager instead of the ec2 key pair. [Install](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html)\n", + " the Session Manager plugin and run\n", + "\n", + "``` bash\n", + "# get the instance ids of the workers\n", + "python3 petctl.py list_hosts \n", + "\n", + "# ssh onto one of the workers\n", + "awscli ssm start-session --target \n", + " -- example --\n", + "awscli ssm start-session --target i-00b00EXAMPLE\n", + "```\n", + "\n", + "Once SSH'ed, the workers run in a docker container managed by `systemd`.\n", + "You can take a look at their console outputs by running\n", + "\n", + "``` bash\n", + "# see the status of the worker\n", + "sudo systemctl status torchelastic_worker\n", + "# get the container id\n", + "sudo docker ps\n", + "# tail the container logs\n", + "sudo docker logs -f \n", + "```\n", + "\n", + "You can also manually stop and start the workers by running\n", + "``` bash\n", + "sudo systemctl stop torchelastic_worker\n", + "sudo systemctl start torchelastic_worker\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **EXERCISE:** Open up two terminals and SSH onto each worker. Tail the docker logs\n", + "on each worker. Now stop worker 1 and observe the worker 2 re-rendezvous and\n", + "since `--min_size=1` it continues training by itself. Now restart worker 1 and\n", + "observe that worker 2 notices that worker 1 is waiting to join and re-rendezvous,\n", + "the `state` object in worker 2 is `sync()`'ed to worker 1 and both resume training\n", + "without loss of progress." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note**: by design, `petctl` tries to use the least number of AWS services. This\n", + "was done intentionally to allow non-AWS users to easily transfer the functionality\n", + "to their environment. Hence it currently does not have the functionality to query\n", + "status of the job or to terminate the ASG when the job is done (there is nothing\n", + "that is monitoring the job!). In practice consider using EKS, Batch, or SageMaker." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Stop training\n", + "To stop the job and tear down the resources, use the `kill_job` command:\n", + "\n", + "``` bash\n", + "python3 petctl.py kill_job ${USER}-job\n", + "```\n", + "\n", + "You'll notice that the two ASGs created with the `run_job` command are deleted." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/ray_aws.ipynb b/tutorials/ray_aws.ipynb new file mode 100644 index 0000000000..d9043dce14 --- /dev/null +++ b/tutorials/ray_aws.ipynb @@ -0,0 +1,255 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Distributed training on AWS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial we will learn: \n", + "1. How to start a cluster on AWS for use with Classy Vision; \n", + "2. How to start a new project on the cluster; \n", + "3. How to launch training jobs on the cluster;\n", + "\n", + "## 1. Setup\n", + "\n", + "Make sure you have Classy Vision installed, as described in our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial. \n", + "\n", + "For this tutorial we will also need the Classy Vision sources, you can clone it with this command (on your terminal):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! git clone https://github.com/facebookresearch/ClassyVision.git" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial we'll use [Ray](https://github.com/ray-project/ray) to manage the AWS resources. Install Ray and all its required dependencies with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "% cd ./ClassyVision/examples/ray\n", + "! pip install -r requirements.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You should also set up your AWS CLI and credentials as described [here](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration). To make sure everything is working, run on your terminal:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! aws ec2 describe-instances" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That should print a JSON file with all your current AWS instances (or empty if you don't have any). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Cluster setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have a sample cluster configuration file stored in the Classy Vision repository, under `./examples/ray/cluster_config.yml`. Let's verify that Ray can start the cluster appropriately:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! ray up cluster_config.yml -y" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That will take about 10 minutes, and at the end you should see a message explaining how to connect to the cluster. Assuming everything worked successfully, now tear down the cluster:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! ray down cluster_config.yml -y" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now set up an EFS volume to store our code and datasets. Follow [this tutorial](https://aws.amazon.com/getting-started/tutorials/create-network-file-system/) to setup the EFS volume in your AWS account. \n", + "\n", + "When you're done with that tutorial, go back to the EFS section in the AWS console, find your filesystem there and click `Manage file system access`. Add the `ray-autoscaler-default` security group to the list of security groups allowed to use your EFS volume. That security group should have been created by the `ray up` command we ran earlier.\n", + "\n", + "You should now have an identifier for your EFS volume. Open `cluster_config.yml` in your favorite text editor and replace `{{FileSystemId}}` with your own EFS id. We are now ready to launch our cluster again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! ray up cluster_config.yml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Create a project\n", + "\n", + "When it's done, let's attach to the head node of the cluster:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! ray attach cluster_config.yml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That will give you an SSH session into the head node, which coordinates all the worker nodes in Ray. In our example configuration file, the head node is a CPU-only machine, and the workers all have GPUs.\n", + "\n", + "Both the head node and the worker nodes will have the same EFS volume mounted, so we'll use that to send code from the head to the workers. The following commands are meant to run on the head node (e.g. in the terminal prompt you got from `ray attach`). Let's start a project in the EFS folder:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "$ cd efs\n", + "$ classy-project my_project" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Start training\n", + "\n", + "Classy Vision comes with a launcher analogous to `torch.distributed.launch`, but that launches jobs on multiple machines using Ray. To use it, simply run:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "$ python -m classy_vision.distributed.launch_ray --nnodes=2 --use_env ~/efs/my_project/classy_train.py --config ~/efs/my_project/configs/template_config.json --distributed_backend ddp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Your first time running this you might see logs like `Not enough GPUs available`. That's normal, and it's because the worker nodes are still being set up. The `ray up` command should have printed a command line you can use to follow their progress. But there's no need to do anything, the launcher will wait until the workers are available and execute the command automatically.\n", + "\n", + "That's it! When that command is done it should print the folder where the checkpoints are.\n", + "\n", + "> Note that we specified the full absolute path for the config in the argument list. That's because the `classy_train.py` command is running on a remote machine and we are relying on the fact that the EFS folder is mounted at exactly the same location on the head and worker nodes. Keep that in mind if you modify this setup.\n", + "\n", + "> Remember to tear down the cluster with `ray down cluster_config.yml` when you're done. You will be billed as long as the machines are up, even when not using them." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Troubleshooting\n", + "\n", + "If you hit an error during this tutorial, here are a few things that might help to debug what is going on:\n", + "\n", + "### Make sure all workers have initialized properly\n", + "\n", + "When the `ray up` command finishes, it prints a command line to tail the logs. It should look like:\n", + "```bash\n", + "ray exec cluster_config.yml 'tail -n 100 -f /tmp/ray/session_*/logs/monitor*'\n", + "```\n", + "\n", + "Run that command and look for any errors. When the workers are done initializing, you should see `-- StandardAutoscaler: 2/2 target nodes (0 pending)` printed repeatedly on the logs.\n", + "\n", + "### Make sure EFS volumes are mounted on all machines\n", + "\n", + "Sometimes the EFS package fails to install on workers. To verify EFS is working, get the worker node IPs with `ray get-worker-ips cluster_config.yml`, then ssh on them with:\n", + "```bash\n", + "ssh -i ~/.ssh/ray-autoscaler_us-west-2.pem ubuntu@\n", + "```\n", + "\n", + "Once in a worker machine, run `df -h` to list all the current mounts. Verify `/home/ubuntu/efs` is on that list. If it's not, look for the EFS setup commands on the `cluster_config.yml` file and run them yourself. That should clarify what the issue is. If you didn't setup the EFS security groups correctly (as described in step 2), the `mount` command will hang for a few minutes then fail.\n", + "\n", + "## 6. Conclusion\n", + "\n", + "In this tutorial we covered how to start using Classy Vision on AWS using Ray. For more information about Ray, check out their [repository](https://github.com/ray-project/ray). The next tutorials ([[1]](https://classyvision.ai/tutorials/classy_model), [[2]](https://classyvision.ai/tutorials/classy_loss), [[3]](https://classyvision.ai/tutorials/classy_dataset)) will demonstrate how to customize the project created by the `classy-project` utility for your own needs. To learn more about how to train models in Classy Vision and how to use Tensorboard to visualize training progress, check out our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/torchscript.ipynb b/tutorials/torchscript.ipynb new file mode 100644 index 0000000000..139b52666a --- /dev/null +++ b/tutorials/torchscript.ipynb @@ -0,0 +1,142 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using torchscript with Classy Vision" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[torchscript](https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html) is commonly used to export PyTorch models from Python to C++. This is useful for productionizing models, when you typically perform inference on a CPU. This tutorial will demonstrate how to export a Classy Vision model using `torchscript`'s tracing mode and how to load a torchscript model.\n", + "\n", + "## 1. Build and train the model\n", + "\n", + "Our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial covered many ways of training a model, here we'll simply instantiate a ResNeXT model from a config:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.models import build_model\n", + "import torch\n", + "\n", + "config = {\n", + " \"name\": \"resnext\",\n", + " \"num_blocks\": [3, 4, 23, 3],\n", + " \"num_classes\": 1000,\n", + " \"base_width_and_cardinality\": [4, 32],\n", + " \"small_input\": False,\n", + " \"heads\": [\n", + " {\n", + " \"name\": \"fully_connected\",\n", + " \"unique_id\": \"default_head\",\n", + " \"num_classes\": 1000,\n", + " \"fork_block\": \"block3-2\",\n", + " \"in_plane\": 2048\n", + " }\n", + " ]\n", + "}\n", + "\n", + "model = build_model(config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Export the model\n", + "\n", + "Now that the model is built/trained, you can export it using `torch.jit.trace`. To check the results, we'll perform inference on the actual model and on the torchscripted model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with torch.no_grad():\n", + " script = torch.jit.trace(model, torch.randn(1, 3, 224, 224, dtype=torch.float))\n", + " input = torch.randn(1, 3, 224, 224, dtype=torch.float)\n", + " origin_outs = model(input)\n", + " script_outs = script(input)\n", + "\n", + "assert torch.allclose(origin_outs, script_outs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After verifying the torchscripted model works as expected, you can save it using `torch.jit.save`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "torch.jit.save(script, \"/tmp/resnext_101.pt\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Loading a model\n", + "\n", + "Loading a torchscripted model is as simple as calling `torch.jit.load`. If you need to fine-tune or continue training the model, the loaded model can be attached directly to a `ClassificationTask` or `FineTuningTask` in Classy Vision:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "loaded_model = torch.jit.load(\"/tmp/resnext_101.pt\")\n", + "loaded_outs = loaded_model(input)\n", + "\n", + "assert torch.allclose(loaded_outs, origin_outs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Conclusion\n", + "\n", + "`torchscript` makes it really easy to transfer models between research and production with PyTorch, and it works seamlessly with Classy Vision. Check out the [torchscript tutorial](https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html) for more information about how to export a model correctly. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/video_classification.ipynb b/tutorials/video_classification.ipynb new file mode 100644 index 0000000000..d01e66e199 --- /dev/null +++ b/tutorials/video_classification.ipynb @@ -0,0 +1,313 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to do video classification " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, we will show how to train a video classification model in Classy Vision. Given an input video, the video classification task is to predict the most probable class label. This is very similar to image classification, which was covered in other tutorials, but there are a few differences that make video special. As the video duration can be long, we sample short video clips of a small number of frames, use the classifier to make predictions, and finally average the clip-level predictions to get the final video-level predictions. \n", + "\n", + "In this tutorial we will: (1) load a video dataset; (2) configure a video model; (3) configure video meters; (4) build a task; (5) start training; Please note that these steps are being done separately in the tutorial for easy of exposition in the notebook format. As described in our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial, you can combine all configs used in this tutorial into a single config for ClassificationTask and train it using `classy_train.py`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Prepare the dataset\n", + "\n", + "All right! Let's start with the dataset. [UCF-101](https://www.crcv.ucf.edu/data/UCF101.php) is a canonical action recognition dataset. It has 101 action classes, and has 3 folds with different training/testing splitting . We use fold 1 in this tutorial. Classy Vision has implemented the dataset `ucf101`, which can be used to load the training and testing splits. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.dataset import build_dataset\n", + "\n", + "# set it to the folder where video files are saved\n", + "video_dir = \"[PUT YOUR VIDEO FOLDER HERE]\"\n", + "# set it to the folder where dataset splitting files are saved\n", + "splits_dir = \"[PUT THE FOLDER WHICH CONTAINS SPLITTING FILES HERE]\"\n", + "# set it to the file path for saving the metadata\n", + "metadata_file = \"[PUT THE FILE PATH OF DATASET META DATA HERE]\"\n", + "\n", + "datasets = {}\n", + "datasets[\"train\"] = build_dataset({\n", + " \"name\": \"ucf101\",\n", + " \"split\": \"train\",\n", + " \"batchsize_per_replica\": 8, # For training, we use 8 clips in a minibatch in each model replica\n", + " \"use_shuffle\": True, # We shuffle the clips in the training split\n", + " \"num_samples\": 64, # We train on 16 clips in one training epoch\n", + " \"clips_per_video\": 1, # For training, we randomly sample 1 clip from each video\n", + " \"frames_per_clip\": 8, # The video clip contains 8 frames\n", + " \"video_dir\": video_dir,\n", + " \"splits_dir\": splits_dir,\n", + " \"metadata_file\": metadata_file,\n", + " \"fold\": 1,\n", + " \"transforms\": {\n", + " \"video\": [\n", + " {\n", + " \"name\": \"video_default_augment\",\n", + " \"crop_size\": 112,\n", + " \"size_range\": [128, 160]\n", + " }\n", + " ]\n", + " }\n", + "})\n", + "datasets[\"test\"] = build_dataset({\n", + " \"name\": \"ucf101\",\n", + " \"split\": \"test\",\n", + " \"batchsize_per_replica\": 10, # For testing, we will take 1 video once a time, and sample 10 clips per video\n", + " \"use_shuffle\": False, # We do not shuffle clips in the testing split\n", + " \"num_samples\": 80, # We test on 80 clips in one testing epoch\n", + " \"clips_per_video\": 10, # We sample 10 clips per video\n", + " \"frames_per_clip\": 8,\n", + " \"video_dir\": video_dir,\n", + " \"splits_dir\": splits_dir,\n", + " \"metadata_file\": metadata_file,\n", + " \"fold\": 1,\n", + " \"transforms\": {\n", + " \"video\": [\n", + " {\n", + " \"name\": \"video_default_no_augment\",\n", + " \"size\": 128\n", + " }\n", + " ]\n", + " } \n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note we specify different transforms for training and testing split. For training split, we first randomly select a size from `size_range` [128, 160], and resize the video clip so that its short edge is equal to the random size. After that, we take a random crop of spatial size 112 x 112. We find such data augmentation helps the model generalize better, and use it as the default transform with data augmentation. For testing split, we resize the video clip to have short edge of size 128, and skip the random cropping to use the entire video clip. This is the default transform without data augmentation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Define a model trunk and a head\n", + "\n", + "Next, let's create the video model, which consists of a trunk and a head. The trunk can be viewed as a feature extractor for computing discriminative features from raw video pixels while the head is viewed as a classifier for producing the final predictions. Let's first create the trunk of architecture ResNet3D-18 by using the built-in `resnext3d` model in Classy Vision." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.models import build_model\n", + "\n", + "model = build_model({\n", + " \"name\": \"resnext3d\",\n", + " \"frames_per_clip\": 8, # The number of frames we have in each video clip\n", + " \"input_planes\": 3, # We use RGB video frames. So the input planes is 3\n", + " \"clip_crop_size\": 112, # We take croppings of size 112 x 112 from the video frames \n", + " \"skip_transformation_type\": \"postactivated_shortcut\", # The type of skip connection in residual unit\n", + " \"residual_transformation_type\": \"basic_transformation\", # The type of residual connection in residual unit\n", + " \"num_blocks\": [2, 2, 2, 2], # The number of residual blocks in each of the 4 stages \n", + " \"input_key\": \"video\", # The key used to index into the model input of dict type \n", + " \"stage_planes\": 64, \n", + " \"num_classes\": 101 # the number of classes\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also need to create a model head, which consists of an average pooling layer and a linear layer, by using the `fully_convolutional_linear` head. At test time, the shape (channels, frames, height, width) of input tensor is typically `(3 x 8 x 128 x 173)`. The shape of input tensor to the average pooling layer is `(2048, 1, 8, 10)`. Since we do not use a global average pooling but an average pooling layer of kernel size `(1, 7, 7)`, the pooled feature map has shape `(2048, 1, 2, 5)`. The shape of prediction tensor from the linear layer is `(1, 2, 5, 101)`, which indicates the model computes a 101-D prediction vector densely over a `2 x 5` grid. That's why we name the head as `FullyConvolutionalLinearHead` because we use the linear layer as a `1x1` convolution layer to produce spatially dense predictions. Finally, predictions over the `2 x 5` grid are averaged." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.heads import build_head\n", + "from collections import defaultdict\n", + "\n", + "unique_id = \"default_head\"\n", + "head = build_head({\n", + " \"name\": \"fully_convolutional_linear\",\n", + " \"unique_id\": unique_id,\n", + " \"pool_size\": [1, 7, 7],\n", + " \"num_classes\": 101,\n", + " \"in_plane\": 512 \n", + "})\n", + "# In Classy Vision, the head can be attached to any residual block in the trunk. \n", + "# Here we attach the head to the last block as in the standard ResNet model\n", + "fork_block = \"pathway0-stage4-block1\"\n", + "heads = defaultdict(dict)\n", + "heads[fork_block][unique_id] = head\n", + "model.set_heads(heads)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Choose the meters\n", + "\n", + "This is the biggest difference between video and image classification. For images we used `AccuracyMeter` to measure top-1 and top-5 accuracy. For videos you can also use both `AccuracyMeter` and `VideoAccuracyMeter`, but they behave differently:\n", + " * `AccuracyMeter` takes one clip-level prediction and compare it with groundtruth video label. It reports the clip-level accuracy.\n", + " * `VideoAccuracyMeter` takes multiple clip-level predictions from the same video, averages them and compares that with groundtruth video label. It reports the video-level accuracy which is usually higher than clip-level accuracy. \n", + " \n", + " Both meters report top-1 and top-5 accuracy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.meters import build_meters, AccuracyMeter, VideoAccuracyMeter\n", + "\n", + "meters = build_meters({\n", + " \"accuracy\": {\n", + " \"topk\": [1, 5]\n", + " },\n", + " \"video_accuracy\": {\n", + " \"topk\": [1, 5],\n", + " \"clips_per_video_train\": 1,\n", + " \"clips_per_video_test\": 10\n", + " }\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Build a task\n", + "Great! we have defined the minimal set of components necessary for video classification, including dataset, model, loss function, meters and optimizer. We proceed to define a video classification task, and populate it with all the components." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from classy_vision.tasks import ClassificationTask\n", + "from classy_vision.optim import build_optimizer\n", + "from classy_vision.losses import build_loss\n", + "\n", + "loss = build_loss({\"name\": \"CrossEntropyLoss\"})\n", + "\n", + "optimizer = build_optimizer({\n", + " \"name\": \"sgd\",\n", + " \"lr\": {\n", + " \"name\": \"multistep\",\n", + " \"values\": [0.005, 0.0005],\n", + " \"milestones\": [1]\n", + " },\n", + " \"num_epochs\": 2,\n", + " \"weight_decay\": 0.0001,\n", + " \"momentum\": 0.9\n", + "})\n", + "\n", + "num_epochs = 2\n", + "task = (\n", + " ClassificationTask()\n", + " .set_num_epochs(num_epochs)\n", + " .set_loss(loss)\n", + " .set_model(model)\n", + " .set_optimizer(optimizer)\n", + " .set_meters(meters)\n", + ") \n", + "for phase in [\"train\", \"test\"]:\n", + " task.set_dataset(datasets[phase], phase)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 5. Start training\n", + "\n", + "After creating a task, you can simply pass that to a Trainer to start training. Here we will train on a single node and \n", + "configure logging and checkpoints for training:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import os\n", + "\n", + "from classy_vision.trainer import LocalTrainer\n", + "from classy_vision.hooks import CheckpointHook\n", + "from classy_vision.hooks import LossLrMeterLoggingHook\n", + "\n", + "hooks = [LossLrMeterLoggingHook(log_freq=4)]\n", + "\n", + "checkpoint_dir = f\"/tmp/classy_checkpoint_{time.time()}\"\n", + "os.mkdir(checkpoint_dir)\n", + "hooks.append(CheckpointHook(checkpoint_dir, input_args={}))\n", + "\n", + "task = task.set_hooks(hooks)\n", + "\n", + "trainer = LocalTrainer()\n", + "trainer.train(task)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the training progresses, you should see `LossLrMeterLoggingHook` printing the loss, learning rate and meter metrics. Checkpoints will be available in the folder created above.\n", + "\n", + "## 6. Conclusion\n", + "\n", + "Video classification is very similar to image classification in Classy Vision, you just need to use an appropriate dataset, model and meters. This tutorial glossed over many details about training, please take a look at our [Getting started](https://classyvision.ai/tutorials/getting_started) tutorial to learn more. Refer to our API reference for more details about [ResNeXt3D](https://classyvision.ai/api/models.html#classy_vision.models.ResNeXt3D) models, [UCF101](https://classyvision.ai/api/dataset.html#classy_vision.dataset.UCF101Dataset) dataset and [VideoAccuracy](http://classyvision.ai/api/meters.html#classy_vision.meters.VideoAccuracyMeter) meters.\n" + ] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/wsl_model_predict.ipynb b/tutorials/wsl_model_predict.ipynb new file mode 100644 index 0000000000..45fd2d3792 --- /dev/null +++ b/tutorials/wsl_model_predict.ipynb @@ -0,0 +1,167 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Copyright (c) Facebook, Inc. and its affiliates.\n", + "# This source code is licensed under the MIT license found in the\n", + "# LICENSE file in the root directory of this source tree." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# TODO(@mannatsingh): use torchhub when the repo is public. The replacement code\n", + "# must be run from the top level directory in the meantime.\n", + "# classy_interface = torch.hub.load(\"facebookresearch/ClassyVision\", \"resnext101_32x8d_wsl\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "path = os.path.abspath(os.path.join(os.path.abspath(\"\"), \"../..\"))\n", + "sys.path.append(path)\n", + "import hubconf\n", + "classy_interface = hubconf.resnext101_32x8d_wsl()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2019-10-17 14:39:31-- https://github.com/pytorch/hub/raw/master/dog.jpg\n", + "Resolving fwdproxy (fwdproxy)... 2401:db00:20ff:ff70:face:b00c:0:1e10\n", + "Connecting to fwdproxy (fwdproxy)|2401:db00:20ff:ff70:face:b00c:0:1e10|:8080... connected.\n", + "Proxy request sent, awaiting response... 302 Found\n", + "Location: https://raw.githubusercontent.com/pytorch/hub/master/dog.jpg [following]\n", + "--2019-10-17 14:39:31-- https://raw.githubusercontent.com/pytorch/hub/master/dog.jpg\n", + "Connecting to fwdproxy (fwdproxy)|2401:db00:20ff:ff70:face:b00c:0:1e10|:8080... connected.\n", + "Proxy request sent, awaiting response... 200 OK\n", + "Length: 661378 (646K) [image/jpeg]\n", + "Saving to: ‘dog.jpg’\n", + "\n", + "100%[======================================>] 661,378 --.-K/s in 0.01s \n", + "\n", + "2019-10-17 14:39:31 (47.0 MB/s) - ‘dog.jpg’ saved [661378/661378]\n", + "\n" + ] + }, + { + "data": { + "image/jpeg": "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U2bGztc07vTezv8Anrm2hgaA9zXcEOIH3NhJwYXvFZh5A3ugaHdP6Vjf8Jv+jusUnuZP3T9ieKXYtzO+s3W+o1hoe7DB8D7idY9zfd7v5LFSec+HA2+tD3Oe7cJMlsO2O/4NzfanPoOqqaIY8CXP/ONk7jO3dY39F6VX0ErId6oIbWS5pLhqQY0D9v8Ab/N+h6f5ijlLIbJBQSezEix1oosd7XgOLzpueIHtj6LWx7k9dlley71PcHkO5BJB3QZ+jsaUfKc31a9zJtc07dXe0CW/ptv0/UZ+kZ/o/Z6iDc5s1jad+0gukzyN5PH6Tcmy4qOp2Qb62qx2Sxln6Z9rBPp2yJOjfft/O+n+ai052fi5bXMucNzQ1pBhp2+5zNfa76X56FYGiQTurFR9w0JbHvDdu9v01MbPTbv2wIDQY2l0/S9pS9dje7Rqmty8nIdYLGN2NG57mF42kf4VrTt9L3t2fuPVN9rbCSWObvBAMAgGdjbOG727Wenv/nFOwv3PNzZcf51rT7QCe3p/SY1mzYhNLRY4NG6yGNLn/S2z+jsidu/agTK9Uv8A/9n/7R0mUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAccAgAAAttGADhCSU0EJQAAAAAAEKjUbjwTObFCP+0SD/wHhpA4QklNA+0AAAAAABAASAAAAAEAAQBIAAAAAQABOEJJTQQmAAAAAAAOAAAAAAAAAAAAAD+AAAA4QklNBA0AAAAAAAQAAACWOEJJTQQZAAAAAAAEAAAAHjhCSU0D8wAAAAAACQAAAAAAAAAAAQA4QklNBAoAAAAAAAEAADhCSU0nEAAAAAAACgABAAAAAAAAAAI4QklNA/UAAAAAAEgAL2ZmAAEAbGZmAAYAAAAAAAEAL2ZmAAEAoZmaAAYAAAAAAAEAMgAAAAEAWgAAAAYAAAAAAAEANQAAAAEALQAAAAYAAAAAAAE4QklNA/gAAAAAAHAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAOEJJTQQIAAAAAAAQAAAAAQAAAkAAAAJAAAAAADhCSU0EHgAAAAAABAAAAAA4QklNBBoAAAAA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eG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgZXhpZjpFeHBvc3VyZVRpbWU9IjEvMTI1IiBleGlmOkZOdW1iZXI9IjM0LzEwIiBleGlmOkV4cG9zdXJlUHJvZ3JhbT0iMiIgZXhpZjpFeGlmVmVyc2lvbj0iMDIyMCIgZXhpZjpEYXRlVGltZU9yaWdpbmFsPSIyMDA0LTA5LTA0VDE1OjAzOjA3LTA1OjAwIiBleGlmOkRhdGVUaW1lRGlnaXRpemVkPSIyMDA0LTA5LTA0VDE1OjAzOjA3LTA1OjAwIiBleGlmOlNodXR0ZXJTcGVlZFZhbHVlPSI3MC8xMCIgZXhpZjpBcGVydHVyZVZhbHVlPSIzNS8xMCIgZXhpZjpFeHBvc3VyZUJpYXNWYWx1ZT0iMC8xMDAwIiBleGlmOk1heEFwZXJ0dXJlVmFsdWU9IjMzLzEwIiBleGlmOk1ldGVyaW5nTW9kZT0iMSIgZXhpZjpMaWdodFNvdXJjZT0iMCIgZXhpZjpGb2NhbExlbmd0aD0iMTAyLzEwIiBleGlmOkZsYXNocGl4VmVyc2lvbj0iMDEwMCIgZXhpZjpDb2xvclNwYWNlPSIxIiBleGlmOlBpeGVsWERpbWVuc2lvbj0iMTU0NiIgZXhpZjpQaXhlbFlEaW1lbnNpb249IjEyMTMiIGV4aWY6RXhwb3N1cmVJbmRleD0iMTAwLzEiIGV4aWY6U2Vuc2luZ01ldGhvZD0iMiIgZXhpZjpGaWxlU291cmNlPSIzIiBleGlmOlNjZW5lVHlwZT0iMSIgZXhpZjpOYXRpdmVEaWdlc3Q9IjM2ODY0LDQwOTYwLDQwOTYxLDM3MTIxLDM3MTIyLDQwOTYyLDQwOTYzLDM3NTEwLDQwOTY0LDM2ODY3LDM2ODY4LDMzNDM0LDMzNDM3LDM0ODUwLDM0ODUyLDM0ODU1LDM0ODU2LDM3Mzc3LDM3Mzc4LDM3Mzc5LDM3MzgwLDM3MzgxLDM3MzgyLDM3MzgzLDM3Mzg0LDM3Mzg1LDM3Mzg2LDM3Mzk2LDQxNDgzLDQxNDg0LDQxNDg2LDQxNDg3LDQxNDg4LDQxNDkyLDQxNDkzLDQxNDk1LDQxNzI4LDQxNzI5LDQxNzMwLDQxOTg1LDQxOTg2LDQxOTg3LDQxOTg4LDQxOTg5LDQxOTkwLDQxOTkxLDQxOTkyLDQxOTkzLDQxOTk0LDQxOTk1LDQxOTk2LDQyMDE2LDAsMiw0LDUsNiw3LDgsOSwxMCwxMSwxMiwxMywxNCwxNSwxNiwxNywxOCwyMCwyMiwyMywyNCwyNSwyNiwyNywyOCwzMDtCQzU3NjMyOUEwRkI1QTc1RENDNUMyRjdEQTAwRDkyOCIgdGlmZjpNYWtlPSJFQVNUTUFOIEtPREFLIENPTVBBTlkiIHRpZmY6TW9kZWw9IktPREFLIERYMzkwMCBaT09NIERJR0lUQUwgQ0FNRVJBIiB0aWZmOk9yaWVudGF0aW9uPSIxIiB0aWZmOlhSZXNvbHV0aW9uPSI3MjAwMDAvMTAwMDAiIHRpZmY6WVJlc29sdXRpb249IjcyMDAwMC8xMDAwMCIgdGlmZjpSZXNvbHV0aW9uVW5pdD0iMiIgdGlmZjpZQ2JDclBvc2l0aW9uaW5nPSIxIiB0aWZmOk5hdGl2ZURpZ2VzdD0iMjU2LDI1NywyNTgsMjU5LDI2MiwyNzQsMjc3LDI4NCw1MzAsNTMxLDI4MiwyODMsMjk2LDMwMSwzMTgsMzE5LDUyOSw1MzIsMzA2LDI3MCwyNzEsMjcyLDMwNSwzMTUsMzM0MzI7ODJBRjM2ODE3RDIzMTNFQzA3M0MwNUI0RjU4RjE2QUQiIHhtcDpDcmVhdGVEYXRlPSIyMDA0LTA5LTA0VDE1OjAzOjA3LTA1OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAwOS0wOS0xNFQyMjowNDowOC0wNzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAwOS0wOS0xNFQyMjowNDowOC0wNzowMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1MgV2luZG93cyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjdjZjNmM2MwLWNiZDQtMTFkOS1iZTE0LWE0Y2JmMGM0YmE0ZSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowODE5RkQwQUIzQTFERTExQjBBOEFGQjIyNDNDQThCNCIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6N2NmM2YzYzAtY2JkNC0xMWQ5LWJlMTQtYTRjYmYwYzRiYTRlIiBkYzpmb3JtYXQ9ImltYWdlL2pwZWciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wUmlnaHRzOk1hcmtlZD0iRmFsc2UiPiA8ZXhpZjpDb21wb25lbnRzQ29uZmlndXJhdGlvbj4gPHJkZjpTZXE+IDxyZGY6bGk+MTwvcmRmOmxpPiA8cmRmOmxpPjI8L3JkZjpsaT4gPHJkZjpsaT4zPC9yZGY6bGk+IDxyZGY6bGk+MDwvcmRmOmxpPiA8L3JkZjpTZXE+IDwvZXhpZjpDb21wb25lbnRzQ29uZmlndXJhdGlvbj4gPGV4aWY6Rmxhc2ggZXhpZjpGaXJlZD0iRmFsc2UiIGV4aWY6UmV0dXJuPSIwIiBleGlmOk1vZGU9IjAiIGV4aWY6RnVuY3Rpb249IkZhbHNlIiBleGlmOlJlZEV5ZU1vZGU9IkZhbHNlIi8+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjA4MTlGRDBBQjNBMURFMTFCMEE4QUZCMjI0M0NBOEI0IiBzdEV2dDp3aGVuPSIyMDA5LTA5LTE0VDIyOjA0OjA4LTA3OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M0IFdpbmRvd3MiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/eHBhY2tldCBlbmQ9InciPz7/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQg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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": { + "bento_obj_id": "140494349111984" + }, + "output_type": "execute_result" + } + ], + "source": [ + "# Download an example image from the pytorch website\n", + "!wget https://github.com/pytorch/hub/raw/master/dog.jpg -O dog.jpg\n", + "from IPython.display import Image\n", + "Image(filename='dog.jpg')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "258" + ] + }, + "execution_count": 7, + "metadata": { + "bento_obj_id": "140493963418320" + }, + "output_type": "execute_result" + } + ], + "source": [ + "dataset = classy_interface.create_image_dataset([\"./dog.jpg\"], split=\"test\")\n", + "data_iterator = classy_interface.get_data_iterator(dataset)\n", + "input = next(data_iterator)\n", + "# set the model to eval mode\n", + "classy_interface.eval()\n", + "output = classy_interface.predict(input)\n", + "# see the prediction for the input\n", + "classy_interface.predict(input).argmax().item()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "bento_stylesheets": { + "bento/extensions/flow/main.css": true, + "bento/extensions/kernel_selector/main.css": true, + "bento/extensions/kernel_ui/main.css": true, + "bento/extensions/new_kernel/main.css": true, + "bento/extensions/system_usage/main.css": true, + "bento/extensions/theme/main.css": true + }, + "kernelspec": { + "display_name": "pytorch", + "language": "python", + "name": "bento_kernel_pytorch" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.3rc1+" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000000..7391779aba --- /dev/null +++ b/website/README.md @@ -0,0 +1,193 @@ +This website was created with [Docusaurus](https://docusaurus.io/). + +# What's In This Document + +* [Get Started in 5 Minutes](#get-started-in-5-minutes) +* [Directory Structure](#directory-structure) +* [Editing Content](#editing-content) +* [Adding Content](#adding-content) +* [Full Documentation](#full-documentation) + +# Get Started in 5 Minutes + +1. Make sure all the dependencies for the website are installed: + +```sh +# Install dependencies +$ yarn +``` +2. Run your dev server: + +```sh +# Start the site +$ yarn start +``` + +## Directory Structure + +Your project file structure should look something like this + +``` +my-docusaurus/ + docs/ + doc-1.md + doc-2.md + doc-3.md + website/ + blog/ + 2016-3-11-oldest-post.md + 2017-10-24-newest-post.md + core/ + node_modules/ + pages/ + static/ + css/ + img/ + package.json + sidebar.json + siteConfig.js +``` + +# Editing Content + +## Editing an existing docs page + +Edit docs by navigating to `docs/` and editing the corresponding document: + +`docs/doc-to-be-edited.md` + +```markdown +--- +id: page-needs-edit +title: This Doc Needs To Be Edited +--- + +Edit me... +``` + +For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Editing an existing blog post + +Edit blog posts by navigating to `website/blog` and editing the corresponding post: + +`website/blog/post-to-be-edited.md` +```markdown +--- +id: post-needs-edit +title: This Blog Post Needs To Be Edited +--- + +Edit me... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +# Adding Content + +## Adding a new docs page to an existing sidebar + +1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: + +```md +--- +id: newly-created-doc +title: This Doc Needs To Be Edited +--- + +My new content here.. +``` + +1. Refer to that doc's ID in an existing sidebar in `website/sidebar.json`: + +```javascript +// Add newly-created-doc to the Getting Started category of docs +{ + "docs": { + "Getting Started": [ + "quick-start", + "newly-created-doc" // new doc here + ], + ... + }, + ... +} +``` + +For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding a new blog post + +1. Make sure there is a header link to your blog in `website/siteConfig.js`: + +`website/siteConfig.js` +```javascript +headerLinks: [ + ... + { blog: true, label: 'Blog' }, + ... +] +``` + +2. Create the blog post with the format `YYYY-MM-DD-My-Blog-Post-Title.md` in `website/blog`: + +`website/blog/2018-05-21-New-Blog-Post.md` + +```markdown +--- +author: Frank Li +authorURL: https://twitter.com/foobarbaz +authorFBID: 503283835 +title: New Blog Post +--- + +Lorem Ipsum... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +## Adding items to your site's top navigation bar + +1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: + +`website/siteConfig.js` +```javascript +{ + headerLinks: [ + ... + /* you can add docs */ + { doc: 'my-examples', label: 'Examples' }, + /* you can add custom pages */ + { page: 'help', label: 'Help' }, + /* you can add external links */ + { href: 'https://github.com/facebook/docusaurus', label: 'GitHub' }, + ... + ], + ... +} +``` + +For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding custom pages + +1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: +1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: + +`website/siteConfig.js` +```javascript +{ + headerLinks: [ + ... + { page: 'my-new-custom-page', label: 'My New Custom Page' }, + ... + ], + ... +} +``` + +For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). + +# Full Documentation + +Full documentation can be found on the [website](https://docusaurus.io/). diff --git a/website/core/Footer.js b/website/core/Footer.js new file mode 100644 index 0000000000..fec406770b --- /dev/null +++ b/website/core/Footer.js @@ -0,0 +1,111 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +const PropTypes = require("prop-types"); +const React = require("react"); + +function SocialFooter(props) { + const repoUrl = `https://github.com/${props.config.organizationName}/${props.config.projectName}`; + return ( + + ); +} + +SocialFooter.propTypes = { + config: PropTypes.object +}; + +class Footer extends React.Component { + docUrl(doc, language) { + const baseUrl = this.props.config.baseUrl; + const docsUrl = this.props.config.docsUrl; + const docsPart = `${docsUrl ? `${docsUrl}/` : ""}`; + const langPart = `${language ? `${language}/` : ""}`; + return `${baseUrl}${docsPart}${langPart}${doc}`; + } + + pageUrl(doc, language) { + const baseUrl = this.props.config.baseUrl; + return baseUrl + (language ? `${language}/` : "") + doc; + } + + render() { + const currentYear = new Date().getFullYear(); + return ( +