diff --git a/MANIFEST.in b/MANIFEST.in index cd797d8..56e7a5b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include LICENSE.txt -include README.md \ No newline at end of file +include README.md +recursive-include bootstrap/templates *py \ No newline at end of file diff --git a/bootstrap/new.py b/bootstrap/new.py new file mode 100644 index 0000000..c57b453 --- /dev/null +++ b/bootstrap/new.py @@ -0,0 +1,57 @@ +from pathlib import Path +from argparse import ArgumentParser + + +def replace_content(file_path, prj_name): + content = file_path.read_text() + content = content.replace('{PROJECT_NAME}', prj_name) + content = content.replace('{PROJECT_NAME_LOWER}', prj_name.lower()) + content = content.replace(' # noqa: E999', '') + return content + + +def new_project(prj_name, prj_dir): + # will be rename into project_name.lower() + suffix + # ex: dataset.py -> myproject.py + files_to_rename = ['dataset.py', 'criterion.py', 'metric.py', 'network.py', 'options.yaml'] + + # will be rename into project_name.lower() + # ex: project/datasets -> myproject/datasets + dirs_to_rename = ['project'] + + path = Path(prj_dir) + path = path / f'{prj_name.lower()}.bootstrap.pytorch' + path.mkdir() + tpl_path = Path(__file__).parent / 'templates' / 'default' + + print(f'Creating project {prj_name.lower()} in {path}') + + # recursive iteration over directories and files + for p in tpl_path.rglob('*'): + + # absolute path to local path + # ex: bootstrap.pytorch/templates/default/project -> project + tpl_local_path = p.relative_to(tpl_path) + + # replace name of directories + local_path = p.relative_to(tpl_path) + for dir_name in dirs_to_rename: + local_path = Path(str(local_path).replace(dir_name, prj_name.lower())) + + if p.is_dir(): + Path(path / local_path).mkdir() + + if p.is_file(): + content = replace_content(tpl_path / tpl_local_path, prj_name) + if p.name in files_to_rename: + local_path = Path(local_path.parent / f'{prj_name.lower()}{p.suffix}') + print(local_path) + Path(path / local_path).write_text(content) + + +if __name__ == '__main__': + parser = ArgumentParser() + parser.add_argument('--project_name', type=str, default='MyProject') + parser.add_argument('--project_dir', type=str, default='.') + args = parser.parse_args() + new_project(args.project_name, args.project_dir) diff --git a/bootstrap/templates/default/.gitignore b/bootstrap/templates/default/.gitignore new file mode 100644 index 0000000..616dfb7 --- /dev/null +++ b/bootstrap/templates/default/.gitignore @@ -0,0 +1,118 @@ +# Boostrap.pytorch +logs/* +data/* +!.gitkeep +docs/src +!logger.py + +# Apple +.DS_Store +._.DS_Store + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +# lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +*.swp +*.nfs* diff --git a/bootstrap/templates/default/LICENSE.txt b/bootstrap/templates/default/LICENSE.txt new file mode 100644 index 0000000..121c97f --- /dev/null +++ b/bootstrap/templates/default/LICENSE.txt @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020+, {PROJECT_NAME} +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bootstrap/templates/default/MANIFEST.in b/bootstrap/templates/default/MANIFEST.in new file mode 100644 index 0000000..cd797d8 --- /dev/null +++ b/bootstrap/templates/default/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE.txt +include README.md \ No newline at end of file diff --git a/bootstrap/templates/default/README.md b/bootstrap/templates/default/README.md new file mode 100644 index 0000000..0c49a14 --- /dev/null +++ b/bootstrap/templates/default/README.md @@ -0,0 +1,33 @@ +# {PROJECT_NAME} + +## Install + +[Conda](https://docs.conda.io/en/latest/miniconda.html) + +```bash +conda create --name {PROJECT_NAME_LOWER} python=3 +source activate {PROJECT_NAME_LOWER} + +cd $HOME +git clone --recursive https://github.com/{PROJECT_NAME}/{PROJECT_NAME_LOWER}.bootstrap.pytorch.git +cd {PROJECT_NAME_LOWER}.bootstrap.pytorch +pip install -r requirements.txt +``` + +## Reproducing results + +Run experiment: +```bash +python -m bootstrap.run \ +-o {PROJECT_NAME_LOWER}/options/{PROJECT_NAME_LOWER}.yaml \ +--exp.dir logs/{PROJECT_NAME_LOWER}/1_exp +``` + +Display training and evaluation figures: +```bash +open logs/{PROJECT_NAME_LOWER}/1_exp/view.html +``` + +Display table of results: +```bash +python -m bootstrap.compare -o \ No newline at end of file diff --git a/bootstrap/templates/default/project/__version__.py b/bootstrap/templates/default/project/__version__.py new file mode 100644 index 0000000..c57bfd5 --- /dev/null +++ b/bootstrap/templates/default/project/__version__.py @@ -0,0 +1 @@ +__version__ = '0.0.0' diff --git a/bootstrap/templates/default/project/datasets/__init__.py b/bootstrap/templates/default/project/datasets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bootstrap/templates/default/project/datasets/dataset.py b/bootstrap/templates/default/project/datasets/dataset.py new file mode 100644 index 0000000..142f8a1 --- /dev/null +++ b/bootstrap/templates/default/project/datasets/dataset.py @@ -0,0 +1,54 @@ +import torch +import torch.utils.data as tdata +from bootstrap.datasets import transforms as btf + + +class {PROJECT_NAME}Dataset(tdata.Dataset): # noqa: E999 + + def __init__( + self, + dir_data, + split='train', + batch_size=4, + shuffle=False, + pin_memory=False, + nb_threads=4, + *args, + **kwargs): + self.dir_data = dir_data + self.split = split + self.batch_size = batch_size + self.shuffle = shuffle + self.pin_memory = pin_memory + self.nb_threads = nb_threads + self.sampler = None + + self.collate_fn = btf.Compose([ + btf.ListDictsToDictLists(), + btf.StackTensors() + ]) + + self.nb_items = kwargs['nb_items'] + self.data = torch.randn(self.nb_items, 10) + self.target = torch.zeros(self.nb_items) + self.target[:int(self.nb_items / 2)].fill_(1) + + def make_batch_loader(self, batch_size=None, shuffle=None): + batch_loader = tdata.DataLoader( + dataset=self, + batch_size=self.batch_size if batch_size is None else batch_size, + shuffle=self.shuffle if shuffle is None else shuffle, + pin_memory=self.pin_memory, + num_workers=self.nb_threads, + collate_fn=self.collate_fn, + sampler=self.sampler) + return batch_loader + + def __len__(self): + return self.data.shape[0] + + def __getitem__(self, idx): + item = {} + item['data'] = self.data[idx] + item['target'] = self.target[idx] + return item diff --git a/bootstrap/templates/default/project/datasets/factory.py b/bootstrap/templates/default/project/datasets/factory.py new file mode 100644 index 0000000..5ece23d --- /dev/null +++ b/bootstrap/templates/default/project/datasets/factory.py @@ -0,0 +1,40 @@ +from bootstrap.lib.options import Options +from bootstrap.lib.logger import Logger +from .{PROJECT_NAME_LOWER} import {PROJECT_NAME}Dataset # noqa: E999 + + +def factory(engine=None): + Logger()('Creating dataset...') + + opt = Options()['dataset'] + + dataset = {} + + if opt.get('train_split', None): + dataset['train'] = factory_split(opt['train_split']) + + if opt.get('eval_split', None): + dataset['eval'] = factory_split(opt['eval_split']) + + return dataset + + +def factory_split(split): + opt = Options()['dataset'] + + shuffle = ('train' in split) + + dict_opt = opt.asdict() + dict_opt.pop('dir', None) + dict_opt.pop('batch_size', None) + dict_opt.pop('nb_threads', None) + dataset = {PROJECT_NAME}Dataset( # noqa: E999 + dir_data=opt['dir'], + split=split, + batch_size=opt['batch_size'], + shuffle=shuffle, + nb_threads=opt['nb_threads'], + **dict_opt + ) + + return dataset diff --git a/bootstrap/templates/default/project/models/__init__.py b/bootstrap/templates/default/project/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bootstrap/templates/default/project/models/criterions/__init__.py b/bootstrap/templates/default/project/models/criterions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bootstrap/templates/default/project/models/criterions/criterion.py b/bootstrap/templates/default/project/models/criterions/criterion.py new file mode 100644 index 0000000..5630b4e --- /dev/null +++ b/bootstrap/templates/default/project/models/criterions/criterion.py @@ -0,0 +1,16 @@ +import torch +import torch.nn as nn + + +class {PROJECT_NAME}Criterion(nn.Module): # noqa: E999 + + def __init__(self, *args, **kwargs): + super({PROJECT_NAME}Criterion, self).__init__() # noqa: E999 + self.bce_loss = nn.BCELoss() + + def forward(self, net_out, batch): + pred = net_out['pred'].squeeze(1) + target = batch['target'] + loss = self.bce_loss(pred, target) + out = {'loss': loss} + return out diff --git a/bootstrap/templates/default/project/models/criterions/factory.py b/bootstrap/templates/default/project/models/criterions/factory.py new file mode 100644 index 0000000..538623d --- /dev/null +++ b/bootstrap/templates/default/project/models/criterions/factory.py @@ -0,0 +1,13 @@ +from bootstrap.lib.options import Options +from .{PROJECT_NAME_LOWER} import {PROJECT_NAME}Criterion # noqa: E999 + + +def factory(engine=None, mode=None): + opt = Options()['model.criterion'] + + if opt['name'] == '{PROJECT_NAME_LOWER}': + criterion = {PROJECT_NAME}Criterion(**opt) # noqa: E999 + else: + raise ValueError(opt['name']) + + return criterion diff --git a/bootstrap/templates/default/project/models/metrics/__init__.py b/bootstrap/templates/default/project/models/metrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bootstrap/templates/default/project/models/metrics/factory.py b/bootstrap/templates/default/project/models/metrics/factory.py new file mode 100644 index 0000000..36f91bb --- /dev/null +++ b/bootstrap/templates/default/project/models/metrics/factory.py @@ -0,0 +1,13 @@ +from bootstrap.lib.options import Options +from .{PROJECT_NAME_LOWER} import {PROJECT_NAME}Metric # noqa: E999 + + +def factory(engine=None, mode='train'): + opt = Options()['model.metric'] + + if opt['name'] == '{PROJECT_NAME_LOWER}': + metric = {PROJECT_NAME}Metric(**opt) # noqa: E999 + else: + raise ValueError(opt['name']) + + return metric diff --git a/bootstrap/templates/default/project/models/metrics/metric.py b/bootstrap/templates/default/project/models/metrics/metric.py new file mode 100644 index 0000000..3c8147b --- /dev/null +++ b/bootstrap/templates/default/project/models/metrics/metric.py @@ -0,0 +1,15 @@ +import torch.nn as nn + + +class {PROJECT_NAME}Metric(nn.Module): # noqa: E999 + + def __init__(self, *args, **kwargs): + super({PROJECT_NAME}Metric, self).__init__() # noqa: E999 + self.thresh = kwargs['thresh'] + + def forward(self, cri_out, net_out, batch): + pred = net_out['pred'] > self.thresh + target = batch['target'] + acc = (pred == target).float().mean() + out = {'accuracy': acc} + return out diff --git a/bootstrap/templates/default/project/models/networks/__init__.py b/bootstrap/templates/default/project/models/networks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bootstrap/templates/default/project/models/networks/factory.py b/bootstrap/templates/default/project/models/networks/factory.py new file mode 100644 index 0000000..e1d45b9 --- /dev/null +++ b/bootstrap/templates/default/project/models/networks/factory.py @@ -0,0 +1,17 @@ +import torch +from bootstrap.lib.options import Options +from bootstrap.models.networks.data_parallel import DataParallel +from .{PROJECT_NAME_LOWER} import {PROJECT_NAME}Network # noqa: E999 + + +def factory(engine): + opt = Options()['model.network'] + + if opt['name'] == '{PROJECT_NAME_LOWER}': + net = {PROJECT_NAME}Network(**opt) # noqa: E999 + else: + raise ValueError(opt['name']) + + if torch.cuda.device_count() > 1: + net = DataParallel(net) + return net diff --git a/bootstrap/templates/default/project/models/networks/network.py b/bootstrap/templates/default/project/models/networks/network.py new file mode 100644 index 0000000..f3653dc --- /dev/null +++ b/bootstrap/templates/default/project/models/networks/network.py @@ -0,0 +1,16 @@ +import torch.nn as nn + + +class {PROJECT_NAME}Network(nn.Module): # noqa: E999 + + def __init__(self, *args, **kwargs): + super({PROJECT_NAME}Network, self).__init__() # noqa: E999 + self.net = nn.Sequential( + nn.Linear(kwargs['dim_in'], kwargs['dim_out']), + nn.Sigmoid()) + + def forward(self, batch): + x = batch['data'] + y = self.net(x) + out = {'pred': y} + return out diff --git a/bootstrap/templates/default/project/options/options.yaml b/bootstrap/templates/default/project/options/options.yaml new file mode 100644 index 0000000..bf7f6ec --- /dev/null +++ b/bootstrap/templates/default/project/options/options.yaml @@ -0,0 +1,46 @@ +exp: + dir: logs/{PROJECT_NAME_LOWER}/1_exp + resume: # last, best_[...], or empty (from scratch) +dataset: + import: {PROJECT_NAME_LOWER}.datasets.factory + name: {PROJECT_NAME_LOWER} + dir: data/{PROJECT_NAME_LOWER} + train_split: train + eval_split: val + nb_threads: 4 + batch_size: 64 + nb_items: 100 +model: + name: default + network: + import: {PROJECT_NAME_LOWER}.models.networks.factory + name: {PROJECT_NAME_LOWER} + dim_in: 10 + dim_out: 1 + criterion: + import: {PROJECT_NAME_LOWER}.models.criterions.factory + name: {PROJECT_NAME_LOWER} + metric: + import: {PROJECT_NAME_LOWER}.models.metrics.factory + name: {PROJECT_NAME_LOWER} + thresh: 0.5 +optimizer: + name: adam + lr: 0.0004 +engine: + name: default + debug: False + nb_epochs: 10 + print_freq: 10 + saving_criteria: + - loss:min # save when new_best < best + - accuracy:max # save when new_best > best +misc: + cuda: True + seed: 1337 +views: + name: plotly + items: + - logs:train_epoch.loss+logs:eval_epoch.loss + - logs:train_batch.loss + - logs:train_epoch.accuracy+logs:eval_epoch.accuracy diff --git a/bootstrap/templates/default/requirements.txt b/bootstrap/templates/default/requirements.txt new file mode 100644 index 0000000..4446a1f --- /dev/null +++ b/bootstrap/templates/default/requirements.txt @@ -0,0 +1 @@ +bootstrap.pytorch \ No newline at end of file diff --git a/bootstrap/templates/default/setup.cfg b/bootstrap/templates/default/setup.cfg new file mode 100644 index 0000000..224a779 --- /dev/null +++ b/bootstrap/templates/default/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md \ No newline at end of file diff --git a/bootstrap/templates/default/setup.py b/bootstrap/templates/default/setup.py new file mode 100644 index 0000000..1718493 --- /dev/null +++ b/bootstrap/templates/default/setup.py @@ -0,0 +1,151 @@ +"""A setuptools based setup module. + +See: +https://packaging.python.org/en/latest/distributing.html +https://github.com/pypa/sampleproject +""" + +# Always prefer setuptools over distutils +from setuptools import setup, find_packages +# To use a consistent encoding +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +# Arguments marked as "Required" below must be included for upload to PyPI. +# Fields marked as "Optional" may be commented out. + +# https://stackoverflow.com/questions/458550/standard-way-to-embed-version-into-python-package/16084844#16084844 +exec(open(path.join(here, '{PROJECT_NAME_LOWER}', '__version__.py')).read()) +setup( + # This is the name of your project. The first time you publish this + # package, this name will be registered for you. It will determine how + # users can install this project, e.g.: + # + # $ pip install sampleproject + # + # And where it will live on PyPI: https://pypi.org/project/sampleproject/ + # + # There are some restrictions on what makes a valid project name + # specification here: + # https://packaging.python.org/specifications/core-metadata/#name + name='{PROJECT_NAME_LOWER}.bootstrap.pytorch', # Required + + # Versions should comply with PEP 440: + # https://www.python.org/dev/peps/pep-0440/ + # + # For a discussion on single-sourcing the version across setup.py and the + # project code, see + # https://packaging.python.org/en/latest/single_source_version.html + version=__version__, # noqa: F821 # Required + + # This is a one-line description or tagline of what your project does. This + # corresponds to the "Summary" metadata field: + # https://packaging.python.org/specifications/core-metadata/#summary + description='{PROJECT_NAME}', # Required + + # This is an optional longer description of your project that represents + # the body of text which users will see when they visit PyPI. + # + # Often, this is the same as your README, so you can just read it in from + # that file directly (as we have already done above) + # + # This field corresponds to the "Description" metadata field: + # https://packaging.python.org/specifications/core-metadata/#description-optional + long_description=long_description, # Optional + + # This should be a valid link to your project's main homepage. + # + # This field corresponds to the "Home-Page" metadata field: + # https://packaging.python.org/specifications/core-metadata/#home-page-optional + url='https://github.com/{PROJECT_NAME}/{PROJECT_NAME_LOWER}.bootstrap.pytorch', # noqa: E501 # Optional + + # This should be your name or the name of the organization which owns the + # project. + author='{PROJECT_NAME}', # Optional + + # This should be a valid email address corresponding to the author listed + # above. + author_email='contact@{PROJECT_NAME_LOWER}.com', # Optional + + # Classifiers help users find your project by categorizing it. + # + # For a list of valid classifiers, see + # https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ # Optional + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + + # Indicate who your project is intended for + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + + # Pick your license as you wish + 'License :: OSI Approved :: MIT License', + + # Specify the Python versions you support here. In particular, ensure + # that you indicate whether you support Python 2, Python 3 or both. + 'Programming Language :: Python :: 3.7', + ], + + # This field adds keywords for your project which will appear on the + # project page. What does your project relate to? + # + # Note that this is a string of words separated by whitespace, not a list. + keywords='pytorch framework bootstrap deep learning scaffolding', # noqa: E501 # Optional + + # You can just specify package directories manually here if your project is + # simple. Or you can use find_packages(). + # + # Alternatively, if you just want to distribute a single Python file, use + # the `py_modules` argument instead as follows, which will expect a file + # called `my_module.py` to exist: + # + # py_modules=["my_module"], + # + packages=find_packages(exclude=[ + 'data', + 'logs', + ]), + + # This field lists other packages that your project depends on to run. + # Any package you put here will be installed by pip when your project is + # installed, so they must be valid existing projects. + # + # For an analysis of "install_requires" vs pip's requirements files see: + # https://packaging.python.org/en/latest/requirements.html + install_requires=[ + 'bootstrap.pytorch' + ], + + # List additional groups of dependencies here (e.g. development + # dependencies). Users will be able to install these using the "extras" + # syntax, for example: + # + # $ pip install sampleproject[dev] + # + # Similar to `install_requires` above, these must be valid existing + # projects. + # extras_require={ # Optional + # 'dev': ['check-manifest'], + # 'test': ['coverage'], + # }, + + # If there are data files included in your packages that need to be + # installed, specify them here. + # + # If using Python 2.6 or earlier, then these have to be included in + # MANIFEST.in as well. + # package_data={ # Optional + # 'sample': ['package_data.dat'], + # }, + include_package_data=True, +) diff --git a/setup.py b/setup.py index fad6fbf..6b50ea4 100644 --- a/setup.py +++ b/setup.py @@ -156,6 +156,7 @@ # package_data={ # Optional # 'sample': ['package_data.dat'], # }, + include_package_data=True, # Although 'package_data' is the preferred approach, in some case you may # need to place data files outside of your packages. See: diff --git a/tests/test_new.py b/tests/test_new.py new file mode 100644 index 0000000..851a808 --- /dev/null +++ b/tests/test_new.py @@ -0,0 +1,29 @@ +import os +import pytest + +def test_new(tmpdir): + exit_status = os.system(f'python -m bootstrap.new --project_name MyProject --project_dir {tmpdir}') + assert exit_status == 0 + + os.chdir(os.path.join(tmpdir, 'myproject.bootstrap.pytorch')) + exit_status = os.system('python -m bootstrap.run -o myproject/options/myproject.yaml --exp.dir logs/myproject/1_exp --misc.cuda False --engine.nb_epochs 10') + assert exit_status == 0 + + fnames = [ + 'ckpt_best_accuracy_engine.pth.tar', + 'ckpt_best_loss_optimizer.pth.tar', + 'logs.txt', + 'ckpt_best_accuracy_model.pth.tar', + 'ckpt_last_engine.pth.tar', + 'options.yaml', + 'ckpt_best_accuracy_optimizer.pth.tar', + 'ckpt_last_model.pth.tar', + 'view.html', + 'ckpt_best_loss_engine.pth.tar', + 'ckpt_last_optimizer.pth.tar', + 'ckpt_best_loss_model.pth.tar', + 'logs.json' + ] + + for fname in fnames: + assert os.path.isfile(f'logs/myproject/1_exp/{fname}')