├── data
└── .gitkeep
├── logs
└── .gitkeep
├── recipe1m
├── __init__.py
├── models
│ ├── __init__.py
│ ├── criterions
│ │ ├── __init__.py
│ │ ├── pairwise.py
│ │ ├── trijoint.py
│ │ └── triplet.py
│ ├── metrics
│ │ ├── __init__.py
│ │ ├── utils.py
│ │ └── trijoint.py
│ ├── networks
│ │ ├── __init__.py
│ │ └── trijoint.py
│ ├── factory.py
│ └── trijoint.py
├── visu
│ ├── __init__.py
│ ├── ingrs_count.py
│ ├── old_top5.py
│ ├── calculate_mean_features.py
│ ├── mean_to_images.py
│ ├── ingrs_to_images.py
│ ├── old_ingrs_to_img_by_class.py
│ ├── modality_to_modality_top5.py
│ ├── make_menu.py
│ ├── remove_ingrs.py
│ ├── ingrs_to_images_per_class.py
│ ├── old_tsne.py
│ └── old_top5_old.py
├── datasets
│ ├── __init__.py
│ ├── factory.py
│ ├── batch_sampler.py
│ └── recipe1m.py
├── optimizers
│ ├── __init__.py
│ ├── factory.py
│ └── trijoint.py
├── options
│ ├── pairwise.yaml
│ ├── pairwise_plus.yaml
│ ├── lifted_struct.yaml
│ ├── max.yaml
│ ├── semi_hard.yaml
│ ├── avg_nosem.yaml
│ ├── triplet.yaml
│ ├── avg.yaml
│ ├── adamine.yaml
│ └── abstract.yaml
├── extract.py
└── api.py
├── images
├── model.png
└── task.png
├── demo_web
├── images
│ ├── loading.gif
│ ├── logos.png
│ └── walle.jpg
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── css
│ ├── custom.css
│ └── bootstrap-theme.min.css
├── js
│ ├── npm.js
│ └── custom.js
└── index.html
├── requirements.txt
├── .gitignore
└── README.md
/data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/logs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/recipe1m/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/recipe1m/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/recipe1m/visu/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/recipe1m/datasets/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/recipe1m/optimizers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/recipe1m/models/criterions/__init__.py:
--------------------------------------------------------------------------------
1 | from .trijoint import Trijoint
--------------------------------------------------------------------------------
/recipe1m/models/metrics/__init__.py:
--------------------------------------------------------------------------------
1 | from .trijoint import Trijoint
--------------------------------------------------------------------------------
/recipe1m/models/networks/__init__.py:
--------------------------------------------------------------------------------
1 | from .trijoint import Trijoint
--------------------------------------------------------------------------------
/images/model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/images/model.png
--------------------------------------------------------------------------------
/images/task.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/images/task.png
--------------------------------------------------------------------------------
/demo_web/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/demo_web/images/loading.gif
--------------------------------------------------------------------------------
/demo_web/images/logos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/demo_web/images/logos.png
--------------------------------------------------------------------------------
/demo_web/images/walle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/demo_web/images/walle.jpg
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | torch>=1.0
2 | torchvision
3 | lmdb
4 | pretrainedmodels
5 | bootstrap.pytorch
6 | tqdm
7 | werkzeug
8 |
--------------------------------------------------------------------------------
/demo_web/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/demo_web/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/demo_web/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/demo_web/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/demo_web/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/demo_web/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/demo_web/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cadene/recipe1m.bootstrap.pytorch/HEAD/demo_web/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/recipe1m/optimizers/factory.py:
--------------------------------------------------------------------------------
1 | from bootstrap.lib.options import Options
2 | from .trijoint import Trijoint
3 |
4 | def factory(model, engine=None):
5 |
6 | if Options()['optimizer']['name'] == 'trijoint_fixed_fine_tune':
7 | optimizer = Trijoint(Options()['optimizer'], model, engine)
8 | else:
9 | raise ValueError()
10 |
11 | return optimizer
12 |
13 |
--------------------------------------------------------------------------------
/recipe1m/options/pairwise.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/pairwise
4 | dataset:
5 | freq_mismatch: 0.80
6 | model:
7 | with_classif: True
8 | criterion:
9 | name: trijoint
10 | weight_classif: 0.01
11 | keep_background: False
12 | retrieval_strategy:
13 | name: pairwise_pytorch # quadruplet, triplet, pairwise, or pairwise_pytorch
14 | margin: 0.1
--------------------------------------------------------------------------------
/recipe1m/models/factory.py:
--------------------------------------------------------------------------------
1 | from bootstrap.lib.options import Options
2 | from .trijoint import Trijoint
3 |
4 | def factory(engine=None):
5 |
6 | if Options()['model.name'] == 'trijoint':
7 | model = Trijoint(
8 | Options()['model'],
9 | Options()['dataset.nb_classes'],
10 | engine.dataset.keys(),
11 | engine)
12 | else:
13 | raise ValueError()
14 |
15 | return model
16 |
17 |
--------------------------------------------------------------------------------
/recipe1m/options/pairwise_plus.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/pairwise_plus
4 | dataset:
5 | freq_mismatch: 0.80
6 | model:
7 | with_classif: True
8 | criterion:
9 | name: trijoint
10 | weight_classif: 0.01
11 | keep_background: False
12 | retrieval_strategy:
13 | name: pairwise # quadruplet, triplet, pairwise, or pairwise_pytorch
14 | pos_margin: 0.3
15 | neg_margin: 0.9
--------------------------------------------------------------------------------
/demo_web/css/custom.css:
--------------------------------------------------------------------------------
1 | .btn-file {
2 | position: relative;
3 | overflow: hidden;
4 | }
5 | .btn-file input[type=file] {
6 | position: absolute;
7 | top: 0;
8 | right: 0;
9 | min-width: 100%;
10 | min-height: 100%;
11 | font-size: 100px;
12 | text-align: right;
13 | filter: alpha(opacity=0);
14 | opacity: 0;
15 | outline: none;
16 | background: white;
17 | cursor: inherit;
18 | display: block;
19 | }
20 |
21 | #vqa-visual{
22 | width: 300px;
23 | }
--------------------------------------------------------------------------------
/demo_web/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------
/recipe1m/options/lifted_struct.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/lifted_struct
4 | dataset:
5 | freq_mismatch: 0.0
6 | model:
7 | with_classif: False
8 | criterion:
9 | name: trijoint
10 | keep_background: False
11 | retrieval_strategy:
12 | name: triplet # quadruplet, triplet, pairwise, or pairwise_pytorch
13 | margin: 0.3
14 | sampling: max_negative # random (outdated), max_negative, or prob_negative
15 | nb_samples: 9999
16 | aggregation: valid # mean, valid
17 | substrategy:
18 | - LIFT
19 | substrategy_weights:
20 | - 1.0
--------------------------------------------------------------------------------
/recipe1m/options/max.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/max
4 | dataset:
5 | freq_mismatch: 0.0
6 | model:
7 | with_classif: False
8 | criterion:
9 | name: trijoint
10 | keep_background: False
11 | retrieval_strategy:
12 | name: triplet # quadruplet, triplet, pairwise, or pairwise_pytorch
13 | margin: 0.3
14 | sampling: max_negative # random (outdated), max_negative, or prob_negative
15 | nb_samples: 1
16 | aggregation: mean # mean, valid
17 | substrategy:
18 | - IRR
19 | - RII
20 | substrategy_weights:
21 | - 1.0
22 | - 1.0
--------------------------------------------------------------------------------
/recipe1m/options/semi_hard.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/semi_hard
4 | dataset:
5 | freq_mismatch: 0.0
6 | model:
7 | with_classif: False
8 | criterion:
9 | name: trijoint
10 | keep_background: False
11 | retrieval_strategy:
12 | name: triplet # quadruplet, triplet, pairwise, or pairwise_pytorch
13 | margin: 0.3
14 | sampling: semi_hard # random (outdated), max_negative, or prob_negative
15 | nb_samples: 9999
16 | aggregation: valid # mean, valid
17 | substrategy:
18 | - IRR
19 | - RII
20 | substrategy_weights:
21 | - 1.0
22 | - 1.0
--------------------------------------------------------------------------------
/recipe1m/options/avg_nosem.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/avg_nosem
4 | dataset:
5 | freq_mismatch: 0.0
6 | model:
7 | with_classif: False
8 | criterion:
9 | name: trijoint
10 | keep_background: False
11 | retrieval_strategy:
12 | name: triplet # quadruplet, triplet, pairwise, or pairwise_pytorch
13 | margin: 0.3
14 | sampling: max_negative # random (outdated), max_negative, or prob_negative
15 | nb_samples: 9999
16 | aggregation: mean # mean, valid (adamine)
17 | substrategy:
18 | - IRR
19 | - RII
20 | substrategy_weights:
21 | - 1.0
22 | - 1.0
23 |
--------------------------------------------------------------------------------
/recipe1m/options/triplet.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/triplet
4 | dataset:
5 | freq_mismatch: 0.0
6 | model:
7 | with_classif: False
8 | criterion:
9 | name: trijoint
10 | keep_background: False
11 | retrieval_strategy:
12 | name: triplet # quadruplet, triplet, pairwise, or pairwise_pytorch
13 | margin: 0.3
14 | sampling: max_negative # random (outdated), max_negative, or prob_negative
15 | nb_samples: 9999
16 | aggregation: valid # mean, valid
17 | substrategy:
18 | - IRR
19 | - RII
20 | - SIRR
21 | - SRII
22 | substrategy_weights:
23 | - 1.0
24 | - 1.0
25 | - 0.1
26 | - 0.1
--------------------------------------------------------------------------------
/recipe1m/options/avg.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/avg
4 | dataset:
5 | freq_mismatch: 0.0
6 | model:
7 | with_classif: False
8 | criterion:
9 | name: trijoint
10 | keep_background: False
11 | retrieval_strategy:
12 | name: triplet # quadruplet, triplet, pairwise, or pairwise_pytorch
13 | margin: 0.3
14 | sampling: max_negative # random (outdated), max_negative, or prob_negative
15 | nb_samples: 9999
16 | aggregation: mean # mean, valid (adamine)
17 | substrategy:
18 | - IRR
19 | - RII
20 | - SIRR
21 | - SRII
22 | substrategy_weights:
23 | - 1.0
24 | - 1.0
25 | - 0.1
26 | - 0.1
27 |
--------------------------------------------------------------------------------
/recipe1m/options/adamine.yaml:
--------------------------------------------------------------------------------
1 | __include__: abstract.yaml
2 | exp:
3 | dir: logs/recipe1m/adamine
4 | dataset:
5 | freq_mismatch: 0.0
6 | model:
7 | with_classif: False
8 | criterion:
9 | name: trijoint
10 | keep_background: False
11 | retrieval_strategy:
12 | name: triplet # quadruplet, triplet, pairwise, or pairwise_pytorch
13 | margin: 0.3
14 | sampling: max_negative # random (outdated), max_negative, or prob_negative
15 | nb_samples: 9999
16 | aggregation: valid # mean, valid (adamine)
17 | substrategy:
18 | - IRR
19 | - RII
20 | - SIRR
21 | - SRII
22 | substrategy_weights:
23 | - 1.0
24 | - 1.0
25 | - 0.1
26 | - 0.1
27 |
--------------------------------------------------------------------------------
/recipe1m/datasets/factory.py:
--------------------------------------------------------------------------------
1 | from bootstrap.lib.options import Options
2 | from .recipe1m import Recipe1M
3 |
4 | def factory(engine=None):
5 | dataset = {}
6 |
7 | if Options()['dataset']['name'] == 'recipe1m':
8 |
9 | if Options()['dataset'].get('train_split', None):
10 | dataset['train'] = factory_recipe1m(Options()['dataset']['train_split'])
11 |
12 | if Options()['dataset'].get('eval_split', None):
13 | dataset['eval'] = factory_recipe1m(Options()['dataset']['eval_split'])
14 | else:
15 | raise ValueError()
16 |
17 | return dataset
18 |
19 |
20 | def factory_recipe1m(split):
21 | dataset = Recipe1M(
22 | Options()['dataset']['dir'],
23 | split,
24 | batch_size=Options()['dataset']['batch_size'],
25 | nb_threads=Options()['dataset']['nb_threads'],
26 | freq_mismatch=Options()['dataset']['freq_mismatch'],
27 | batch_sampler=Options()['dataset']['batch_sampler'],
28 | image_from=Options()['dataset']['image_from'])
29 | return dataset
--------------------------------------------------------------------------------
/recipe1m/models/criterions/pairwise.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | from torch.autograd import Variable
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 |
7 | class Pairwise(nn.Module):
8 |
9 | def __init__(self, opt):
10 | super(Pairwise, self).__init__()
11 | self.alpha_pos = opt['retrieval_strategy']['pos_margin']
12 | self.alpha_neg = opt['retrieval_strategy']['neg_margin']
13 |
14 | def forward(self, input1, input2, target):
15 | # target should be 1 for matched samples or -1 for not matched ones
16 | distances = self.dist(input1, input2)
17 | target = target.squeeze(1)
18 | cost = target * distances
19 |
20 | cost[target > 0] -= self.alpha_pos
21 | cost[target < 0] += self.alpha_neg
22 | cost[cost < 0] = 0
23 |
24 | out = {}
25 | out['bad_pairs'] = (cost == 0).float().sum() / cost.numel()
26 | out['loss'] = cost.mean()
27 | return out
28 |
29 | def dist(self, input1, input2):
30 | input1 = nn.functional.normalize(input1)
31 | input2 = nn.functional.normalize(input2)
32 | return 1 - torch.mul(input1, input2).sum(1)
33 |
--------------------------------------------------------------------------------
/recipe1m/models/trijoint.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 |
4 | from bootstrap.lib.options import Options
5 | from bootstrap.datasets import transforms
6 | from bootstrap.models.model import Model
7 |
8 | from . import networks
9 | from . import criterions
10 | from . import metrics
11 |
12 | class Trijoint(Model):
13 |
14 | def __init__(self,
15 | opt,
16 | nb_classes,
17 | modes=['train', 'eval'],
18 | engine=None,
19 | cuda_tf=transforms.ToCuda):
20 | super(Trijoint, self).__init__(engine, cuda_tf=cuda_tf)
21 |
22 | self.network = networks.Trijoint(
23 | opt['network'],
24 | nb_classes,
25 | with_classif=opt['with_classif'])
26 |
27 | self.criterions = {}
28 | self.metrics = {}
29 |
30 | if 'train' in modes:
31 | self.criterions['train'] = criterions.Trijoint(
32 | opt['criterion'],
33 | nb_classes,
34 | opt['network']['dim_emb'],
35 | with_classif=opt['with_classif'],
36 | engine=engine)
37 |
38 | self.metrics['train'] = metrics.Trijoint(
39 | opt['metric'],
40 | with_classif=opt['with_classif'],
41 | engine=engine,
42 | mode='train')
43 |
44 | if 'eval' in modes:
45 | self.metrics['eval'] = metrics.Trijoint(
46 | opt['metric'],
47 | with_classif=opt['with_classif'],
48 | engine=engine,
49 | mode='eval')
--------------------------------------------------------------------------------
/recipe1m/visu/ingrs_count.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from torch.autograd import Variable
8 | from PIL import Image
9 | from tqdm import tqdm
10 | import bootstrap.lib.utils as utils
11 |
12 | def main():
13 |
14 | Logger('.')
15 |
16 | split = 'train'
17 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
18 | path_opts = os.path.join(dir_exp, 'options.yaml')
19 | dir_extract = os.path.join(dir_exp, 'extract_count', split)
20 | path_ingrs_count = os.path.join(dir_extract, 'ingrs.pth')
21 |
22 | Options(path_opts)
23 | utils.set_random_seed(Options()['misc']['seed'])
24 |
25 | dataset = factory(split)
26 |
27 | if not os.path.isfile(path_ingrs_count):
28 | ingrs_count = {}
29 | os.system('mkdir -p '+dir_extract)
30 |
31 | for i in tqdm(range(len(dataset.recipes_dataset))):
32 | item = dataset.recipes_dataset[i]
33 | for ingr in item['ingrs']['interim']:
34 | if ingr not in ingrs_count:
35 | ingrs_count[ingr] = 1
36 | else:
37 | ingrs_count[ingr] += 1
38 |
39 | torch.save(ingrs_count, path_ingrs_count)
40 | else:
41 | ingrs_count = torch.load(path_ingrs_count)
42 |
43 | import ipdb; ipdb.set_trace()
44 | sort = sorted(ingrs_count, key=ingrs_count.get)
45 | import ipdb; ipdb.set_trace()
46 |
47 | Logger()('End')
48 |
49 |
50 | # python -m recipe1m.visu.top5
51 | if __name__ == '__main__':
52 | main()
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Boostrap.pytorch
2 | !.gitkeep
3 |
4 | # Apple
5 | .DS_Store
6 | ._.DS_Store
7 |
8 | # Byte-compiled / optimized / DLL files
9 | __pycache__/
10 | *.py[cod]
11 | *$py.class
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Distribution / packaging
17 | .Python
18 | build/
19 | develop-eggs/
20 | dist/
21 | downloads/
22 | eggs/
23 | .eggs/
24 | # lib/
25 | lib64/
26 | parts/
27 | sdist/
28 | var/
29 | wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 | MANIFEST
34 |
35 | # PyInstaller
36 | # Usually these files are written by a python script from a template
37 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
38 | *.manifest
39 | *.spec
40 |
41 | # Installer logs
42 | pip-log.txt
43 | pip-delete-this-directory.txt
44 |
45 | # Unit test / coverage reports
46 | htmlcov/
47 | .tox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | *.cover
54 | .hypothesis/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | .static_storage/
63 | .media/
64 | local_settings.py
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # pyenv
83 | .python-version
84 |
85 | # celery beat schedule file
86 | celerybeat-schedule
87 |
88 | # SageMath parsed files
89 | *.sage.py
90 |
91 | # Environments
92 | .env
93 | .venv
94 | env/
95 | venv/
96 | ENV/
97 | env.bak/
98 | venv.bak/
99 |
100 | # Spyder project settings
101 | .spyderproject
102 | .spyproject
103 |
104 | # Rope project settings
105 | .ropeproject
106 |
107 | # mkdocs documentation
108 | /site
109 |
110 | # mypy
111 | .mypy_cache/
112 |
--------------------------------------------------------------------------------
/recipe1m/visu/old_top5.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from recipe1m.models.networks.trijoint import Trijoint
8 |
9 | def main():
10 | classes = ['pizza', 'pork chops', 'cupcake', 'hamburger', 'green beans']
11 | nb_points = 100
12 | split = 'test'
13 | dir_exp = 'logs/recipe1m/trijoint/2017-12-14-15-04-51'
14 | path_opts = os.path.join(dir_exp, 'options.yaml')
15 | dir_extract = os.path.join(dir_exp, 'extract', split)
16 | dir_img = os.path.join(dir_extract, 'image')
17 | dir_rcp = os.path.join(dir_extract, 'recipe')
18 | path_model = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
19 |
20 | dir_visu = os.path.join(dir_exp, 'visu', 'top5')
21 |
22 | #Options(path_opts)
23 | Options.load_from_yaml(path_opts)
24 | dataset = factory(split)
25 |
26 | network = Trijoint()
27 | network.eval()
28 | model_state = torch.load(path_model)
29 | network.load_state_dict(model_state['network'])
30 |
31 | list_idx = torch.randperm(len(dataset))
32 |
33 | img_embs = []
34 | rcp_embs = []
35 | for i in range(nb_points):
36 | idx = list_idx[i]
37 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
38 | path_rcp = os.path.join(dir_rcp, '{}.pth'.format(idx))
39 | img_embs.append(torch.load(path_img))
40 | rcp_embs.append(torch.load(path_rcp))
41 |
42 | img_embs = torch.stack(img_embs, 0)
43 | rcp_embs = torch.stack(rcp_embs, 0)
44 |
45 | dist = fast_distance(img_embs, rcp_embs)
46 |
47 | im2recipe_ids = np.argsort(dist.numpy(), axis=0)
48 | recipe2im_ids = np.argsort(dist.numpy(), axis=1)
49 |
50 | import ipdb; ipdb.set_trace()
51 |
52 |
53 |
54 | def fast_distance(A,B):
55 | # A and B must have norm 1 for this to work for the ranking
56 | return torch.mm(A,B.t()) * -1
57 |
58 | # python -m recipe1m.visu.top5
59 | if __name__ == '__main__':
60 | main()
--------------------------------------------------------------------------------
/recipe1m/models/metrics/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | from bootstrap.lib.options import Options
4 |
5 | def accuracy(output, target, topk=(1,), ignore_index=None):
6 | """Computes the precision@k for the specified values of k"""
7 |
8 | if ignore_index is not None:
9 | target_mask = (target != ignore_index)
10 | target = target[target_mask]
11 | output_mask = target_mask.unsqueeze(1)
12 | output_mask = output_mask.expand_as(output)
13 | output = output[output_mask]
14 | output = output.view(-1, output_mask.size(1))
15 |
16 | maxk = max(topk)
17 | batch_size = target.size(0)
18 |
19 | _, pred = output.topk(maxk, 1, True, True)
20 | pred = pred.t()
21 | correct = pred.eq(target.view(1, -1).expand_as(pred))
22 |
23 | res = []
24 | for k in topk:
25 | correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)
26 | res.append(correct_k.mul_(100.0 / batch_size)[0])
27 | return res
28 |
29 | def save_activation(identifier, activation):
30 | retrieval_dir = os.path.join(Options()['model']['metric']['retrieval_dir'],
31 | os.path.basename(Options()['exp']['dir']))
32 | if not os.path.exists(retrieval_dir):
33 | os.makedirs(retrieval_dir)
34 | file_path = os.path.join(retrieval_dir, '{}.pth'.format(identifier))
35 | torch.save(activation, file_path)
36 |
37 | def load_activation(identifier):
38 | retrieval_dir = os.path.join(Options()['model']['metric']['retrieval_dir'],
39 | os.path.basename(Options()['exp']['dir']))
40 | if not os.path.exists(retrieval_dir):
41 | os.makedirs(retrieval_dir)
42 | file_path = os.path.join(retrieval_dir, '{}.pth'.format(identifier))
43 | return torch.load(file_path)
44 |
45 | def delete_activation(identifier):
46 | retrieval_dir = os.path.join(Options()['model']['metric']['retrieval_dir'],
47 | os.path.basename(Options()['exp']['dir']))
48 | file_path = os.path.join(retrieval_dir, '{}.pth'.format(identifier))
49 | if os.path.isfile(file_path):
50 | os.remove(file_path)
51 |
--------------------------------------------------------------------------------
/recipe1m/options/abstract.yaml:
--------------------------------------------------------------------------------
1 | exp:
2 | dir: logs/recipe1m/default
3 | resume: # best, last, or empty (from scratch)
4 | dataset:
5 | import: recipe1m.datasets.factory
6 | name: recipe1m
7 | dir: data/recipe1m
8 | train_split: train
9 | eval_split: val
10 | nb_classes: 1048
11 | database: lmdb
12 | image_from: database # or pil_loader
13 | batch_size: 100 # = optimizer.batch_size or inferior
14 | batch_sampler: triplet_classif # random or triplet_classif
15 | nb_threads: 4
16 | debug: False
17 | model:
18 | import: recipe1m.models.factory
19 | name: trijoint
20 | network:
21 | name: trijoint
22 | path_ingrs: data/recipe1m/text/vocab.pkl
23 | dim_image_out: 2048
24 | with_ingrs: True
25 | dim_ingr_out: 300
26 | with_instrs: True
27 | dim_instr_in: 1024
28 | dim_instr_out: 1024
29 | dim_emb: 1024
30 | activations:
31 | - tanh
32 | - normalize
33 | criterion: __NotImplemented__
34 | metric:
35 | name: trijoint
36 | retrieval_dir: /tmp/recipe1m
37 | nb_bags: 10
38 | nb_matchs_per_bag: 1000
39 | optimizer:
40 | import: recipe1m.optimizers.factory
41 | name: trijoint_fixed_fine_tune
42 | switch_epoch: 20
43 | lr: 0.0001
44 | #switch_step: 50000
45 | batch_size_factor: # TODO remove?
46 | clip_grad: 8.
47 | engine:
48 | name: logger
49 | nb_epochs: 80
50 | print_freq: 10
51 | debug: False
52 | saving_criteria:
53 | #- train_epoch.loss:min
54 | - eval_epoch.metric.med_im2recipe_mean:min
55 | - eval_epoch.metric.recall_at_1_im2recipe_mean:max
56 | misc:
57 | cuda: True
58 | seed: 1338
59 | logs_name:
60 | view:
61 | - logs:train_epoch.loss
62 | - logs:train_epoch.bad_pairs
63 | - logs:eval_epoch.metric.med_im2recipe_mean
64 | - logs:eval_epoch.metric.recall_at_1_im2recipe_mean
65 | - logs:eval_epoch.metric.recall_at_5_im2recipe_mean
66 | - logs:eval_epoch.metric.recall_at_10_im2recipe_mean
67 | - logs:eval_epoch.metric.med_recipe2im_mean
68 | - logs:eval_epoch.metric.recall_at_1_recipe2im_mean
69 | - logs:eval_epoch.metric.recall_at_5_recipe2im_mean
70 | - logs:eval_epoch.metric.recall_at_10_recipe2im_mean
71 | - logs:optimizer.is_optimizer_recipe&image
72 | - logs:optimizer.total_norm
73 |
--------------------------------------------------------------------------------
/recipe1m/visu/calculate_mean_features.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 | #classes = ['pizza', 'pork chops', 'cupcake', 'hamburger', 'green beans']
18 | split = 'test'
19 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
20 | path_opts = os.path.join(dir_exp, 'options.yaml')
21 | dir_extract = os.path.join(dir_exp, 'extract_mean_features', split)
22 | dir_img = os.path.join(dir_extract, 'image')
23 | dir_rcp = os.path.join(dir_extract, 'recipe')
24 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
25 |
26 | Options.load_from_yaml(path_opts)
27 | utils.set_random_seed(Options()['misc']['seed'])
28 |
29 | Logger()('Load dataset...')
30 | dataset = factory(split)
31 |
32 | Logger()('Load model...')
33 | model = model_factory()
34 | model_state = torch.load(path_model_ckpt)
35 | model.load_state_dict(model_state)
36 | model.set_mode(split)
37 |
38 | if not os.path.isdir(dir_extract):
39 | Logger()('Create extract_dir {}'.format(dir_extract))
40 | os.system('mkdir -p '+dir_extract)
41 |
42 | mean_ingrs = torch.zeros(model.network.recipe_embedding.dim_ingr_out*2) # bi LSTM
43 | mean_instrs = torch.zeros(model.network.recipe_embedding.dim_instr_out)
44 |
45 | for i in tqdm(range(len(dataset))):
46 | item = dataset[i]
47 | batch = dataset.items_tf()([item])
48 |
49 | batch = model.prepare_batch(batch)
50 | out_ingrs = model.network.recipe_embedding.forward_ingrs(batch['recipe']['ingrs'])
51 | out_instrs = model.network.recipe_embedding.forward_instrs(batch['recipe']['instrs'])
52 |
53 | mean_ingrs += out_ingrs.data.cpu().squeeze(0)
54 | mean_instrs += out_instrs.data.cpu().squeeze(0)
55 |
56 | mean_ingrs /= len(dataset)
57 | mean_instrs /= len(dataset)
58 |
59 | path_ingrs = os.path.join(dir_extract, 'ingrs.pth')
60 | path_instrs = os.path.join(dir_extract, 'instrs.pth')
61 |
62 | torch.save(mean_ingrs, path_ingrs)
63 | torch.save(mean_instrs, path_instrs)
64 |
65 | Logger()('End')
66 |
67 |
68 | # python -m recipe1m.visu.top5
69 | if __name__ == '__main__':
70 | main()
--------------------------------------------------------------------------------
/recipe1m/extract.py:
--------------------------------------------------------------------------------
1 | """
2 | # How to use extract.py
3 |
4 | ```
5 | $ python -m recipe1m.extract -o logs/recipe1m/adamine/options.yaml \
6 | --dataset.train_split \
7 | --dataset.eval_split test \
8 | --exp.resume best_eval_epoch.metric.med_im2recipe_mean \
9 | --misc.logs_name extract_test
10 | ```
11 | """
12 |
13 | import os
14 | import torch
15 | import torch.backends.cudnn as cudnn
16 | import bootstrap.lib.utils as utils
17 | import bootstrap.engines as engines
18 | import bootstrap.models as models
19 | import bootstrap.datasets as datasets
20 | import bootstrap.views as views
21 | from bootstrap.lib.logger import Logger
22 | from bootstrap.lib.options import Options
23 | from bootstrap.run import init_logs_options_files
24 |
25 | def extract(path_opts=None):
26 | Options(path_opts)
27 | utils.set_random_seed(Options()['misc']['seed'])
28 |
29 | assert Options()['dataset']['eval_split'] is not None, 'eval_split must be set'
30 | assert Options()['dataset']['train_split'] is None, 'train_split must be None'
31 |
32 | init_logs_options_files(Options()['exp']['dir'], Options()['exp']['resume'])
33 |
34 | Logger().log_dict('options', Options(), should_print=True)
35 | Logger()(os.uname())
36 | if torch.cuda.is_available():
37 | cudnn.benchmark = True
38 | Logger()('Available GPUs: {}'.format(utils.available_gpu_ids()))
39 |
40 | engine = engines.factory()
41 | engine.dataset = datasets.factory(engine)
42 | engine.model = models.factory(engine)
43 | engine.view = views.factory(engine)
44 |
45 | # init extract directory
46 | dir_extract = os.path.join(Options()['exp']['dir'], 'extract', Options()['dataset']['eval_split'])
47 | os.system('mkdir -p '+dir_extract)
48 | path_img_embs = os.path.join(dir_extract, 'image_emdeddings.pth')
49 | path_rcp_embs = os.path.join(dir_extract, 'recipe_emdeddings.pth')
50 | img_embs = torch.FloatTensor(len(engine.dataset['eval']), Options()['model.network.dim_emb'])
51 | rcp_embs = torch.FloatTensor(len(engine.dataset['eval']), Options()['model.network.dim_emb'])
52 |
53 | def save_embeddings(module, input, out):
54 | nonlocal img_embs
55 | nonlocal rcp_embs
56 | batch = input[0] # tuple of len=1
57 | for j, idx in enumerate(batch['recipe']['index']):
58 | # path_image = os.path.join(dir_image, '{}.pth'.format(idx))
59 | # path_recipe = os.path.join(dir_recipe, '{}.pth'.format(idx))
60 | # torch.save(out['image_embedding'][j].data.cpu(), path_image)
61 | # torch.save(out['recipe_embedding'][j].data.cpu(), path_recipe)
62 | img_embs[idx] = out['image_embedding'][j].data.cpu()
63 | rcp_embs[idx] = out['recipe_embedding'][j].data.cpu()
64 |
65 | engine.model.register_forward_hook(save_embeddings)
66 | engine.resume()
67 | engine.eval()
68 |
69 | Logger()('Saving image embeddings to {} ...'.format(path_img_embs))
70 | torch.save(img_embs, path_img_embs)
71 |
72 | Logger()('Saving recipe embeddings to {} ...'.format(path_rcp_embs))
73 | torch.save(rcp_embs, path_rcp_embs)
74 |
75 |
76 | if __name__ == '__main__':
77 | from bootstrap.run import main
78 | main(run=extract)
--------------------------------------------------------------------------------
/recipe1m/models/criterions/trijoint.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | from bootstrap.lib.logger import Logger
4 | from bootstrap.lib.options import Options
5 | from .triplet import Triplet
6 | from .pairwise import Pairwise
7 |
8 | class Trijoint(nn.Module):
9 |
10 | def __init__(self, opt, nb_classes, dim_emb, with_classif=False, engine=None):
11 | super(Trijoint, self).__init__()
12 | self.with_classif = with_classif
13 | if self.with_classif:
14 | self.weight_classif = opt['weight_classif']
15 | if self.weight_classif == 0:
16 | Logger()('You should use "--model.with_classif False"', Logger.ERROR)
17 | self.weight_retrieval = 1 - 2 * opt['weight_classif']
18 |
19 | self.keep_background = opt.get('keep_background', False)
20 | if self.keep_background:
21 | # http://pytorch.org/docs/master/nn.html?highlight=crossentropy#torch.nn.CrossEntropyLoss
22 | self.ignore_index = -100
23 | else:
24 | self.ignore_index = 0
25 |
26 | Logger()('ignore_index={}'.format(self.ignore_index))
27 | if self.with_classif:
28 | self.criterion_image_classif = nn.CrossEntropyLoss(ignore_index=self.ignore_index)
29 | self.criterion_recipe_classif = nn.CrossEntropyLoss(ignore_index=self.ignore_index)
30 |
31 | self.retrieval_strategy = opt['retrieval_strategy.name']
32 |
33 | if self.retrieval_strategy == 'triplet':
34 | self.criterion_retrieval = Triplet(
35 | opt,
36 | nb_classes,
37 | dim_emb,
38 | engine)
39 |
40 | elif self.retrieval_strategy == 'pairwise':
41 | self.criterion_retrieval = Pairwise(opt)
42 |
43 | elif self.retrieval_strategy == 'pairwise_pytorch':
44 | self.criterion_retrieval = nn.CosineEmbeddingLoss()
45 |
46 | else:
47 | raise ValueError('Unknown loss ({})'.format(self.retrieval_strategy))
48 |
49 | def forward(self, net_out, batch):
50 | if self.retrieval_strategy == 'triplet':
51 | out = self.criterion_retrieval(net_out['image_embedding'],
52 | net_out['recipe_embedding'],
53 | batch['match'],
54 | batch['image']['class_id'],
55 | batch['recipe']['class_id'])
56 | elif self.retrieval_strategy == 'pairwise':
57 | out = self.criterion_retrieval(net_out['image_embedding'],
58 | net_out['recipe_embedding'],
59 | batch['match'])
60 | elif self.retrieval_strategy == 'pairwise_pytorch':
61 | out = {}
62 | out['loss'] = self.criterion_retrieval(net_out['image_embedding'],
63 | net_out['recipe_embedding'],
64 | batch['match'])
65 |
66 | if self.with_classif:
67 | out['image_classif'] = self.criterion_image_classif(net_out['image_classif'],
68 | batch['image']['class_id'].squeeze(1))
69 | out['recipe_classif'] = self.criterion_recipe_classif(net_out['recipe_classif'],
70 | batch['recipe']['class_id'].squeeze(1))
71 | out['loss'] *= self.weight_retrieval
72 | out['loss'] += out['image_classif'] * self.weight_classif
73 | out['loss'] += out['recipe_classif'] * self.weight_classif
74 |
75 | return out
76 |
--------------------------------------------------------------------------------
/recipe1m/optimizers/trijoint.py:
--------------------------------------------------------------------------------
1 | import torch
2 | from bootstrap.lib.options import Options
3 | from bootstrap.lib.logger import Logger
4 | from torch.nn.utils.clip_grad import clip_grad_norm_
5 |
6 | class Trijoint(torch.optim.Optimizer):
7 |
8 | def __init__(self, opt, model, engine=None):
9 | self.model = model
10 | self.lr = opt['lr']
11 | self.switch_epoch = opt['switch_epoch']
12 | self.clip_grad = opt.get('clip_grad', False)
13 | self.optimizers = {}
14 | self.optimizers['recipe'] = torch.optim.Adam(self.model.network.get_parameters_recipe(), self.lr)
15 | self.optimizers['image'] = torch.optim.Adam(self.model.network.get_parameters_image(), self.lr)
16 | self.current_optimizer_name = 'recipe'
17 | self.epoch = 0
18 | self._activate_model()
19 | if engine:
20 | engine.register_hook('train_on_start_epoch', self._auto_fixed_fine_tune)
21 | if self.clip_grad:
22 | engine.register_hook('train_on_print', self.print_total_norm)
23 |
24 | def state_dict(self):
25 | state = {}
26 | state['optimizers'] = {}
27 | for key, value in self.optimizers.items():
28 | state['optimizers'][key] = value.state_dict()
29 | state['attributs'] = {
30 | 'current_optimizer_name': self.current_optimizer_name,
31 | 'epoch': self.epoch
32 | }
33 | return state
34 |
35 | def load_state_dict(self, state_dict):
36 | for key, _ in self.optimizers.items():
37 | value = state_dict['optimizers'][key]
38 | if len(value['state']) != 0: # bug pytorch??
39 | self.optimizers[key].load_state_dict(value)
40 | if 'attributs' in state_dict:
41 | for key, value in state_dict['attributs'].items():
42 | setattr(self, key, value)
43 | self._activate_model()
44 |
45 | def zero_grad(self):
46 | for name in self.current_optimizer_name.split('&'):
47 | self.optimizers[name].zero_grad()
48 |
49 | def step(self, closure=None):
50 | if self.clip_grad:
51 | self.clip_grad_norm()
52 | for name in self.current_optimizer_name.split('&'):
53 | self.optimizers[name].step(closure)
54 |
55 | def clip_grad_norm(self):
56 | params = []
57 | for k in self.optimizers:
58 | for group in self.optimizers[k].param_groups:
59 | for p in group['params']:
60 | params.append(p)
61 | self.total_norm = clip_grad_norm_(params, self.clip_grad)
62 | Logger().log_value('optimizer.total_norm', self.total_norm, should_print=False)
63 |
64 | def print_total_norm(self):
65 | Logger()('{} total_norm: {:.6f}'.format(' '*len('train'), self.total_norm))
66 |
67 | def _activate_model(self):
68 | optim_name = self.current_optimizer_name
69 | activate_recipe = (optim_name == 'recipe') or (optim_name == 'recipe&image')
70 | activate_image = (optim_name == 'image') or (optim_name == 'recipe&image')
71 | for p_dict in self.model.network.get_parameters_recipe():
72 | for p in p_dict['params']:
73 | p.requires_grad = activate_recipe
74 | for p in self.model.network.get_parameters_image():
75 | p.requires_grad = activate_image
76 |
77 | def _auto_fixed_fine_tune(self):
78 | if self.current_optimizer_name == 'recipe' and self.epoch == self.switch_epoch:
79 | self.current_optimizer_name = 'recipe&image'
80 | self._activate_model()
81 | Logger()('Switched to optimizer '+self.current_optimizer_name)
82 |
83 | Logger().log_value('optimizer.is_optimizer_recipe&image',
84 | int(self.current_optimizer_name == 'recipe&image'),
85 | should_print=False)
86 | self.epoch += 1
87 |
--------------------------------------------------------------------------------
/recipe1m/visu/mean_to_images.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 |
18 | #classes = ['pizza', 'pork chops', 'cupcake', 'hamburger', 'green beans']
19 | nb_points = 1000
20 | split = 'test'
21 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
22 | path_opts = os.path.join(dir_exp, 'options.yaml')
23 | dir_extract = os.path.join(dir_exp, 'extract', split)
24 | dir_extract_mean = os.path.join(dir_exp, 'extract_mean_features', split)
25 | dir_img = os.path.join(dir_extract, 'image')
26 | dir_rcp = os.path.join(dir_extract, 'recipe')
27 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
28 |
29 | dir_visu = os.path.join(dir_exp, 'visu', 'mean_to_image')
30 | os.system('mkdir -p '+dir_visu)
31 |
32 | #Options(path_opts)
33 | Options.load_from_yaml(path_opts)
34 | utils.set_random_seed(Options()['misc']['seed'])
35 |
36 | dataset = factory(split)
37 |
38 | Logger()('Load model...')
39 | model = model_factory()
40 | model_state = torch.load(path_model_ckpt)
41 | model.load_state_dict(model_state)
42 | model.set_mode(split)
43 |
44 | #emb = network.recipe_embedding.forward_ingrs(input_['recipe']['ingrs'])
45 | list_idx = torch.randperm(len(dataset))
46 |
47 | Logger()('Load embeddings...')
48 | img_embs = []
49 | rcp_embs = []
50 | for i in range(nb_points):
51 | idx = list_idx[i]
52 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
53 | path_rcp = os.path.join(dir_rcp, '{}.pth'.format(idx))
54 | if not os.path.isfile(path_img):
55 | Logger()('No such file: {}'.format(path_img))
56 | continue
57 | if not os.path.isfile(path_rcp):
58 | Logger()('No such file: {}'.format(path_rcp))
59 | continue
60 | img_embs.append(torch.load(path_img))
61 | rcp_embs.append(torch.load(path_rcp))
62 |
63 | img_embs = torch.stack(img_embs, 0)
64 | rcp_embs = torch.stack(rcp_embs, 0)
65 |
66 |
67 | Logger()('Load means')
68 | path_ingrs = os.path.join(dir_extract_mean, 'ingrs.pth')
69 | path_instrs = os.path.join(dir_extract_mean, 'instrs.pth')
70 |
71 | mean_ingrs = torch.load(path_ingrs)
72 | mean_instrs = torch.load(path_instrs)
73 |
74 | mean_ingrs = Variable(mean_ingrs.unsqueeze(0).cuda(), requires_grad=False)
75 | mean_instrs = Variable(mean_instrs.unsqueeze(0).cuda(), requires_grad=False)
76 |
77 | Logger()('Forward ingredient...')
78 | ingr_emb = model.network.recipe_embedding.forward_ingrs_instrs(mean_ingrs, mean_instrs)
79 | ingr_emb = ingr_emb.data.cpu()
80 | ingr_emb = ingr_emb.expand_as(img_embs)
81 |
82 | Logger()('Fast distance...')
83 | dist = fast_distance(img_embs, ingr_emb)[:, 0]
84 |
85 | sorted_img_ids = np.argsort(dist.numpy())
86 |
87 | Logger()('Load/save images...')
88 | for i in range(20):
89 | img_id = sorted_img_ids[i]
90 | img_id = int(img_id)
91 |
92 | path_img_from = dataset[img_id]['image']['path']
93 | path_img_to = os.path.join(dir_visu, 'image_top_{}.png'.format(i+1))
94 | img = Image.open(path_img_from)
95 | img.save(path_img_to)
96 | #os.system('cp {} {}'.format(path_img_from, path_img_to))
97 |
98 | Logger()('End')
99 |
100 |
101 | def fast_distance(A,B):
102 | # A and B must have norm 1 for this to work for the ranking
103 | return torch.mm(A,B.t()) * -1
104 |
105 | # python -m recipe1m.visu.top5
106 | if __name__ == '__main__':
107 | main()
--------------------------------------------------------------------------------
/demo_web/js/custom.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 |
3 | // Image processing
4 |
5 | $(document).on('change', '.btn-file :file', function() {
6 | var input = $(this),
7 | label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
8 | input.trigger('fileselect', [label]);
9 | });
10 |
11 | $('.btn-file :file').on('fileselect', function(event, label) {
12 | var input = $(this).parents('.input-group').find(':text'),
13 | log = label;
14 |
15 | if( input.length ) {
16 | input.val(log);
17 | } else {
18 | if( log ) alert(log);
19 | }
20 |
21 | });
22 |
23 | function readURL(input) {
24 | if (input.files && input.files[0]) {
25 | var reader = new FileReader();
26 |
27 | reader.onload = function (e) {
28 | $('#adamine-image').attr('src', e.target.result);
29 | }
30 |
31 | reader.readAsDataURL(input.files[0]);
32 | }
33 | }
34 |
35 | $("#imgInp").change(function(){
36 | readURL(this);
37 | });
38 |
39 | // Send Image
40 |
41 | $(document).ajaxStart(function () {
42 | $('#loading').show();
43 | $('#adamine-recipe').hide();
44 | }).ajaxStop(function () {
45 | $('#loading').hide();
46 | $('#adamine-recipe').show();
47 | });
48 |
49 | var formBasic = function () {
50 | var formData = $("#formBasic").serialize();
51 | var data = {
52 | image: $('#adamine-image').attr('src'),
53 | mode: 'all'
54 | }
55 | //question : $('#adamine-question').val()}
56 | $.ajax({
57 |
58 | type: 'post',
59 | data: data,
60 | dataType: 'json',
61 | url: 'http://edwards:3456', // your global ip address and port
62 |
63 | error: function () {
64 | alert("There was an error processing this page.");
65 | return false;
66 | },
67 |
68 | complete: function (output) {
69 | if ('responseJSON' in output) {
70 | var ul = $('
');
71 |
72 | for (i=0; i < output.responseJSON.length; i++) {
73 | var li = $('');
74 | var out = output.responseJSON[i]
75 |
76 | li.append($('class: ' + out['class_name'] + '
'));
77 | li.append($('title: ' + out['title'] + '
'));
78 | li.append($('
'));
79 |
80 | ingrs = []
81 | for (j=0; j < out['ingredients'].length; j++) {
82 | ingrs.push(out['ingredients'][j]['text'])
83 | }
84 | li.append($('ingredients: ' + ingrs.join(', ') + '
'));
85 |
86 | instrs = []
87 | for (j=0; j < out['instructions'].length; j++) {
88 | instrs.push(out['instructions'][j]['text'])
89 | }
90 | li.append($('instructions: ' + instrs.join('\n') + '
'));
91 |
92 | ul.append(li);
93 | }
94 |
95 | $('#adamine-recipe').html(ul);
96 | console.log(output.responseJSON);
97 |
98 | } else if ('responseText' in output) {
99 | alert(output.responseText);
100 | console.log(output.responseText);
101 |
102 | } else {
103 | alert('Something wrong happend!');
104 | console.log(output)
105 | }
106 | }
107 | });
108 | return false;
109 | };
110 |
111 | $("#basic-submit").on("click", function (e) {
112 | e.preventDefault();
113 | formBasic();
114 | });
115 | });
--------------------------------------------------------------------------------
/recipe1m/api.py:
--------------------------------------------------------------------------------
1 | """
2 | # How to use api.py
3 |
4 | ```
5 | $ python -m recipe1m.api -o logs/recipe1m/adamine/options.yaml \
6 | --dataset.train_split \
7 | --dataset.eval_split test \
8 | --exp.resume best_eval_epoch.metric.med_im2recipe_mean \
9 | --dataset.eval_split test \
10 | --misc.logs_name api
11 | ```
12 | """
13 | # TODO: add ip and port as cli?
14 | # TODO: save the image and the results
15 |
16 | import os
17 | import re
18 | import json
19 | import base64
20 | import numpy as np
21 | import torch
22 | import torch.backends.cudnn as cudnn
23 | import bootstrap.lib.utils as utils
24 | import bootstrap.engines as engines
25 | import bootstrap.models as models
26 | import bootstrap.datasets as datasets
27 | from PIL import Image
28 | from io import BytesIO
29 | from glob import glob
30 | from werkzeug.wrappers import Request, Response
31 | from werkzeug.serving import run_simple
32 | from bootstrap.lib.logger import Logger
33 | from bootstrap.lib.options import Options
34 | from bootstrap.run import init_logs_options_files
35 | from bootstrap.run import main
36 | from .models.metrics.trijoint import fast_distance
37 |
38 |
39 | def decode_image(strb64):
40 | strb64 = re.sub('^data:image/.+;base64,', '', strb64)
41 | pil_img = Image.open(BytesIO(base64.b64decode(strb64)))
42 | return pil_img
43 |
44 | def encode_image(pil_img):
45 | buffer_ = BytesIO()
46 | pil_img.save(buffer_, format='PNG')
47 | img_str = base64.b64encode(buffer_.getvalue()).decode()
48 | img_str = 'data:image/png;base64,'+img_str
49 | return img_str
50 |
51 | def load_img(path):
52 | with open(path, 'rb') as f:
53 | with Image.open(f) as img:
54 | return img.convert('RGB')
55 |
56 | def process_image(pil_img, mode='recipe', top=5):
57 | tensor = engine.dataset['eval'].images_dataset.image_tf(pil_img)
58 | item = {'data': tensor}
59 | batch = engine.dataset['eval'].items_tf()([item])
60 | batch = engine.model.prepare_batch(batch)
61 |
62 | if mode == 'recipe':
63 | embs = rcp_embs
64 | elif mode == 'image':
65 | embs = img_embs
66 | elif mode == 'all':
67 | embs = all_embs
68 |
69 | with torch.no_grad():
70 | img_emb = engine.model.network.image_embedding(batch)
71 | img_emb = img_emb.data.cpu()
72 | distances = fast_distance(img_emb, embs)
73 | values, ids = distances[0].sort()
74 |
75 | out = []
76 | for i in range(top):
77 | idx = ids[i].item()
78 | if mode == 'all':
79 | idx = all_ids[idx]
80 | item = engine.dataset['eval'][idx]
81 |
82 | info = {}
83 | info['class_name'] = item['recipe']['class_name']
84 | info['ingredients'] = item['recipe']['layer1']['ingredients']
85 | info['instructions'] = item['recipe']['layer1']['instructions']
86 | info['url'] = item['recipe']['layer1']['url']
87 | info['title'] = item['recipe']['layer1']['title']
88 | info['path_img'] = item['image']['path']
89 | info['img_strb64'] = encode_image(load_img(item['image']['path']))
90 | out.append(info)
91 |
92 | return out
93 |
94 | @Request.application
95 | def application(request):
96 | utils.set_random_seed(Options()['misc']['seed'])
97 |
98 | if 'image' not in request.form:
99 | return Response('"image" POST field is missing')
100 | if 'mode' not in request.form:
101 | return Response('"mode" POST field is missing')
102 | if 'top' not in request.form:
103 | return Response('"top" POST field is missing')
104 |
105 | if request.form['mode'] not in ['recipe', 'image', 'all']:
106 | return Response('"mode" must be equals to ' + ' | '.join(['recipe', 'image', 'all']))
107 |
108 | pil_img = decode_image(request.form['image'])
109 | out = process_image(pil_img,
110 | mode=request.form['mode'],
111 | top=int(request.form['top']))
112 | out = json.dumps(out)
113 | response = Response(out)
114 | response.headers.add('Access-Control-Allow-Origin', '*')
115 | response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH')
116 | response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization')
117 | response.headers.add('X-XSS-Protection', '0')
118 | return response
119 |
120 | def api(path_opts=None):
121 | global engine
122 | global img_embs
123 | global rcp_embs
124 | global all_embs
125 | global all_ids
126 |
127 | Options(path_opts)
128 | utils.set_random_seed(Options()['misc']['seed'])
129 |
130 | assert Options()['dataset']['eval_split'] is not None, 'eval_split must be set'
131 | assert Options()['dataset']['train_split'] is None, 'train_split must be None'
132 |
133 | init_logs_options_files(Options()['exp']['dir'], Options()['exp']['resume'])
134 |
135 | Logger().log_dict('options', Options(), should_print=True)
136 | Logger()(os.uname())
137 | if torch.cuda.is_available():
138 | cudnn.benchmark = True
139 | Logger()('Available GPUs: {}'.format(utils.available_gpu_ids()))
140 |
141 | engine = engines.factory()
142 | engine.dataset = datasets.factory(engine)
143 | engine.model = models.factory(engine)
144 | engine.model.eval()
145 | engine.resume()
146 |
147 | dir_extract = os.path.join(Options()['exp']['dir'], 'extract', Options()['dataset']['eval_split'])
148 | path_img_embs = os.path.join(dir_extract, 'image_emdeddings.pth')
149 | path_rcp_embs = os.path.join(dir_extract, 'recipe_emdeddings.pth')
150 | img_embs = torch.load(path_img_embs)
151 | rcp_embs = torch.load(path_rcp_embs)
152 | all_embs = torch.cat([img_embs, rcp_embs], dim=0)
153 | all_ids = list(range(img_embs.shape[0])) + list(range(rcp_embs.shape[0]))
154 |
155 | my_local_ip = '132.227.204.160' # localhost | 192.168.0.41 (hostname --ip-address)
156 | my_local_port = 3456 # 8080 | 3456
157 | run_simple(my_local_ip, my_local_port, application)
158 |
159 |
160 | if __name__ == '__main__':
161 | main(run=api)
--------------------------------------------------------------------------------
/recipe1m/visu/ingrs_to_images.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 |
18 | #classes = ['pizza', 'pork chops', 'cupcake', 'hamburger', 'green beans']
19 | nb_points = 1000
20 | split = 'test'
21 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
22 | path_opts = os.path.join(dir_exp, 'options.yaml')
23 | dir_extract = os.path.join(dir_exp, 'extract', split)
24 | dir_extract_mean = os.path.join(dir_exp, 'extract_mean_features', split)
25 | dir_img = os.path.join(dir_extract, 'image')
26 | dir_rcp = os.path.join(dir_extract, 'recipe')
27 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
28 |
29 | is_mean = True
30 | ingrs_list = ['tomato']#['tomato', 'salad', 'onion', 'chicken']
31 |
32 |
33 | #Options(path_opts)
34 | Options.load_from_yaml(path_opts)
35 | utils.set_random_seed(Options()['misc']['seed'])
36 |
37 | dataset = factory(split)
38 |
39 | Logger()('Load model...')
40 | model = model_factory()
41 | model_state = torch.load(path_model_ckpt)
42 | model.load_state_dict(model_state)
43 | model.eval()
44 |
45 | if not os.path.isdir(dir_extract):
46 | os.system('mkdir -p '+dir_rcp)
47 | os.system('mkdir -p '+dir_img)
48 |
49 | for i in tqdm(range(len(dataset))):
50 | item = dataset[i]
51 | batch = dataset.items_tf()([item])
52 |
53 | if model.is_cuda:
54 | batch = model.cuda_tf()(batch)
55 |
56 | is_volatile = (model.mode not in ['train', 'trainval'])
57 | batch = model.variable_tf(volatile=is_volatile)(batch)
58 |
59 | out = model.network(batch)
60 |
61 | path_image = os.path.join(dir_img, '{}.pth'.format(i))
62 | path_recipe = os.path.join(dir_rcp, '{}.pth'.format(i))
63 | torch.save(out['image_embedding'][0].data.cpu(), path_image)
64 | torch.save(out['recipe_embedding'][0].data.cpu(), path_recipe)
65 |
66 |
67 |
68 | # b = dataset.make_batch_loader().__iter__().__next__()
69 | # import ipdb; ipdb.set_trace()
70 |
71 | ingrs = torch.LongTensor(1, len(ingrs_list))
72 | for i, ingr_name in enumerate(ingrs_list):
73 | ingrs[0, i] = dataset.recipes_dataset.ingrname_to_ingrid[ingr_name]
74 |
75 | input_ = {
76 | 'recipe': {
77 | 'ingrs': {
78 | 'data': Variable(ingrs.cuda(), requires_grad=False),
79 | 'lengths': [ingrs.size(1)]
80 | },
81 | 'instrs': {
82 | 'data': Variable(torch.FloatTensor(1, 1, 1024).fill_(0).cuda(), requires_grad=False),
83 | 'lengths': [1]
84 | }
85 | }
86 | }
87 |
88 | #emb = network.recipe_embedding.forward_ingrs(input_['recipe']['ingrs'])
89 | list_idx = torch.randperm(len(dataset))
90 | # nb_points = list_idx.size(0)
91 |
92 | Logger()('Load embeddings...')
93 | img_embs = []
94 | rcp_embs = []
95 | for i in range(nb_points):
96 | idx = list_idx[i]
97 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
98 | path_rcp = os.path.join(dir_rcp, '{}.pth'.format(idx))
99 | if not os.path.isfile(path_img):
100 | Logger()('No such file: {}'.format(path_img))
101 | continue
102 | if not os.path.isfile(path_rcp):
103 | Logger()('No such file: {}'.format(path_rcp))
104 | continue
105 | img_embs.append(torch.load(path_img))
106 | rcp_embs.append(torch.load(path_rcp))
107 |
108 | img_embs = torch.stack(img_embs, 0)
109 | rcp_embs = torch.stack(rcp_embs, 0)
110 |
111 | Logger()('Load mean embeddings')
112 |
113 | path_ingrs = os.path.join(dir_extract_mean, 'ingrs.pth')
114 | path_instrs = os.path.join(dir_extract_mean, 'instrs.pth')
115 |
116 | mean_ingrs = torch.load(path_ingrs)
117 | mean_instrs = torch.load(path_instrs)
118 |
119 | Logger()('Forward ingredient...')
120 | #ingr_emb = model.network.recipe_embedding(input_['recipe'])
121 | ingr_emb = model.network.recipe_embedding.forward_one_ingr(
122 | input_['recipe']['ingrs'],
123 | emb_instrs=mean_instrs.unsqueeze(0))
124 |
125 | ingr_emb = ingr_emb.data.cpu()
126 | ingr_emb = ingr_emb.expand_as(img_embs)
127 |
128 | Logger()('Fast distance...')
129 | dist = fast_distance(img_embs, ingr_emb)[:, 0]
130 |
131 | sorted_ids = np.argsort(dist.numpy())
132 |
133 | dir_visu = os.path.join(dir_exp, 'visu', 'ingrs_to_image_nb_points:{}_instrs:{}_mean:{}'.format(nb_points, '-'.join(ingrs_list), is_mean))
134 | os.system('mkdir -p '+dir_visu)
135 |
136 | Logger()('Load/save images to {}...'.format(dir_visu))
137 | for i in range(20):
138 | idx = int(sorted_ids[i])
139 | item_id = list_idx[idx]
140 | item = dataset[item_id]
141 | path_img_from = item['image']['path']
142 | ingrs = [ingr.replace('/', '\'') for ingr in item['recipe']['ingrs']['interim']]
143 | cname = item['recipe']['class_name']
144 | path_img_to = os.path.join(dir_visu, 'image_top:{}_cname:{}.png'.format(i+1, cname))
145 | img = Image.open(path_img_from)
146 | img.save(path_img_to)
147 | #os.system('cp {} {}'.format(path_img_from, path_img_to))
148 |
149 |
150 | Logger()('End')
151 |
152 |
153 |
154 |
155 | def fast_distance(A,B):
156 | # A and B must have norm 1 for this to work for the ranking
157 | return torch.mm(A,B.t()) * -1
158 |
159 | # python -m recipe1m.visu.top5
160 | if __name__ == '__main__':
161 | main()
--------------------------------------------------------------------------------
/recipe1m/visu/old_ingrs_to_img_by_class.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 |
18 | #classes = ['pizza', 'pork chops', 'cupcake', 'hamburger', 'green beans']
19 | nb_points = 1000
20 | split = 'test'
21 | dir_exp = 'logs/recipe1m/trijoint/2017-12-14-15-04-51'
22 | path_opts = os.path.join(dir_exp, 'options.yaml')
23 | dir_extract = os.path.join(dir_exp, 'extract', split)
24 | dir_img = os.path.join(dir_extract, 'image')
25 | dir_rcp = os.path.join(dir_extract, 'recipe')
26 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
27 |
28 |
29 |
30 | #Options(path_opts)
31 | Options.load_from_yaml(path_opts)
32 | utils.set_random_seed(Options()['misc']['seed'])
33 |
34 | dataset = factory(split)
35 |
36 | Logger()('Load model...')
37 | model = model_factory()
38 | model_state = torch.load(path_model_ckpt)
39 | model.load_state_dict(model_state)
40 | model.set_mode(split)
41 |
42 | if not os.path.isdir(dir_extract):
43 | os.system('mkdir -p '+dir_rcp)
44 | os.system('mkdir -p '+dir_img)
45 |
46 | for i in tqdm(range(len(dataset))):
47 | item = dataset[i]
48 | batch = dataset.items_tf()([item])
49 |
50 | if model.is_cuda:
51 | batch = model.cuda_tf()(batch)
52 |
53 | is_volatile = (model.mode not in ['train', 'trainval'])
54 | batch = model.variable_tf(volatile=is_volatile)(batch)
55 |
56 | out = model.network(batch)
57 |
58 | path_image = os.path.join(dir_img, '{}.pth'.format(i))
59 | path_recipe = os.path.join(dir_rcp, '{}.pth'.format(i))
60 | torch.save(out['image_embedding'][0].data.cpu(), path_image)
61 | torch.save(out['recipe_embedding'][0].data.cpu(), path_recipe)
62 |
63 |
64 | # b = dataset.make_batch_loader().__iter__().__next__()
65 | # class_name = 'pizza'
66 | # ingrs = torch.LongTensor(1, 2)
67 | # ingrs[0, 0] = dataset.recipes_dataset.ingrname_to_ingrid['mushrooms']
68 | # ingrs[0, 1] = dataset.recipes_dataset.ingrname_to_ingrid['mushroom']
69 |
70 | class_name = 'hamburger'
71 | ingrs = torch.LongTensor(1, 2)
72 | ingrs[0, 0] = dataset.recipes_dataset.ingrname_to_ingrid['mushroom']
73 | ingrs[0, 1] = dataset.recipes_dataset.ingrname_to_ingrid['mushrooms']
74 |
75 |
76 | #ingrs[0, 0] = dataset.recipes_dataset.ingrname_to_ingrid['tomato']
77 | #ingrs[0, 1] = dataset.recipes_dataset.ingrname_to_ingrid['salad']
78 | #ingrs[0, 2] = dataset.recipes_dataset.ingrname_to_ingrid['onion']
79 | #ingrs[0, 3] = dataset.recipes_dataset.ingrname_to_ingrid['chicken']
80 |
81 | input_ = {
82 | 'recipe': {
83 | 'ingrs': {
84 | 'data': Variable(ingrs.cuda(), requires_grad=False),
85 | 'lengths': [ingrs.size(1)]
86 | },
87 | 'instrs': {
88 | 'data': Variable(torch.FloatTensor(1, 1, 1024).fill_(0).cuda(), requires_grad=False),
89 | 'lengths': [1]
90 | }
91 | }
92 | }
93 |
94 | #emb = network.recipe_embedding.forward_ingrs(input_['recipe']['ingrs'])
95 | #list_idx = torch.randperm(len(dataset))
96 |
97 | indices_by_class = dataset._make_indices_by_class()
98 | class_id = dataset.cname_to_cid[class_name]
99 | list_idx = torch.Tensor(indices_by_class[class_id])
100 | rand_idx = torch.randperm(list_idx.size(0))
101 | list_idx = list_idx[rand_idx]
102 |
103 | list_idx = list_idx.view(-1).int()
104 |
105 | img_embs = []
106 | rcp_embs = []
107 |
108 | if nb_points > list_idx.size(0):
109 | nb_points = list_idx.size(0)
110 |
111 | Logger()('Load {} embeddings...'.format(nb_points))
112 | for i in range(nb_points):
113 | idx = list_idx[i]
114 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
115 | path_rcp = os.path.join(dir_rcp, '{}.pth'.format(idx))
116 | if not os.path.isfile(path_img):
117 | Logger()('No such file: {}'.format(path_img))
118 | continue
119 | if not os.path.isfile(path_rcp):
120 | Logger()('No such file: {}'.format(path_rcp))
121 | continue
122 | img_embs.append(torch.load(path_img))
123 | rcp_embs.append(torch.load(path_rcp))
124 |
125 | img_embs = torch.stack(img_embs, 0)
126 | rcp_embs = torch.stack(rcp_embs, 0)
127 |
128 | Logger()('Forward ingredient...')
129 | #ingr_emb = model.network.recipe_embedding(input_['recipe'])
130 | ingr_emb = model.network.recipe_embedding.forward_one_ingr(input_['recipe']['ingrs'])
131 |
132 | ingr_emb = ingr_emb.data.cpu()
133 | ingr_emb = ingr_emb.expand_as(img_embs)
134 |
135 | Logger()('Fast distance...')
136 | dist = fast_distance(img_embs, ingr_emb)[:, 0]
137 |
138 | sorted_ids = np.argsort(dist.numpy())
139 |
140 | dir_visu = os.path.join(dir_exp, 'visu', 'ingrs_to_image_by_class_{}'.format(class_name))
141 | os.system('mkdir -p '+dir_visu)
142 |
143 | Logger()('Load/save images...')
144 | for i in range(20):
145 | idx = int(sorted_ids[i])
146 | item_id = list_idx[idx]
147 | item = dataset[item_id]
148 | Logger()(item['recipe']['class_name'])
149 | Logger()(item['image']['class_name'])
150 | path_img_from = item['image']['path']
151 | path_img_to = os.path.join(dir_visu, 'image_top_{}.png'.format(i+1))
152 | img = Image.open(path_img_from)
153 | img.save(path_img_to)
154 | #os.system('cp {} {}'.format(path_img_from, path_img_to))
155 |
156 |
157 | Logger()('End')
158 |
159 |
160 |
161 |
162 | def fast_distance(A,B):
163 | # A and B must have norm 1 for this to work for the ranking
164 | return torch.mm(A,B.t()) * -1
165 |
166 | # python -m recipe1m.visu.top5
167 | if __name__ == '__main__':
168 | main()
--------------------------------------------------------------------------------
/recipe1m/visu/modality_to_modality_top5.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 | import argparse
18 | parser = argparse.ArgumentParser()
19 | parser.add_argument('modality_to_modality', help='foo help', default='recipe_to_image')
20 | args = parser.parse_args()
21 |
22 |
23 | #classes = ['pizza', 'pork chops', 'cupcake', 'hamburger', 'green beans']
24 | nb_points = 1000
25 | modality_to_modality = args.modality_to_modality#'image_to_image'
26 | print(modality_to_modality)
27 | split = 'test'
28 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
29 | path_opts = os.path.join(dir_exp, 'options.yaml')
30 | dir_extract = os.path.join(dir_exp, 'extract', split)
31 | dir_img = os.path.join(dir_extract, 'image')
32 | dir_rcp = os.path.join(dir_extract, 'recipe')
33 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
34 |
35 | Options.load_from_yaml(path_opts)
36 | Options()['misc']['seed'] = 11
37 | utils.set_random_seed(Options()['misc']['seed'])
38 |
39 | dataset = factory(split)
40 |
41 | Logger()('Load model...')
42 | model = model_factory()
43 | model_state = torch.load(path_model_ckpt)
44 | model.load_state_dict(model_state)
45 | model.eval()
46 |
47 | if not os.path.isdir(dir_extract):
48 | os.system('mkdir -p '+dir_rcp)
49 | os.system('mkdir -p '+dir_img)
50 |
51 | for i in tqdm(range(len(dataset))):
52 | item = dataset[i]
53 | batch = dataset.items_tf()([item])
54 |
55 | if model.is_cuda:
56 | batch = model.cuda_tf()(batch)
57 |
58 | is_volatile = (model.mode not in ['train', 'trainval'])
59 | batch = model.variable_tf(volatile=is_volatile)(batch)
60 |
61 | out = model.network(batch)
62 |
63 | path_image = os.path.join(dir_img, '{}.pth'.format(i))
64 | path_recipe = os.path.join(dir_rcp, '{}.pth'.format(i))
65 | torch.save(out['image_embedding'][0].data.cpu(), path_image)
66 | torch.save(out['recipe_embedding'][0].data.cpu(), path_recipe)
67 |
68 |
69 |
70 | indices_by_class = dataset._make_indices_by_class()
71 |
72 | # class_name = classes[0] # TODO
73 | # class_id = dataset.cname_to_cid[class_name]
74 | # list_idx = torch.Tensor(indices_by_class[class_id])
75 | # rand_idx = torch.randperm(list_idx.size(0))
76 | # list_idx = list_idx[rand_idx]
77 | # list_idx = list_idx.view(-1).int()
78 | list_idx = torch.randperm(len(dataset))
79 |
80 | #nb_points = list_idx.size(0)
81 |
82 | dir_visu = os.path.join(dir_exp, 'visu', '{}_top20_seed:{}'.format(modality_to_modality, Options()['misc']['seed']))
83 | os.system('rm -rf '+dir_visu)
84 | os.system('mkdir -p '+dir_visu)
85 |
86 | Logger()('Load embeddings...')
87 | img_embs = []
88 | rcp_embs = []
89 | for i in range(nb_points):
90 | idx = list_idx[i]
91 | #idx = i
92 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
93 | path_rcp = os.path.join(dir_rcp, '{}.pth'.format(idx))
94 | if not os.path.isfile(path_img):
95 | Logger()('No such file: {}'.format(path_img))
96 | continue
97 | if not os.path.isfile(path_rcp):
98 | Logger()('No such file: {}'.format(path_rcp))
99 | continue
100 | img_embs.append(torch.load(path_img))
101 | rcp_embs.append(torch.load(path_rcp))
102 |
103 | img_embs = torch.stack(img_embs, 0)
104 | rcp_embs = torch.stack(rcp_embs, 0)
105 |
106 | # Logger()('Forward ingredient...')
107 | # #ingr_emb = model.network.recipe_embedding(input_['recipe'])
108 | # ingr_emb = model.network.recipe_embedding.forward_one_ingr(
109 | # input_['recipe']['ingrs'],
110 | # emb_instrs=mean_instrs.unsqueeze(0))
111 |
112 | # ingr_emb = ingr_emb.data.cpu()
113 | # ingr_emb = ingr_emb.expand_as(img_embs)
114 |
115 |
116 | Logger()('Fast distance...')
117 |
118 | if modality_to_modality == 'image_to_recipe':
119 | dist = fast_distance(img_embs, rcp_embs)
120 | elif modality_to_modality == 'recipe_to_image':
121 | dist = fast_distance(rcp_embs, img_embs)
122 | elif modality_to_modality == 'recipe_to_recipe':
123 | dist = fast_distance(rcp_embs, rcp_embs)
124 | elif modality_to_modality == 'image_to_image':
125 | dist = fast_distance(img_embs, img_embs)
126 |
127 | dist=dist[:, 0]
128 | sorted_ids = np.argsort(dist.numpy())
129 |
130 | Logger()('Load/save images in {}...'.format(dir_visu))
131 | for i in range(20):
132 | idx = int(sorted_ids[i])
133 | item_id = list_idx[idx]
134 | #item_id = idx
135 | item = dataset[item_id]
136 | write_img_rcp(dir_visu, item, top=i)
137 | #os.system('cp {} {}'.format(path_img_from, path_img_to))
138 |
139 |
140 | Logger()('End')
141 |
142 | def write_img_rcp(dir_visu, item, top=1):
143 | dir_visu = os.path.join(dir_visu, 'top{}_class:{}_item:{}'.format(top, item['recipe']['class_name'].replace(' ', '_'), item['index']))
144 | path_rcp = dir_visu + '_rcp.txt'
145 | path_img = dir_visu + '_img.png'
146 | #os.system('mkdir -p '+dir_fig_i)
147 |
148 | s = [item['recipe']['layer1']['title']]
149 | s += [d['text'] for d in item['recipe']['layer1']['ingredients']]
150 | s += [d['text'] for d in item['recipe']['layer1']['instructions']]
151 |
152 | with open(path_rcp, 'w') as f:
153 | f.write('\n'.join(s))
154 |
155 | path_img_from = item['image']['path']
156 | img = Image.open(path_img_from)
157 | img.save(path_img)
158 |
159 | # for j in range(5):
160 | # id_img = recipe2im[i,j]
161 | # path_img_load = X_img['path'][id_img]
162 | # class_name = X_img['class_name'][id_img]
163 | # class_name = class_name.replace(' ', '-')
164 | # if id_img == i:
165 | # path_img_save = os.path.join(dir_fig_i, 'img_{}_{}_found.png'.format(j, class_name))
166 | # else:
167 | # path_img_save = os.path.join(dir_fig_i, 'img_{}_{}.png'.format(j, class_name))
168 | # I = load_image(path_img_load, crop_size=500)
169 | # I.save(path_img_save)
170 |
171 | def fast_distance(A,B):
172 | # A and B must have norm 1 for this to work for the ranking
173 | return torch.mm(A,B.t()) * -1
174 |
175 | # python -m recipe1m.visu.top5
176 | if __name__ == '__main__':
177 | main()
--------------------------------------------------------------------------------
/recipe1m/visu/make_menu.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 | #classes = ['hamburger']
18 | #nb_points =
19 | split = 'test'
20 | class_name = None#'potato salad'
21 | modality_to_modality = 'recipe_to_image'
22 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
23 | path_opts = os.path.join(dir_exp, 'options.yaml')
24 | dir_extract = os.path.join(dir_exp, 'extract', split)
25 | dir_extract_mean = os.path.join(dir_exp, 'extract_mean_features', split)
26 | dir_img = os.path.join(dir_extract, 'image')
27 | dir_rcp = os.path.join(dir_extract, 'recipe')
28 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
29 |
30 |
31 |
32 | #is_mean = True
33 | #ingrs_list = ['carotte', 'salad', 'tomato']#['avocado']
34 |
35 | #Options(path_opts)
36 | Options(path_opts)
37 | Options()['misc']['seed'] = 11
38 | utils.set_random_seed(Options()['misc']['seed'])
39 |
40 | chosen_item_id = 51259
41 | dataset = factory(split)
42 | if class_name:
43 | class_id = dataset.cname_to_cid[class_name]
44 | indices_by_class = dataset._make_indices_by_class()
45 | nb_points = len(indices_by_class[class_id])
46 | list_idx = torch.Tensor(indices_by_class[class_id])
47 | rand_idx = torch.randperm(list_idx.size(0))
48 | list_idx = list_idx[rand_idx]
49 | list_idx = list_idx.view(-1).int()
50 | dir_visu = os.path.join(dir_exp, 'visu', 'remove_ingrs_item:{}_nb_points:{}_class:{}'.format(chosen_item_id, nb_points, class_name.replace(' ', '_')))
51 | else:
52 | nb_points = 1000
53 | list_idx = torch.randperm(len(dataset))
54 | dir_visu = os.path.join(dir_exp, 'visu', 'remove_ingrs_item:{}_nb_points:{}_removed'.format(chosen_item_id, nb_points))
55 |
56 |
57 | # for i in range(20):
58 | # item_id = list_idx[i]
59 | # item = dataset[item_id]
60 | # write_img_rcp(dir_visu, item, top=i)
61 |
62 | Logger()('Load model...')
63 | model = model_factory()
64 | model_state = torch.load(path_model_ckpt)
65 | model.load_state_dict(model_state)
66 | model.eval()
67 |
68 | item = dataset[chosen_item_id]
69 |
70 | # from tqdm import tqdm
71 | # ids = []
72 | # for i in tqdm(range(len(dataset.recipes_dataset))):
73 | # item = dataset.recipes_dataset[i]#23534]
74 | # if 'broccoli' in item['ingrs']['interim']:
75 | # print('broccoli', i)
76 | # ids.append(i)
77 |
78 | # # if 'mushroom' in item['ingrs']['interim']:
79 | # # print('mushroom', i)
80 | # # break
81 |
82 | import ipdb; ipdb.set_trace()
83 |
84 |
85 |
86 | # input_ = {
87 | # 'recipe': {
88 | # 'ingrs': {
89 | # 'data': item['recipe']['ingrs']['data'],
90 | # 'lengths': item['recipe']['ingrs']['lengths']
91 | # },
92 | # 'instrs': {
93 | # 'data': item['recipe']['instrs']['data'],
94 | # 'lengths': item['recipe']['instrs']['lengths']
95 | # }
96 | # }
97 | # }
98 |
99 | instrs = torch.FloatTensor(6,1024)
100 | instrs[0] = item['recipe']['instrs']['data'][0]
101 | instrs[1] = item['recipe']['instrs']['data'][1]
102 | instrs[2] = item['recipe']['instrs']['data'][3]
103 | instrs[3] = item['recipe']['instrs']['data'][4]
104 | instrs[4] = item['recipe']['instrs']['data'][6]
105 | instrs[5] = item['recipe']['instrs']['data'][7]
106 |
107 | ingrs = torch.LongTensor([612,585,844,3087,144,188,1])
108 |
109 | input_ = {
110 | 'recipe': {
111 | 'ingrs': {
112 | 'data': ingrs,
113 | 'lengths': ingrs.size(0)
114 | },
115 | 'instrs': {
116 | 'data': instrs,
117 | 'lengths': instrs.size(0)
118 | }
119 | }
120 | }
121 |
122 | batch = dataset.items_tf()([input_])
123 | batch = model.prepare_batch(batch)
124 | out = model.network.recipe_embedding(batch['recipe'])
125 |
126 | # path_rcp = os.path.join(dir_rcp, '{}.pth'.format(23534))
127 | # rcp_emb = torch.load(path_rcp)
128 |
129 |
130 | Logger()('Load embeddings...')
131 | img_embs = []
132 | for i in range(nb_points):
133 | try:
134 | idx = list_idx[i]
135 | except:
136 | import ipdb; ipdb.set_trace()
137 | #idx = i
138 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
139 | if not os.path.isfile(path_img):
140 | Logger()('No such file: {}'.format(path_img))
141 | continue
142 | img_embs.append(torch.load(path_img))
143 |
144 | img_embs = torch.stack(img_embs, 0)
145 |
146 | Logger()('Fast distance...')
147 |
148 | dist = fast_distance(out.data.cpu().expand_as(img_embs), img_embs)
149 | dist = dist[0, :]
150 | sorted_ids = np.argsort(dist.numpy())
151 |
152 | os.system('rm -rf '+dir_visu)
153 | os.system('mkdir -p '+dir_visu)
154 |
155 | Logger()('Load/save images in {}...'.format(dir_visu))
156 | write_img_rcp(dir_visu, item, top=0, begin_with='query')
157 | for i in range(20):
158 | idx = int(sorted_ids[i])
159 | item_id = list_idx[idx]
160 | #item_id = idx
161 | item = dataset[item_id]
162 | write_img_rcp(dir_visu, item, top=i, begin_with='nn')
163 |
164 | Logger()('End')
165 |
166 | def write_img_rcp(dir_visu, item, top=1, begin_with=''):
167 | dir_visu = os.path.join(dir_visu, begin_with+'_top{}_class:{}_item:{}'.format(top, item['recipe']['class_name'].replace(' ', '_'), item['index']))
168 | path_rcp = dir_visu + '_rcp.txt'
169 | path_img = dir_visu + '_img.png'
170 | #os.system('mkdir -p '+dir_fig_i)
171 |
172 | s = [item['recipe']['layer1']['title']]
173 | s += ['\nIngredients raw']
174 | s += [d['text'] for d in item['recipe']['layer1']['ingredients']]
175 | s += ['\nIngredients interim']
176 | s += ['{}: {}'.format(item['recipe']['ingrs']['data'][idx], d) for idx, d in enumerate(item['recipe']['ingrs']['interim'])]
177 | s += ['\nInstructions raw']
178 | s += [d['text'] for d in item['recipe']['layer1']['instructions']]
179 |
180 | with open(path_rcp, 'w') as f:
181 | f.write('\n'.join(s))
182 |
183 | path_img_from = item['image']['path']
184 | img = Image.open(path_img_from)
185 | img.save(path_img)
186 |
187 |
188 | def fast_distance(A,B):
189 | # A and B must have norm 1 for this to work for the ranking
190 | return torch.mm(A,B.t()) * -1
191 |
192 | # python -m recipe1m.visu.top5
193 | if __name__ == '__main__':
194 | main()
--------------------------------------------------------------------------------
/recipe1m/visu/remove_ingrs.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 | #classes = ['hamburger']
18 | #nb_points =
19 | split = 'test'
20 | class_name = None#'potato salad'
21 | modality_to_modality = 'recipe_to_image'
22 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
23 | path_opts = os.path.join(dir_exp, 'options.yaml')
24 | dir_extract = os.path.join(dir_exp, 'extract', split)
25 | dir_extract_mean = os.path.join(dir_exp, 'extract_mean_features', split)
26 | dir_img = os.path.join(dir_extract, 'image')
27 | dir_rcp = os.path.join(dir_extract, 'recipe')
28 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
29 |
30 |
31 |
32 | #is_mean = True
33 | #ingrs_list = ['carotte', 'salad', 'tomato']#['avocado']
34 |
35 | #Options(path_opts)
36 | Options(path_opts)
37 | Options()['misc']['seed'] = 11
38 | utils.set_random_seed(Options()['misc']['seed'])
39 |
40 | chosen_item_id = 51259
41 | dataset = factory(split)
42 | if class_name:
43 | class_id = dataset.cname_to_cid[class_name]
44 | indices_by_class = dataset._make_indices_by_class()
45 | nb_points = len(indices_by_class[class_id])
46 | list_idx = torch.Tensor(indices_by_class[class_id])
47 | rand_idx = torch.randperm(list_idx.size(0))
48 | list_idx = list_idx[rand_idx]
49 | list_idx = list_idx.view(-1).int()
50 | dir_visu = os.path.join(dir_exp, 'visu', 'remove_ingrs_item:{}_nb_points:{}_class:{}'.format(chosen_item_id, nb_points, class_name.replace(' ', '_')))
51 | else:
52 | nb_points = 1000
53 | list_idx = torch.randperm(len(dataset))
54 | dir_visu = os.path.join(dir_exp, 'visu', 'remove_ingrs_item:{}_nb_points:{}_removed'.format(chosen_item_id, nb_points))
55 |
56 |
57 | # for i in range(20):
58 | # item_id = list_idx[i]
59 | # item = dataset[item_id]
60 | # write_img_rcp(dir_visu, item, top=i)
61 |
62 | Logger()('Load model...')
63 | model = model_factory()
64 | model_state = torch.load(path_model_ckpt)
65 | model.load_state_dict(model_state)
66 | model.eval()
67 |
68 | item = dataset[chosen_item_id]
69 |
70 | # from tqdm import tqdm
71 | # ids = []
72 | # for i in tqdm(range(len(dataset.recipes_dataset))):
73 | # item = dataset.recipes_dataset[i]#23534]
74 | # if 'broccoli' in item['ingrs']['interim']:
75 | # print('broccoli', i)
76 | # ids.append(i)
77 |
78 | # # if 'mushroom' in item['ingrs']['interim']:
79 | # # print('mushroom', i)
80 | # # break
81 |
82 | # import ipdb; ipdb.set_trace()
83 |
84 |
85 |
86 | # input_ = {
87 | # 'recipe': {
88 | # 'ingrs': {
89 | # 'data': item['recipe']['ingrs']['data'],
90 | # 'lengths': item['recipe']['ingrs']['lengths']
91 | # },
92 | # 'instrs': {
93 | # 'data': item['recipe']['instrs']['data'],
94 | # 'lengths': item['recipe']['instrs']['lengths']
95 | # }
96 | # }
97 | # }
98 |
99 | instrs = torch.FloatTensor(6,1024)
100 | instrs[0] = item['recipe']['instrs']['data'][0]
101 | instrs[1] = item['recipe']['instrs']['data'][1]
102 | instrs[2] = item['recipe']['instrs']['data'][3]
103 | instrs[3] = item['recipe']['instrs']['data'][4]
104 | instrs[4] = item['recipe']['instrs']['data'][6]
105 | instrs[5] = item['recipe']['instrs']['data'][7]
106 |
107 | ingrs = torch.LongTensor([612,585,844,3087,144,188,1])
108 |
109 | input_ = {
110 | 'recipe': {
111 | 'ingrs': {
112 | 'data': ingrs,
113 | 'lengths': ingrs.size(0)
114 | },
115 | 'instrs': {
116 | 'data': instrs,
117 | 'lengths': instrs.size(0)
118 | }
119 | }
120 | }
121 |
122 | batch = dataset.items_tf()([input_])
123 | batch = model.prepare_batch(batch)
124 | out = model.network.recipe_embedding(batch['recipe'])
125 |
126 | # path_rcp = os.path.join(dir_rcp, '{}.pth'.format(23534))
127 | # rcp_emb = torch.load(path_rcp)
128 |
129 |
130 | Logger()('Load embeddings...')
131 | img_embs = []
132 | for i in range(nb_points):
133 | try:
134 | idx = list_idx[i]
135 | except:
136 | import ipdb; ipdb.set_trace()
137 | #idx = i
138 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
139 | if not os.path.isfile(path_img):
140 | Logger()('No such file: {}'.format(path_img))
141 | continue
142 | img_embs.append(torch.load(path_img))
143 |
144 | img_embs = torch.stack(img_embs, 0)
145 |
146 | Logger()('Fast distance...')
147 |
148 | dist = fast_distance(out.data.cpu().expand_as(img_embs), img_embs)
149 | dist = dist[0, :]
150 | sorted_ids = np.argsort(dist.numpy())
151 |
152 | os.system('rm -rf '+dir_visu)
153 | os.system('mkdir -p '+dir_visu)
154 |
155 | Logger()('Load/save images in {}...'.format(dir_visu))
156 | write_img_rcp(dir_visu, item, top=0, begin_with='query')
157 | for i in range(20):
158 | idx = int(sorted_ids[i])
159 | item_id = list_idx[idx]
160 | #item_id = idx
161 | item = dataset[item_id]
162 | write_img_rcp(dir_visu, item, top=i, begin_with='nn')
163 |
164 | Logger()('End')
165 |
166 | def write_img_rcp(dir_visu, item, top=1, begin_with=''):
167 | dir_visu = os.path.join(dir_visu, begin_with+'_top{}_class:{}_item:{}'.format(top, item['recipe']['class_name'].replace(' ', '_'), item['index']))
168 | path_rcp = dir_visu + '_rcp.txt'
169 | path_img = dir_visu + '_img.png'
170 | #os.system('mkdir -p '+dir_fig_i)
171 |
172 | s = [item['recipe']['layer1']['title']]
173 | s += ['\nIngredients raw']
174 | s += [d['text'] for d in item['recipe']['layer1']['ingredients']]
175 | s += ['\nIngredients interim']
176 | s += ['{}: {}'.format(item['recipe']['ingrs']['data'][idx], d) for idx, d in enumerate(item['recipe']['ingrs']['interim'])]
177 | s += ['\nInstructions raw']
178 | s += [d['text'] for d in item['recipe']['layer1']['instructions']]
179 |
180 | with open(path_rcp, 'w') as f:
181 | f.write('\n'.join(s))
182 |
183 | path_img_from = item['image']['path']
184 | img = Image.open(path_img_from)
185 | img.save(path_img)
186 |
187 |
188 | def fast_distance(A,B):
189 | # A and B must have norm 1 for this to work for the ranking
190 | return torch.mm(A,B.t()) * -1
191 |
192 | # python -m recipe1m.visu.top5
193 | if __name__ == '__main__':
194 | main()
--------------------------------------------------------------------------------
/recipe1m/visu/ingrs_to_images_per_class.py:
--------------------------------------------------------------------------------
1 | import os
2 | import torch
3 | import numpy as np
4 | from bootstrap.lib.logger import Logger
5 | from bootstrap.lib.options import Options
6 | from recipe1m.datasets.factory import factory
7 | from bootstrap.models.factory import factory as model_factory
8 | from torch.autograd import Variable
9 | from PIL import Image
10 | from tqdm import tqdm
11 | import bootstrap.lib.utils as utils
12 |
13 | def main():
14 |
15 | Logger('.')
16 |
17 |
18 |
19 | #classes = ['pizza', 'pork chops', 'cupcake', 'hamburger', 'green beans']
20 | nb_points = 1000
21 | split = 'test'
22 | class_name = 'pizza'
23 | dir_exp = '/home/cadene/doc/bootstrap.pytorch/logs/recipe1m/trijoint/2017-12-14-15-04-51'
24 | path_opts = os.path.join(dir_exp, 'options.yaml')
25 | dir_extract = os.path.join(dir_exp, 'extract', split)
26 | dir_extract_mean = os.path.join(dir_exp, 'extract_mean_features', split)
27 | dir_img = os.path.join(dir_extract, 'image')
28 | dir_rcp = os.path.join(dir_extract, 'recipe')
29 | path_model_ckpt = os.path.join(dir_exp, 'ckpt_best_val_epoch.metric.recall_at_1_im2recipe_mean_model.pth.tar')
30 |
31 | is_mean = True
32 | ingrs_list = ['fresh_strawberries']#['avocado']
33 |
34 |
35 | Options(path_opts)
36 | Options()['misc']['seed'] = 2
37 | utils.set_random_seed(Options()['misc']['seed'])
38 |
39 | dataset = factory(split)
40 |
41 | Logger()('Load model...')
42 | model = model_factory()
43 | model_state = torch.load(path_model_ckpt)
44 | model.load_state_dict(model_state)
45 | model.eval()
46 |
47 | if not os.path.isdir(dir_extract):
48 | os.system('mkdir -p '+dir_rcp)
49 | os.system('mkdir -p '+dir_img)
50 |
51 | for i in tqdm(range(len(dataset))):
52 | item = dataset[i]
53 | batch = dataset.items_tf()([item])
54 |
55 | if model.is_cuda:
56 | batch = model.cuda_tf()(batch)
57 |
58 | is_volatile = (model.mode not in ['train', 'trainval'])
59 | batch = model.variable_tf(volatile=is_volatile)(batch)
60 |
61 | out = model.network(batch)
62 |
63 | path_image = os.path.join(dir_img, '{}.pth'.format(i))
64 | path_recipe = os.path.join(dir_rcp, '{}.pth'.format(i))
65 | torch.save(out['image_embedding'][0].data.cpu(), path_image)
66 | torch.save(out['recipe_embedding'][0].data.cpu(), path_recipe)
67 |
68 |
69 |
70 | # b = dataset.make_batch_loader().__iter__().__next__()
71 | # import ipdb; ipdb.set_trace()
72 |
73 | ingrs = torch.LongTensor(1, len(ingrs_list))
74 | for i, ingr_name in enumerate(ingrs_list):
75 | ingrs[0, i] = dataset.recipes_dataset.ingrname_to_ingrid[ingr_name]
76 |
77 | input_ = {
78 | 'recipe': {
79 | 'ingrs': {
80 | 'data': Variable(ingrs.cuda(), requires_grad=False),
81 | 'lengths': [ingrs.size(1)]
82 | },
83 | 'instrs': {
84 | 'data': Variable(torch.FloatTensor(1, 1, 1024).fill_(0).cuda(), requires_grad=False),
85 | 'lengths': [1]
86 | }
87 | }
88 | }
89 |
90 | #emb = network.recipe_embedding.forward_ingrs(input_['recipe']['ingrs'])
91 | #list_idx = torch.randperm(len(dataset))
92 |
93 | indices_by_class = dataset._make_indices_by_class()
94 |
95 | #import ipdb; ipdb.set_trace()
96 |
97 | class_id = dataset.cname_to_cid[class_name]
98 | list_idx = torch.Tensor(indices_by_class[class_id])
99 | rand_idx = torch.randperm(list_idx.size(0))
100 | list_idx = list_idx[rand_idx]
101 | list_idx = list_idx.view(-1).int()
102 |
103 | nb_points = list_idx.size(0)
104 |
105 | dir_visu = os.path.join(dir_exp, 'visu', 'ingrs_to_image_nb_points:{}_class:{}_instrs:{}_mean:{}_v2'.format(nb_points, class_name, '-'.join(ingrs_list), is_mean))
106 | os.system('mkdir -p '+dir_visu)
107 |
108 | Logger()('Load embeddings...')
109 | img_embs = []
110 | rcp_embs = []
111 | for i in range(nb_points):
112 | idx = list_idx[i]
113 | path_img = os.path.join(dir_img, '{}.pth'.format(idx))
114 | path_rcp = os.path.join(dir_rcp, '{}.pth'.format(idx))
115 | if not os.path.isfile(path_img):
116 | Logger()('No such file: {}'.format(path_img))
117 | continue
118 | if not os.path.isfile(path_rcp):
119 | Logger()('No such file: {}'.format(path_rcp))
120 | continue
121 | img_embs.append(torch.load(path_img))
122 | rcp_embs.append(torch.load(path_rcp))
123 |
124 | img_embs = torch.stack(img_embs, 0)
125 | rcp_embs = torch.stack(rcp_embs, 0)
126 |
127 | Logger()('Load mean embeddings')
128 |
129 | path_ingrs = os.path.join(dir_extract_mean, 'ingrs.pth')
130 | path_instrs = os.path.join(dir_extract_mean, 'instrs.pth')
131 |
132 | mean_ingrs = torch.load(path_ingrs)
133 | mean_instrs = torch.load(path_instrs)
134 |
135 | Logger()('Forward ingredient...')
136 | #ingr_emb = model.network.recipe_embedding(input_['recipe'])
137 | ingr_emb = model.network.recipe_embedding.forward_one_ingr(
138 | input_['recipe']['ingrs'],
139 | emb_instrs=mean_instrs.unsqueeze(0))
140 |
141 | ingr_emb = ingr_emb.data.cpu()
142 | ingr_emb = ingr_emb.expand_as(img_embs)
143 |
144 | Logger()('Fast distance...')
145 | dist = fast_distance(img_embs, ingr_emb)[:, 0]
146 |
147 | sorted_ids = np.argsort(dist.numpy())
148 |
149 | Logger()('Load/save images in {}...'.format(dir_visu))
150 | for i in range(20):
151 | idx = int(sorted_ids[i])
152 | item_id = list_idx[idx]
153 | item = dataset[item_id]
154 | # path_img_from = item['image']['path']
155 | # ingrs = [ingr.replace('/', '\'') for ingr in item['recipe']['ingrs']['interim']]
156 | # cname = item['recipe']['class_name']
157 | # path_img_to = os.path.join(dir_visu, 'image_top:{}_ingrs:{}_cname:{}.png'.format(i+1, '-'.join(ingrs), cname))
158 | # img = Image.open(path_img_from)
159 | # img.save(path_img_to)
160 | #os.system('cp {} {}'.format(path_img_from, path_img_to))
161 |
162 | write_img_rcp(dir_visu, item, top=i, begin_with='nn')
163 |
164 | Logger()('End')
165 |
166 | def write_img_rcp(dir_visu, item, top=1, begin_with=''):
167 | dir_visu = os.path.join(dir_visu, begin_with+'_top{}_class:{}_item:{}'.format(top, item['recipe']['class_name'].replace(' ', '_'), item['index']))
168 | path_rcp = dir_visu + '_rcp.txt'
169 | path_img = dir_visu + '_img.png'
170 | #os.system('mkdir -p '+dir_fig_i)
171 |
172 | s = [item['recipe']['layer1']['title']]
173 | s += ['\nIngredients raw']
174 | s += [d['text'] for d in item['recipe']['layer1']['ingredients']]
175 | s += ['\nIngredients interim']
176 | s += ['{}: {}'.format(item['recipe']['ingrs']['data'][idx], d) for idx, d in enumerate(item['recipe']['ingrs']['interim'])]
177 | s += ['\nInstructions raw']
178 | s += [d['text'] for d in item['recipe']['layer1']['instructions']]
179 |
180 | with open(path_rcp, 'w') as f:
181 | f.write('\n'.join(s))
182 |
183 | path_img_from = item['image']['path']
184 | img = Image.open(path_img_from)
185 | img.save(path_img)
186 |
187 |
188 | Logger()('End')
189 |
190 |
191 |
192 |
193 | def fast_distance(A,B):
194 | # A and B must have norm 1 for this to work for the ranking
195 | return torch.mm(A,B.t()) * -1
196 |
197 | # python -m recipe1m.visu.top5
198 | if __name__ == '__main__':
199 | main()
--------------------------------------------------------------------------------
/demo_web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Recipe1M Demo Adamine
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |