From ae768d6aeea9c6de41c10b3b2c1febe3db0922df Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Wed, 21 Jul 2021 04:51:45 +0000 Subject: [PATCH 01/81] feat: support to enable mixup and cutmix at same time --- .../preprocess/batch_ops/batch_operators.py | 66 +++++++++++++------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/ppcls/data/preprocess/batch_ops/batch_operators.py b/ppcls/data/preprocess/batch_ops/batch_operators.py index 769045b54..55c1b5a44 100644 --- a/ppcls/data/preprocess/batch_ops/batch_operators.py +++ b/ppcls/data/preprocess/batch_ops/batch_operators.py @@ -23,6 +23,7 @@ from ppcls.data.preprocess.ops.fmix import sample_mask class BatchOperator(object): """ BatchOperator """ + def __init__(self, *args, **kwargs): pass @@ -45,35 +46,44 @@ class BatchOperator(object): class MixupOperator(BatchOperator): - """ Mixup operator """ - def __init__(self, alpha=0.2): - assert alpha > 0., \ - 'parameter alpha[%f] should > 0.0' % (alpha) - self._alpha = alpha + """Mixup and Cutmix operator""" - def __call__(self, batch): - imgs, labels, bs = self._unpack(batch) + def __init__(self, + mixup_alpha: float=1., + cutmix_alpha: float=0., + switch_prob: float=0.5): + """Build Mixup operator + + Args: + mixup_alpha (float, optional): The parameter alpha of mixup, mixup is active if > 0. Defaults to 1.. + cutmix_alpha (float, optional): The parameter alpha of cutmix, cutmix is active if > 0. Defaults to 0.. + switch_prob (float, optional): The probability of switching to cutmix instead of mixup when both are active. Defaults to 0.5. + + Raises: + Exception: The value of parameters are illegal. + """ + if mixup_alpha <= 0 and cutmix_alpha <= 0: + raise Exception( + f"At least one of parameter alpha of Mixup and Cutmix is greater than 0. mixup_alpha: {mixup_alpha}, cutmix_alpha: {cutmix_alpha}" + ) + self._mixup_alpha = mixup_alpha + self._cutmix_alpha = cutmix_alpha + self._switch_prob = switch_prob + + def _mixup(self, imgs, labels, bs): idx = np.random.permutation(bs) - lam = np.random.beta(self._alpha, self._alpha) + lam = np.random.beta(self._mixup_alpha, self._mixup_alpha) lams = np.array([lam] * bs, dtype=np.float32) imgs = lam * imgs + (1 - lam) * imgs[idx] return list(zip(imgs, labels, labels[idx], lams)) - -class CutmixOperator(BatchOperator): - """ Cutmix operator """ - def __init__(self, alpha=0.2): - assert alpha > 0., \ - 'parameter alpha[%f] should > 0.0' % (alpha) - self._alpha = alpha - def _rand_bbox(self, size, lam): """ _rand_bbox """ w = size[2] h = size[3] cut_rat = np.sqrt(1. - lam) - cut_w = np.int(w * cut_rat) - cut_h = np.int(h * cut_rat) + cut_w = int(w * cut_rat) + cut_h = int(h * cut_rat) # uniform cx = np.random.randint(w) @@ -86,10 +96,9 @@ class CutmixOperator(BatchOperator): return bbx1, bby1, bbx2, bby2 - def __call__(self, batch): - imgs, labels, bs = self._unpack(batch) + def _cutmix(self, imgs, labels, bs): idx = np.random.permutation(bs) - lam = np.random.beta(self._alpha, self._alpha) + lam = np.random.beta(self._cutmix_alpha, self._cutmix_alpha) bbx1, bby1, bbx2, bby2 = self._rand_bbox(imgs.shape, lam) imgs[:, :, bbx1:bbx2, bby1:bby2] = imgs[idx, :, bbx1:bbx2, bby1:bby2] @@ -98,9 +107,24 @@ class CutmixOperator(BatchOperator): lams = np.array([lam] * bs, dtype=np.float32) return list(zip(imgs, labels, labels[idx], lams)) + def __call__(self, batch): + imgs, labels, bs = self._unpack(batch) + if np.random.rand() < self._switch_prob: + return self._cutmix(imgs, labels, bs) + else: + return self._mixup(imgs, labels, bs) + + +class CutmixOperator(BatchOperator): + def __init__(self, **kwargs): + raise Exception( + f"\"CutmixOperator\" has been deprecated. Please use MixupOperator with \"cutmix_alpha\" and \"switch_prob\" to enable Cutmix. Refor to doc for details." + ) + class FmixOperator(BatchOperator): """ Fmix operator """ + def __init__(self, alpha=1, decay_power=3, max_soft=0., reformulate=False): self._alpha = alpha self._decay_power = decay_power From 079434dc5fbd671153493561e005acb318fcbf87 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Tue, 24 Aug 2021 07:24:56 +0000 Subject: [PATCH 02/81] feat: add AdamW --- ppcls/engine/engine.py | 2 +- ppcls/optimizer/__init__.py | 14 ++++---- ppcls/optimizer/optimizer.py | 67 +++++++++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/ppcls/engine/engine.py b/ppcls/engine/engine.py index ad5c584f0..03842dd73 100644 --- a/ppcls/engine/engine.py +++ b/ppcls/engine/engine.py @@ -175,7 +175,7 @@ class Engine(object): if self.mode == 'train': self.optimizer, self.lr_sch = build_optimizer( self.config["Optimizer"], self.config["Global"]["epochs"], - len(self.train_dataloader), self.model.parameters()) + len(self.train_dataloader), [self.model]) # for distributed self.config["Global"][ diff --git a/ppcls/optimizer/__init__.py b/ppcls/optimizer/__init__.py index 0ccb7d197..cc64a9caa 100644 --- a/ppcls/optimizer/__init__.py +++ b/ppcls/optimizer/__init__.py @@ -41,19 +41,22 @@ def build_lr_scheduler(lr_config, epochs, step_each_epoch): return lr -def build_optimizer(config, epochs, step_each_epoch, parameters=None): +def build_optimizer(config, epochs, step_each_epoch, model_list): config = copy.deepcopy(config) # step1 build lr lr = build_lr_scheduler(config.pop('lr'), epochs, step_each_epoch) logger.debug("build lr ({}) success..".format(lr)) # step2 build regularization if 'regularizer' in config and config['regularizer'] is not None: + if 'weight_decay' in config: + logger.warning( + "ConfigError: Only one of regularizer and weight_decay can be set in Optimizer Config. \"weight_decay\" has been ignored." + ) reg_config = config.pop('regularizer') reg_name = reg_config.pop('name') + 'Decay' reg = getattr(paddle.regularizer, reg_name)(**reg_config) - else: - reg = None - logger.debug("build regularizer ({}) success..".format(reg)) + config["weight_decay"] = reg + logger.debug("build regularizer ({}) success..".format(reg)) # step3 build optimizer optim_name = config.pop('name') if 'clip_norm' in config: @@ -62,8 +65,7 @@ def build_optimizer(config, epochs, step_each_epoch, parameters=None): else: grad_clip = None optim = getattr(optimizer, optim_name)(learning_rate=lr, - weight_decay=reg, grad_clip=grad_clip, - **config)(parameters=parameters) + **config)(model_list=model_list) logger.debug("build optimizer ({}) success..".format(optim)) return optim, lr diff --git a/ppcls/optimizer/optimizer.py b/ppcls/optimizer/optimizer.py index 534108ed7..72310f27b 100644 --- a/ppcls/optimizer/optimizer.py +++ b/ppcls/optimizer/optimizer.py @@ -35,14 +35,15 @@ class Momentum(object): weight_decay=None, grad_clip=None, multi_precision=False): - super(Momentum, self).__init__() + super().__init__() self.learning_rate = learning_rate self.momentum = momentum self.weight_decay = weight_decay self.grad_clip = grad_clip self.multi_precision = multi_precision - def __call__(self, parameters): + def __call__(self, model_list): + parameters = sum([m.parameters() for m in model_list], []) opt = optim.Momentum( learning_rate=self.learning_rate, momentum=self.momentum, @@ -77,7 +78,8 @@ class Adam(object): self.lazy_mode = lazy_mode self.multi_precision = multi_precision - def __call__(self, parameters): + def __call__(self, model_list): + parameters = sum([m.parameters() for m in model_list], []) opt = optim.Adam( learning_rate=self.learning_rate, beta1=self.beta1, @@ -112,7 +114,7 @@ class RMSProp(object): weight_decay=None, grad_clip=None, multi_precision=False): - super(RMSProp, self).__init__() + super().__init__() self.learning_rate = learning_rate self.momentum = momentum self.rho = rho @@ -120,7 +122,8 @@ class RMSProp(object): self.weight_decay = weight_decay self.grad_clip = grad_clip - def __call__(self, parameters): + def __call__(self, model_list): + parameters = sum([m.parameters() for m in model_list], []) opt = optim.RMSProp( learning_rate=self.learning_rate, momentum=self.momentum, @@ -130,3 +133,57 @@ class RMSProp(object): grad_clip=self.grad_clip, parameters=parameters) return opt + + +class AdamW(object): + def __init__(self, + learning_rate=0.001, + beta1=0.9, + beta2=0.999, + epsilon=1e-8, + weight_decay=None, + multi_precision=False, + grad_clip=None, + no_weight_decay_name=None, + one_dim_param_no_weight_decay=False, + **args): + super().__init__() + self.learning_rate = learning_rate + self.beta1 = beta1 + self.beta2 = beta2 + self.epsilon = epsilon + self.grad_clip = grad_clip + self.weight_decay = weight_decay + self.multi_precision = multi_precision + self.no_weight_decay_name_list = no_weight_decay_name.split( + ) if no_weight_decay_name else [] + self.one_dim_param_no_weight_decay = one_dim_param_no_weight_decay + + def __call__(self, model_list): + parameters = sum([m.parameters() for m in model_list], []) + + self.no_weight_decay_param_name_list = [ + p.name for model in model_list for n, p in model.named_parameters() + if any(nd in n for nd in self.no_weight_decay_name_list) + ] + + if self.one_dim_param_no_weight_decay: + self.no_weight_decay_param_name_list += [ + p.name for model in model_list + for n, p in model.named_parameters() if len(p.shape) == 1 + ] + + opt = optim.AdamW( + learning_rate=self.learning_rate, + beta1=self.beta1, + beta2=self.beta2, + epsilon=self.epsilon, + parameters=parameters, + weight_decay=self.weight_decay, + multi_precision=self.multi_precision, + grad_clip=self.grad_clip, + apply_decay_param_fun=self._apply_decay_param_fun) + return opt + + def _apply_decay_param_fun(self, name): + return name not in self.no_weight_decay_param_name_list From b6144fb7cf2ae87248a0a19c232e02a513f81b52 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sat, 4 Sep 2021 17:11:23 +0800 Subject: [PATCH 03/81] add mix dataloader and mix sampler --- ppcls/data/dataloader/__init__.py | 6 +++ ppcls/data/dataloader/mix_dataset.py | 49 ++++++++++++++++++ ppcls/data/dataloader/mix_sampler.py | 74 ++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 ppcls/data/dataloader/mix_dataset.py create mode 100644 ppcls/data/dataloader/mix_sampler.py diff --git a/ppcls/data/dataloader/__init__.py b/ppcls/data/dataloader/__init__.py index e69de29bb..5d6fe91fc 100644 --- a/ppcls/data/dataloader/__init__.py +++ b/ppcls/data/dataloader/__init__.py @@ -0,0 +1,6 @@ +from ppcls.data.dataloader.imagenet_dataset import ImageNetDataset +from ppcls.data.dataloader.multilabel_dataset import MultiLabelDataset +from ppcls.data.dataloader.common_dataset import create_operators +from ppcls.data.dataloader.vehicle_dataset import CompCars, VeriWild +from ppcls.data.dataloader.logo_dataset import LogoDataset +from ppcls.data.dataloader.icartoon_dataset import ICartoonDataset diff --git a/ppcls/data/dataloader/mix_dataset.py b/ppcls/data/dataloader/mix_dataset.py new file mode 100644 index 000000000..c928ca26d --- /dev/null +++ b/ppcls/data/dataloader/mix_dataset.py @@ -0,0 +1,49 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import numpy as np +import os + +from paddle.io import Dataset +from .. import dataloader + + +class MixDataset(Dataset): + def __init__(self, datasets_config): + super(MixDataset, self).__init__() + self.dataset_list = [] + start_idx = 0 + end_idx = 0 + for config_i in datasets_config: + dataset_name = config_i.pop('name') + dataset = getattr(dataloader, dataset_name)(**config_i) + end_idx += len(dataset) + self.dataset_list.append([end_idx, start_idx, dataset]) + start_idx = end_idx + + self.length = end_idx + + def __getitem__(self, idx): + for dataset_i in self.dataset_list: + if dataset_i[0] > idx: + dataset_i_idx = idx - dataset_i[1] + return dataset_i[2][dataset_i_idx] + + def __len__(self): + return self.length + + def get_dataset_list(self): + return self.dataset_list diff --git a/ppcls/data/dataloader/mix_sampler.py b/ppcls/data/dataloader/mix_sampler.py new file mode 100644 index 000000000..eaac66de0 --- /dev/null +++ b/ppcls/data/dataloader/mix_sampler.py @@ -0,0 +1,74 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division + +from paddle.io import DistributedBatchSampler, Sampler + +from ppcls.utils import logger +from ppcls.data.dataloader.mix_dataset import MixDataset +from ppcls.data import dataloader + + +class MixSampler(DistributedBatchSampler): + def __init__(self, dataset, batch_size, sample_configs, iter_per_epoch): + super(MixSampler, self).__init__(dataset, batch_size) + assert isinstance(dataset, MixDataset), "MixSampler only support MixDataset" + self.sampler_list = [] + self.batch_size = batch_size + self.start_list = [] + self.length = iter_per_epoch + dataset_list = dataset.get_dataset_list() + batch_size_left = self.batch_size + self.iter_list = [] + for i, config_i in enumerate(sample_configs): + sample_method = config_i.pop("name") + ratio_i = config_i.pop("ratio") + if i < len(sample_configs) - 1: + batch_size_i = self.batch_size * ratio_i + batch_size_left -= batch_size_i + else: + batch_size_i = batch_size_left + assert batch_size_i <= len(dataset_list[i][2]) + config_i["batch_size"] = batch_size_i + if sample_method == "DistributedBatchSampler": + sampler_i = DistributedBatchSampler(dataset_list[i][2], **config_i) + else: + sampler_i = getattr(dataloader, sample_method)(dataset_list[i][2], **config_i) + self.sampler_list.append(sampler_i) + self.iter_list.append(iter(sampler_i)) + self.length += len(dataset_list[i][2]) * ratio_i + self.iter_counter = 0 + + def __iter__(self): + while self.iter_counter < self.length: + batch = [] + for i, iter_i in enumerate(self.iter_list): + batch_i = next(iter_i, None) + if batch_i is None: + iter_i = iter(self.sampler_list[i]) + self.iter_list[i] = iter_i + batch_i = next(iter_i, None) + assert batch_i is not None, "dataset {} return None".format(i) + batch += [idx + self.start_list[i] for idx in batch_i] + if len(batch) == self.batch_size: + self.iter_counter += 1 + yield batch + else: + logger.info("Some dataset reaches end") + self.iter_counter = 0 + + def __len__(self): + return self.length From 4dc175c9660ff1f7bd8e3db1c792cdf650b5ef5f Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sat, 4 Sep 2021 18:47:08 +0800 Subject: [PATCH 04/81] add write_hard dataset and sampler --- ppcls/data/dataloader/__init__.py | 4 + ppcls/data/dataloader/writer_hard_dataset.py | 38 +++++++++ ppcls/data/dataloader/writer_hard_sampler.py | 82 ++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 ppcls/data/dataloader/writer_hard_dataset.py create mode 100644 ppcls/data/dataloader/writer_hard_sampler.py diff --git a/ppcls/data/dataloader/__init__.py b/ppcls/data/dataloader/__init__.py index 5d6fe91fc..1a08f9897 100644 --- a/ppcls/data/dataloader/__init__.py +++ b/ppcls/data/dataloader/__init__.py @@ -4,3 +4,7 @@ from ppcls.data.dataloader.common_dataset import create_operators from ppcls.data.dataloader.vehicle_dataset import CompCars, VeriWild from ppcls.data.dataloader.logo_dataset import LogoDataset from ppcls.data.dataloader.icartoon_dataset import ICartoonDataset +from ppcls.data.dataloader.writer_hard_sampler import WriterHardSampler +from ppcls.data.dataloader.mix_dataset import MixDataset +from ppcls.data.dataloader.mix_sampler import MixSampler +from ppcls.data.dataloader.writer_hard_dataset import WriterHardDataset diff --git a/ppcls/data/dataloader/writer_hard_dataset.py b/ppcls/data/dataloader/writer_hard_dataset.py new file mode 100644 index 000000000..83aacb595 --- /dev/null +++ b/ppcls/data/dataloader/writer_hard_dataset.py @@ -0,0 +1,38 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import numpy as np +import os + +from .common_dataset import CommonDataset + + +class WriterHardDataset(CommonDataset): + def _load_anno(self, seed=None): + assert os.path.exists(self._cls_path) + assert os.path.exists(self._img_root) + self.images = [] + self.labels = [] + + with open(self._cls_path) as fd: + self.anno_list = fd.readlines() + if seed is not None: + np.random.RandomState(seed).shuffle(self.anno_list) + for l in self.anno_list: + l = l.strip().split(" ") + self.images.append(os.path.join(self._img_root, l[0])) + self.labels.append(int(l[1])) + assert os.path.exists(self.images[-1]) diff --git a/ppcls/data/dataloader/writer_hard_sampler.py b/ppcls/data/dataloader/writer_hard_sampler.py new file mode 100644 index 000000000..856ffd260 --- /dev/null +++ b/ppcls/data/dataloader/writer_hard_sampler.py @@ -0,0 +1,82 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from collections import defaultdict +import numpy as np +import copy +import random +from paddle.io import DistributedBatchSampler + +from ppcls.data.dataloader.writer_hard_dataset import WriterHardDataset + + +class WriterHardSampler(DistributedBatchSampler): + """ + Randomly sample N anchor, then for each identity, + randomly sample 2 positive and 1 negative for each anchor, therefore batch size is N*4. + Args: + - data_source (list): list of (img_path, pid, camid). + - num_instances (int): number of instances per identity in a batch. + - batch_size (int): number of examples in a batch. + """ + + def __init__(self, dataset, batch_size, **args): + super(WriterHardSampler, self).__init__(dataset, batch_size) + self.dataset = dataset + self.batch_size = batch_size + assert not self.batch_size % 4, "bs of WriterHardSampler should be 3*N" + assert isinstance(dataset, WriterHardDataset), "WriterHardSampler only support WriterHardDataset" + self.num_pids_per_batch = self.batch_size // 4 + self.anchor_list = [] + self.person_id_map = {} + self.text_id_map = {} + anno_list = dataset.anno_list + for i, anno_i in enumerate(anno_list): + _, person_id, text_id = anno_i.split(" ") + if text_id != "-1": + if random.random() < 0.5: + self.anchor_list.append([i, person_id, text_id]) + else: + if text_id in self.text_id_map: + self.text_id_map[text_id].append(i) + else: + self.text_id_map[text_id] = [i] + else: + if person_id in self.person_id_map: + self.person_id_map[person_id].append(i) + else: + self.person_id_map[person_id] = [i] + + assert len(self.anchor_list) < self.batch_size, "anchor should be larger than batch_size" + + def __iter__(self): + random.shuffle(self.anchor_list) + for i in range(len(self)): + batch_indices = [] + for j in range(self.batch_size // 4): + anchor = self.anchor_list[i * self.batch_size // 4 + j] + anchor_index = anchor[0] + anchor_person_id = anchor[1] + anchor_text_id = anchor[2] + person_indices = random.sample(self.person_id_map[anchor_person_id], 2) + text_index = random.choice(self.text_id_map[anchor_text_id]) + batch_indices.append(anchor_index) + batch_indices += person_indices + batch_indices.append(text_index) + yield batch_indices + + def __len__(self): + len(self.anchor_list) * 4 // self.batch_size From de298b1ba2914c298191f8e12b3914552752dadb Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sat, 4 Sep 2021 22:07:55 +0800 Subject: [PATCH 05/81] dbg --- ppcls/data/__init__.py | 4 ++++ ppcls/data/dataloader/mix_sampler.py | 3 ++- ppcls/data/dataloader/writer_hard_sampler.py | 13 +++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/ppcls/data/__init__.py b/ppcls/data/__init__.py index b442aa883..2664c296b 100644 --- a/ppcls/data/__init__.py +++ b/ppcls/data/__init__.py @@ -26,9 +26,13 @@ from ppcls.data.dataloader.common_dataset import create_operators from ppcls.data.dataloader.vehicle_dataset import CompCars, VeriWild from ppcls.data.dataloader.logo_dataset import LogoDataset from ppcls.data.dataloader.icartoon_dataset import ICartoonDataset +from ppcls.data.dataloader.mix_dataset import MixDataset +from ppcls.data.dataloader.writer_hard_dataset import WriterHardDataset # sampler from ppcls.data.dataloader.DistributedRandomIdentitySampler import DistributedRandomIdentitySampler +from ppcls.data.dataloader.writer_hard_sampler import WriterHardSampler +from ppcls.data.dataloader.mix_sampler import MixSampler from ppcls.data import preprocess from ppcls.data.preprocess import transform diff --git a/ppcls/data/dataloader/mix_sampler.py b/ppcls/data/dataloader/mix_sampler.py index eaac66de0..d4206be75 100644 --- a/ppcls/data/dataloader/mix_sampler.py +++ b/ppcls/data/dataloader/mix_sampler.py @@ -34,10 +34,11 @@ class MixSampler(DistributedBatchSampler): batch_size_left = self.batch_size self.iter_list = [] for i, config_i in enumerate(sample_configs): + self.start_list.append(dataset_list[i][1]) sample_method = config_i.pop("name") ratio_i = config_i.pop("ratio") if i < len(sample_configs) - 1: - batch_size_i = self.batch_size * ratio_i + batch_size_i = int(self.batch_size * ratio_i) batch_size_left -= batch_size_i else: batch_size_i = batch_size_left diff --git a/ppcls/data/dataloader/writer_hard_sampler.py b/ppcls/data/dataloader/writer_hard_sampler.py index 856ffd260..e4ff42582 100644 --- a/ppcls/data/dataloader/writer_hard_sampler.py +++ b/ppcls/data/dataloader/writer_hard_sampler.py @@ -33,10 +33,11 @@ class WriterHardSampler(DistributedBatchSampler): - batch_size (int): number of examples in a batch. """ - def __init__(self, dataset, batch_size, **args): + def __init__(self, dataset, batch_size, shuffle=True, **args): super(WriterHardSampler, self).__init__(dataset, batch_size) self.dataset = dataset self.batch_size = batch_size + self.shuffle = shuffle assert not self.batch_size % 4, "bs of WriterHardSampler should be 3*N" assert isinstance(dataset, WriterHardDataset), "WriterHardSampler only support WriterHardDataset" self.num_pids_per_batch = self.batch_size // 4 @@ -45,7 +46,7 @@ class WriterHardSampler(DistributedBatchSampler): self.text_id_map = {} anno_list = dataset.anno_list for i, anno_i in enumerate(anno_list): - _, person_id, text_id = anno_i.split(" ") + _, person_id, text_id = anno_i.strip().split(" ") if text_id != "-1": if random.random() < 0.5: self.anchor_list.append([i, person_id, text_id]) @@ -59,11 +60,11 @@ class WriterHardSampler(DistributedBatchSampler): self.person_id_map[person_id].append(i) else: self.person_id_map[person_id] = [i] - - assert len(self.anchor_list) < self.batch_size, "anchor should be larger than batch_size" + assert len(self.anchor_list) > self.batch_size, "anchor should be larger than batch_size" def __iter__(self): - random.shuffle(self.anchor_list) + if self.shuffle: + random.shuffle(self.anchor_list) for i in range(len(self)): batch_indices = [] for j in range(self.batch_size // 4): @@ -79,4 +80,4 @@ class WriterHardSampler(DistributedBatchSampler): yield batch_indices def __len__(self): - len(self.anchor_list) * 4 // self.batch_size + return len(self.anchor_list) * 4 // self.batch_size From 4150df912e8814d267800c155b1c828b242b828c Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Tue, 7 Sep 2021 16:14:40 +0000 Subject: [PATCH 06/81] Add LCNet codes and config --- ppcls/arch/backbone/__init__.py | 1 + ppcls/arch/backbone/legendary_models/lcnet.py | 399 ++++++++++++++++++ ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml | 129 ++++++ ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml | 129 ++++++ ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml | 129 ++++++ ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml | 129 ++++++ ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml | 129 ++++++ ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml | 129 ++++++ ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml | 129 ++++++ ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml | 130 ++++++ 10 files changed, 1433 insertions(+) create mode 100644 ppcls/arch/backbone/legendary_models/lcnet.py create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml create mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml diff --git a/ppcls/arch/backbone/__init__.py b/ppcls/arch/backbone/__init__.py index a1727613a..08d30ac32 100644 --- a/ppcls/arch/backbone/__init__.py +++ b/ppcls/arch/backbone/__init__.py @@ -21,6 +21,7 @@ from ppcls.arch.backbone.legendary_models.resnet import ResNet18, ResNet18_vd, R from ppcls.arch.backbone.legendary_models.vgg import VGG11, VGG13, VGG16, VGG19 from ppcls.arch.backbone.legendary_models.inception_v3 import InceptionV3 from ppcls.arch.backbone.legendary_models.hrnet import HRNet_W18_C, HRNet_W30_C, HRNet_W32_C, HRNet_W40_C, HRNet_W44_C, HRNet_W48_C, HRNet_W60_C, HRNet_W64_C, SE_HRNet_W64_C +from ppcls.arch.backbone.legendary_models.lcnet import LCNet_x0_25, LCNet_x0_35, LCNet_x0_5, LCNet_x0_75, LCNet_x1_0, LCNet_x1_5, LCNet_x2_0, LCNet_x2_5 from ppcls.arch.backbone.model_zoo.resnet_vc import ResNet50_vc from ppcls.arch.backbone.model_zoo.resnext import ResNeXt50_32x4d, ResNeXt50_64x4d, ResNeXt101_32x4d, ResNeXt101_64x4d, ResNeXt152_32x4d, ResNeXt152_64x4d diff --git a/ppcls/arch/backbone/legendary_models/lcnet.py b/ppcls/arch/backbone/legendary_models/lcnet.py new file mode 100644 index 000000000..3dd6621de --- /dev/null +++ b/ppcls/arch/backbone/legendary_models/lcnet.py @@ -0,0 +1,399 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import paddle +import paddle.nn as nn +from paddle import ParamAttr +from paddle.nn import AdaptiveAvgPool2D, BatchNorm, Conv2D, Dropout, Linear +from paddle.regularizer import L2Decay +from paddle.nn.initializer import KaimingNormal +from ppcls.arch.backbone.base.theseus_layer import TheseusLayer +from ppcls.utils.save_load import load_dygraph_pretrain, load_dygraph_pretrain_from_url + +MODEL_URLS = { + "LCNet_x0_25": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams", + "LCNet_x0_35": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams", + "LCNet_x0_5": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams", + "LCNet_x0_75": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams", + "LCNet_x1_0": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams", + "LCNet_x1_5": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams", + "LCNet_x2_0": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams", + "LCNet_x2_5": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams" +} + +__all__ = list(MODEL_URLS.keys()) + +# Each element(list) represents a depthwise block, which is composed of k, in_c, out_c, s, use_se. +# k: kernel_size +# in_c: input channel number in depthwise block +# out_c: output channel number in depthwise block +# s: stride in depthwise block +# use_se: whether to use SE block + +NET_CONFIG = { + "blocks2": + #k, in_c, out_c, s, use_se + [[3, 16, 32, 1, False]], + "blocks3": [[3, 32, 64, 2, False], [3, 64, 64, 1, False]], + "blocks4": [[3, 64, 128, 2, False], [3, 128, 128, 1, False]], + "blocks5": [[3, 128, 256, 2, False], [5, 256, 256, 1, False], + [5, 256, 256, 1, False], [5, 256, 256, 1, False], + [5, 256, 256, 1, False], [5, 256, 256, 1, False]], + "blocks6": [[5, 256, 512, 2, True], [5, 512, 512, 1, True]] +} + + +def make_divisible(v, divisor=8, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class ConvBNLayer(TheseusLayer): + def __init__(self, + num_channels, + filter_size, + num_filters, + stride, + num_groups=1): + super().__init__() + + self.conv = Conv2D( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=filter_size, + stride=stride, + padding=(filter_size - 1) // 2, + groups=num_groups, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + + self.bn = BatchNorm( + num_filters, + param_attr=ParamAttr(regularizer=L2Decay(0.0)), + bias_attr=ParamAttr(regularizer=L2Decay(0.0))) + self.hardswish = nn.Hardswish() + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.hardswish(x) + return x + + +class DepthwiseSeparable(TheseusLayer): + def __init__(self, + num_channels, + num_filters, + stride, + dw_size=3, + use_se=False): + super().__init__() + self.use_se = use_se + self.dw_conv = ConvBNLayer( + num_channels=num_channels, + num_filters=num_channels, + filter_size=dw_size, + stride=stride, + num_groups=num_channels) + if use_se: + self.se = SEModule(num_channels) + self.pw_conv = ConvBNLayer( + num_channels=num_channels, + filter_size=1, + num_filters=num_filters, + stride=1) + + def forward(self, x): + x = self.dw_conv(x) + if self.use_se: + x = self.se(x) + x = self.pw_conv(x) + return x + + +class SEModule(TheseusLayer): + def __init__(self, channel, reduction=4): + super().__init__() + self.avg_pool = AdaptiveAvgPool2D(1) + self.conv1 = Conv2D( + in_channels=channel, + out_channels=channel // reduction, + kernel_size=1, + stride=1, + padding=0) + self.relu = nn.ReLU() + self.conv2 = Conv2D( + in_channels=channel // reduction, + out_channels=channel, + kernel_size=1, + stride=1, + padding=0) + self.hardsigmoid = nn.Hardsigmoid() + + def forward(self, x): + identity = x + x = self.avg_pool(x) + x = self.conv1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.hardsigmoid(x) + x = paddle.multiply(x=identity, y=x) + return x + + +class LCNet(TheseusLayer): + def __init__(self, + scale=1.0, + class_num=1000, + dropout_prob=0.2, + class_expand=1280): + super().__init__() + self.scale = scale + self.class_expand = class_expand + + self.conv1 = ConvBNLayer( + num_channels=3, + filter_size=3, + num_filters=make_divisible(16 * scale), + stride=2) + + self.blocks2 = nn.Sequential(*[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks2"]) + ]) + + self.blocks3 = nn.Sequential(*[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks3"]) + ]) + + self.blocks4 = nn.Sequential(*[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks4"]) + ]) + + self.blocks5 = nn.Sequential(*[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks5"]) + ]) + + self.blocks6 = nn.Sequential(*[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks6"]) + ]) + + self.avg_pool = AdaptiveAvgPool2D(1) + + self.last_conv = Conv2D( + in_channels=make_divisible(NET_CONFIG["blocks6"][-1][2] * scale), + out_channels=self.class_expand, + kernel_size=1, + stride=1, + padding=0, + bias_attr=False) + + self.hardswish = nn.Hardswish() + self.dropout = Dropout(p=dropout_prob, mode="downscale_in_infer") + self.flatten = nn.Flatten(start_axis=1, stop_axis=-1) + + self.fc = Linear(self.class_expand, class_num) + + def forward(self, x): + x = self.conv1(x) + + x = self.blocks2(x) + x = self.blocks3(x) + x = self.blocks4(x) + x = self.blocks5(x) + x = self.blocks6(x) + + x = self.avg_pool(x) + x = self.last_conv(x) + x = self.hardswish(x) + x = self.dropout(x) + x = self.flatten(x) + x = self.fc(x) + return x + + +def _load_pretrained(pretrained, model, model_url, use_ssld): + if pretrained is False: + pass + elif pretrained is True: + load_dygraph_pretrain_from_url(model, model_url, use_ssld=use_ssld) + elif isinstance(pretrained, str): + load_dygraph_pretrain(model, pretrained) + else: + raise RuntimeError( + "pretrained type is not available. Please use `string` or `boolean` type." + ) + + +def LCNet_x0_25(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x0_25 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x0_25` model depends on args. + """ + model = LCNet(scale=0.25, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_25"], use_ssld) + return model + + +def LCNet_x0_35(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x0_35 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x0_35` model depends on args. + """ + model = LCNet(scale=0.35, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_35"], use_ssld) + return model + + +def LCNet_x0_5(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x0_5 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x0_5` model depends on args. + """ + model = LCNet(scale=0.5, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_5"], use_ssld) + return model + + +def LCNet_x0_75(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x0_75 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x0_75` model depends on args. + """ + model = LCNet(scale=0.75, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_75"], use_ssld) + return model + + +def LCNet_x1_0(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x1_0 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x1_0` model depends on args. + """ + model = LCNet(scale=1.0, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x1_0"], use_ssld) + return model + + +def LCNet_x1_5(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x1_5 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x1_5` model depends on args. + """ + model = LCNet(scale=1.5, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x1_5"], use_ssld) + return model + + +def LCNet_x2_0(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x2_0 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x2_0` model depends on args. + """ + model = LCNet(scale=2.0, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x2_0"], use_ssld) + return model + + +def LCNet_x2_5(pretrained=False, use_ssld=False, **kwargs): + """ + LCNet_x2_5 + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `LCNet_x2_5` model depends on args. + """ + model = LCNet(scale=2.5, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x2_5"], use_ssld) + return model diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml new file mode 100644 index 000000000..40d5bc626 --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x0_25 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml new file mode 100644 index 000000000..5d321e6f8 --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x0_35 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml new file mode 100644 index 000000000..42264d86b --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x0_5 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml new file mode 100644 index 000000000..93f86cc49 --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x0_75 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml new file mode 100644 index 000000000..ac4c917fb --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x1_0 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml new file mode 100644 index 000000000..d426d911e --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x1_5 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml new file mode 100644 index 000000000..afe310b4a --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x2_0 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml new file mode 100644 index 000000000..befa1acb1 --- /dev/null +++ b/ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml @@ -0,0 +1,130 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: LCNet_x2_5 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - AutoAugment: + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] From 81b833515c1db3ec7bd44771ccdaa7ce1e1426c0 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Wed, 8 Sep 2021 08:10:34 +0000 Subject: [PATCH 07/81] Add LCNet docs --- README_ch.md | 2 +- README_en.md | 2 ++ docs/en/ImageNet_models_en.md | 49 ++++++++++++++++++++++++-------- docs/en/models/LCNet_en.md | 41 ++++++++++++++++++++++++++ docs/zh_CN/ImageNet_models_cn.md | 48 ++++++++++++++++++++++++------- docs/zh_CN/models/LCNet.md | 41 ++++++++++++++++++++++++++ 6 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 docs/en/models/LCNet_en.md create mode 100644 docs/zh_CN/models/LCNet.md diff --git a/README_ch.md b/README_ch.md index 011e638c0..b168d23ce 100644 --- a/README_ch.md +++ b/README_ch.md @@ -7,7 +7,7 @@ 飞桨图像识别套件PaddleClas是飞桨为工业界和学术界所准备的一个图像识别任务的工具集,助力使用者训练出更好的视觉模型和应用落地。 **近期更新** - +- 2021.09.08 增加PaddleClas自研LCNet系列模型, 这些模型在Intel CPU上有较强的竞争力。相关指标和预训练权重可以从 [这里](docs/zh_CN/ImageNet_models.md)下载。 - 2021.08.11 更新7个[FAQ](docs/zh_CN/faq_series/faq_2021_s2.md)。 - 2021.06.29 添加Swin-transformer系列模型,ImageNet1k数据集上Top1 acc最高精度可达87.2%;支持训练预测评估与whl包部署,预训练模型可以从[这里](docs/zh_CN/models/models_intro.md)下载。 - 2021.06.22,23,24 PaddleClas官方研发团队带来技术深入解读三日直播课。课程回放:[https://aistudio.baidu.com/aistudio/course/introduce/24519](https://aistudio.baidu.com/aistudio/course/introduce/24519) diff --git a/README_en.md b/README_en.md index bc4f59cf9..07b0f3a2a 100644 --- a/README_en.md +++ b/README_en.md @@ -8,6 +8,8 @@ PaddleClas is an image recognition toolset for industry and academia, helping us **Recent updates** +- 2021.09.08 Add LCNet series model developed by PaddleClas, these models show strong competitiveness on Intel CPUs. The metrics and pretrained model can be downloaded [here](docs/en/ImageNet_models_en.md) + - 2021.06.29 Add Swin-transformer series model,Highest top1 acc on ImageNet1k dataset reaches 87.2%, training, evaluation and inference are all supported. Pretrained models can be downloaded [here](docs/en/models/models_intro_en.md). - 2021.06.16 PaddleClas release/2.2. Add metric learning and vector search modules. Add product recognition, animation character recognition, vehicle recognition and logo recognition. Added 30 pretrained models of LeViT, Twins, TNT, DLA, HarDNet, and RedNet, and the accuracy is roughly the same as that of the paper. - [more](./docs/en/update_history_en.md) diff --git a/docs/en/ImageNet_models_en.md b/docs/en/ImageNet_models_en.md index 743337e46..b05c7789b 100644 --- a/docs/en/ImageNet_models_en.md +++ b/docs/en/ImageNet_models_en.md @@ -26,11 +26,11 @@ Accuracy and inference time of the prtrained models based on SSLD distillation a | Model | Top-1 Acc | Reference
Top-1 Acc | Acc gain | time(ms)
bs=1 | time(ms)
bs=4 | Flops(G) | Params(M) | Download Address | |---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| | ResNet34_vd_ssld | 0.797 | 0.760 | 0.037 | 2.434 | 6.222 | 7.39 | 21.82 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet34_vd_ssld_pretrained.pdparams) | -| ResNet50_vd_
ssld | 0.830 | 0.792 | 0.039 | 3.531 | 8.090 | 8.67 | 25.58 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet50_vd_ssld_pretrained.pdparams) | -| ResNet101_vd_
ssld | 0.837 | 0.802 | 0.035 | 6.117 | 13.762 | 16.1 | 44.57 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet101_vd_ssld_pretrained.pdparams) | -| Res2Net50_vd_
26w_4s_ssld | 0.831 | 0.798 | 0.033 | 4.527 | 9.657 | 8.37 | 25.06 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net50_vd_26w_4s_ssld_pretrained.pdparams) | -| Res2Net101_vd_
26w_4s_ssld | 0.839 | 0.806 | 0.033 | 8.087 | 17.312 | 16.67 | 45.22 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net101_vd_26w_4s_ssld_pretrained.pdparams) | -| Res2Net200_vd_
26w_4s_ssld | 0.851 | 0.812 | 0.049 | 14.678 | 32.350 | 31.49 | 76.21 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net200_vd_26w_4s_ssld_pretrained.pdparams) | +| ResNet50_vd_ssld | 0.830 | 0.792 | 0.039 | 3.531 | 8.090 | 8.67 | 25.58 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet50_vd_ssld_pretrained.pdparams) | +| ResNet101_vd_ssld | 0.837 | 0.802 | 0.035 | 6.117 | 13.762 | 16.1 | 44.57 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet101_vd_ssld_pretrained.pdparams) | +| Res2Net50_vd_26w_4s_ssld | 0.831 | 0.798 | 0.033 | 4.527 | 9.657 | 8.37 | 25.06 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net50_vd_26w_4s_ssld_pretrained.pdparams) | +| Res2Net101_vd_26w_4s_ssld | 0.839 | 0.806 | 0.033 | 8.087 | 17.312 | 16.67 | 45.22 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net101_vd_26w_4s_ssld_pretrained.pdparams) | +| Res2Net200_vd_26w_4s_ssld | 0.851 | 0.812 | 0.049 | 14.678 | 32.350 | 31.49 | 76.21 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net200_vd_26w_4s_ssld_pretrained.pdparams) | | HRNet_W18_C_ssld | 0.812 | 0.769 | 0.043 | 7.406 | 13.297 | 4.14 | 21.29 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/HRNet_W18_C_ssld_pretrained.pdparams) | | HRNet_W48_C_ssld | 0.836 | 0.790 | 0.046 | 13.707 | 34.435 | 34.58 | 77.47 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/HRNet_W48_C_ssld_pretrained.pdparams) | | SE_HRNet_W64_C_ssld | 0.848 | - | - | 31.697 | 94.995 | 57.83 | 128.97 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SE_HRNet_W64_C_ssld_pretrained.pdparams) | @@ -38,19 +38,44 @@ Accuracy and inference time of the prtrained models based on SSLD distillation a * Mobile-side distillation pretrained models -| Model | Top-1 Acc | Reference
Top-1 Acc | Acc gain | SD855 time(ms)
bs=1 | Flops(G) | Params(M) | 模型大小(M) | Download Address | +| Model | Top-1 Acc | Reference
Top-1 Acc | Acc gain | SD855 time(ms)
bs=1 | Flops(G) | Params(M) | Storage Size(M) | Download Address | |---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| -| MobileNetV1_
ssld | 0.779 | 0.710 | 0.069 | 32.523 | 1.11 | 4.19 | 16 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV1_ssld_pretrained.pdparams) | -| MobileNetV2_
ssld | 0.767 | 0.722 | 0.045 | 23.318 | 0.6 | 3.44 | 14 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_ssld_pretrained.pdparams) | -| MobileNetV3_
small_x0_35_ssld | 0.556 | 0.530 | 0.026 | 2.635 | 0.026 | 1.66 | 6.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x0_35_ssld_pretrained.pdparams) | -| MobileNetV3_
large_x1_0_ssld | 0.790 | 0.753 | 0.036 | 19.308 | 0.45 | 5.47 | 21 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x1_0_ssld_pretrained.pdparams) | -| MobileNetV3_small_
x1_0_ssld | 0.713 | 0.682 | 0.031 | 6.546 | 0.123 | 2.94 | 12 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x1_0_ssld_pretrained.pdparams) | -| GhostNet_
x1_3_ssld | 0.794 | 0.757 | 0.037 | 19.983 | 0.44 | 7.3 | 29 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_3_ssld_pretrained.pdparams) +| MobileNetV1_ssld | 0.779 | 0.710 | 0.069 | 32.523 | 1.11 | 4.19 | 16 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV1_ssld_pretrained.pdparams) | +| MobileNetV2_ssld | 0.767 | 0.722 | 0.045 | 23.318 | 0.6 | 3.44 | 14 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_ssld_pretrained.pdparams) | +| MobileNetV3_small_x0_35_ssld | 0.556 | 0.530 | 0.026 | 2.635 | 0.026 | 1.66 | 6.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x0_35_ssld_pretrained.pdparams) | +| MobileNetV3_large_x1_0_ssld | 0.790 | 0.753 | 0.036 | 19.308 | 0.45 | 5.47 | 21 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x1_0_ssld_pretrained.pdparams) | +| MobileNetV3_small_x1_0_ssld | 0.713 | 0.682 | 0.031 | 6.546 | 0.123 | 2.94 | 12 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x1_0_ssld_pretrained.pdparams) | +| GhostNet_x1_3_ssld | 0.794 | 0.757 | 0.037 | 19.983 | 0.44 | 7.3 | 29 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_3_ssld_pretrained.pdparams) + +* Intel-CPU-side distillation pretrained models + +| Model | Top-1 Acc | Reference
Top-1 Acc | Acc gain | Intel-Xeon-Gold-6148 time(ms)
bs=1 | Flops(M) | Params(M) | Download Address | +|---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| +| LCNet_x0_5_ssld | 0.661 | 0.631 | 0.030 | 2.05 | 47 | 1.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_ssld_pretrained.pdparams) | +| LCNet_x1_0_ssld | 0.744 | 0.713 | 0.033 | 2.46 | 161 | 3.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_ssld_pretrained.pdparams) | +| LCNet_x2_5_ssld | 0.808 | 0.766 | 0.042 | 5.39 | 906 | 9.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_ssld_pretrained.pdparams) | * Note: `Reference Top-1 Acc` means accuracy of pretrained models which are trained on ImageNet1k dataset. + +### LCNet_series + +Accuracy and inference time metrics of LCNet series models are shown as follows. More detailed information can be refered to [LCNet series tutorial](../en/models/LCNet_en.md). + +| Model | Top-1 Acc | Top-5 Acc | Intel-Xeon-Gold-6148 time(ms)
bs=1 | FLOPs(M) | Params(M) | Download Address | +|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +| LCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams) | +| LCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams) | +| LCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams) | +| LCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams) | +| LCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams) | +| LCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams) | +| LCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams) | +| LCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams) | + + ### ResNet and Vd series diff --git a/docs/en/models/LCNet_en.md b/docs/en/models/LCNet_en.md new file mode 100644 index 000000000..35b18343b --- /dev/null +++ b/docs/en/models/LCNet_en.md @@ -0,0 +1,41 @@ +# LCNet series + +## Overview + +The LCNet series is a network that has excellent performance on Intel-CPU proposed by the Baidu PaddleCV team. The author summarizes some methods that can improve the accuracy of the model on Intel-CPU but hardly increase the inference time. The author combines these methods into a new network, namely LCNet. Compared with other lightweight networks, LCNet can achieve higher accuracy with the same inference time. LCNet has shown strong competitiveness in image classification, object detection, and semantic segmentation. + + + +## Accuracy, FLOPS and Parameters + +| Models | Top1 | Top5 | FLOPs
(M) | Parameters
(M) | +|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +| LCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | +| LCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | +| LCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | +| LCNet_x0_75 |0.6818 | 0.8830 | 99 | 2.4 | +| LCNet_x1_0 |0.7132 | 0.9003 | 161 | 3.0 | +| LCNet_x1_5 |0.7371 | 0.9153 | 342 | 4.5 | +| LCNet_x2_0 |0.7518 | 0.9227 | 590 | 6.5 | +| LCNet_x2_5 |0.7660 | 0.9300 | 906 | 9.0 | +| LCNet_x0_5_ssld |0.6610 | 0.8646 | 47 | 1.9 | +| LCNet_x1_0_ssld |0.7439 | 0.9209 | 161 | 3.0 | +| LCNet_x2_5_ssld |0.8082 | 0.9533 | 906 | 9.0 | + + + +## Inference speed based on Intel(R)-Xeon(R)-Gold-6148-CPU + +| Models | Crop Size | Resize Short Size | FP32
Batch Size=1
(ms) | +|------------------|-----------|-------------------|--------------------------| +| LCNet_x0_25 | 224 | 256 | 1.74 | +| LCNet_x0_35 | 224 | 256 | 1.92 | +| LCNet_x0_5 | 224 | 256 | 2.05 | +| LCNet_x0_75 | 224 | 256 | 2.29 | +| LCNet_x1_0 | 224 | 256 | 2.46 | +| LCNet_x1_5 | 224 | 256 | 3.19 | +| LCNet_x2_0 | 224 | 256 | 4.27 | +| LCNet_x2_5 | 224 | 256 | 5.39 | +| LCNet_x0_5_ssld | 224 | 256 | 2.05 | +| LCNet_x1_0_ssld | 224 | 256 | 2.46 | +| LCNet_x2_5_ssld | 224 | 256 | 5.39 | diff --git a/docs/zh_CN/ImageNet_models_cn.md b/docs/zh_CN/ImageNet_models_cn.md index 373295992..35db512c6 100644 --- a/docs/zh_CN/ImageNet_models_cn.md +++ b/docs/zh_CN/ImageNet_models_cn.md @@ -31,9 +31,9 @@ | 模型 | Top-1 Acc | Reference
Top-1 Acc | Acc gain | time(ms)
bs=1 | time(ms)
bs=4 | Flops(G) | Params(M) | 下载地址 | |---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| | ResNet34_vd_ssld | 0.797 | 0.760 | 0.037 | 2.434 | 6.222 | 7.39 | 21.82 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet34_vd_ssld_pretrained.pdparams) | -| ResNet50_vd_
ssld | 0.830 | 0.792 | 0.039 | 3.531 | 8.090 | 8.67 | 25.58 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet50_vd_ssld_pretrained.pdparams) | -| ResNet101_vd_
ssld | 0.837 | 0.802 | 0.035 | 6.117 | 13.762 | 16.1 | 44.57 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet101_vd_ssld_pretrained.pdparams) | -| Res2Net50_vd_
26w_4s_ssld | 0.831 | 0.798 | 0.033 | 4.527 | 9.657 | 8.37 | 25.06 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net50_vd_26w_4s_ssld_pretrained.pdparams) | +| ResNet50_vd_ssld | 0.830 | 0.792 | 0.039 | 3.531 | 8.090 | 8.67 | 25.58 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet50_vd_ssld_pretrained.pdparams) | +| ResNet101_vd_ssld | 0.837 | 0.802 | 0.035 | 6.117 | 13.762 | 16.1 | 44.57 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet101_vd_ssld_pretrained.pdparams) | +| Res2Net50_vd_26w_4s_ssld | 0.831 | 0.798 | 0.033 | 4.527 | 9.657 | 8.37 | 25.06 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net50_vd_26w_4s_ssld_pretrained.pdparams) | | Res2Net101_vd_
26w_4s_ssld | 0.839 | 0.806 | 0.033 | 8.087 | 17.312 | 16.67 | 45.22 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net101_vd_26w_4s_ssld_pretrained.pdparams) | | Res2Net200_vd_
26w_4s_ssld | 0.851 | 0.812 | 0.049 | 14.678 | 32.350 | 31.49 | 76.21 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/Res2Net200_vd_26w_4s_ssld_pretrained.pdparams) | | HRNet_W18_C_ssld | 0.812 | 0.769 | 0.043 | 7.406 | 13.297 | 4.14 | 21.29 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/HRNet_W18_C_ssld_pretrained.pdparams) | @@ -45,16 +45,44 @@ | 模型 | Top-1 Acc | Reference
Top-1 Acc | Acc gain | SD855 time(ms)
bs=1 | Flops(G) | Params(M) | 模型大小(M) | 下载地址 | |---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| -| MobileNetV1_
ssld | 0.779 | 0.710 | 0.069 | 32.523 | 1.11 | 4.19 | 16 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV1_ssld_pretrained.pdparams) | -| MobileNetV2_
ssld | 0.767 | 0.722 | 0.045 | 23.318 | 0.6 | 3.44 | 14 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_ssld_pretrained.pdparams) | -| MobileNetV3_
small_x0_35_ssld | 0.556 | 0.530 | 0.026 | 2.635 | 0.026 | 1.66 | 6.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x0_35_ssld_pretrained.pdparams) | -| MobileNetV3_
large_x1_0_ssld | 0.790 | 0.753 | 0.036 | 19.308 | 0.45 | 5.47 | 21 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x1_0_ssld_pretrained.pdparams) | -| MobileNetV3_small_
x1_0_ssld | 0.713 | 0.682 | 0.031 | 6.546 | 0.123 | 2.94 | 12 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x1_0_ssld_pretrained.pdparams) | -| GhostNet_
x1_3_ssld | 0.794 | 0.757 | 0.037 | 19.983 | 0.44 | 7.3 | 29 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_3_ssld_pretrained.pdparams) | +| MobileNetV1_ssld | 0.779 | 0.710 | 0.069 | 32.523 | 1.11 | 4.19 | 16 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV1_ssld_pretrained.pdparams) | +| MobileNetV2_ssld | 0.767 | 0.722 | 0.045 | 23.318 | 0.6 | 3.44 | 14 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_ssld_pretrained.pdparams) | +| MobileNetV3_small_x0_35_ssld | 0.556 | 0.530 | 0.026 | 2.635 | 0.026 | 1.66 | 6.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x0_35_ssld_pretrained.pdparams) | +| MobileNetV3_large_x1_0_ssld | 0.790 | 0.753 | 0.036 | 19.308 | 0.45 | 5.47 | 21 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x1_0_ssld_pretrained.pdparams) | +| MobileNetV3_small_x1_0_ssld | 0.713 | 0.682 | 0.031 | 6.546 | 0.123 | 2.94 | 12 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x1_0_ssld_pretrained.pdparams) | +| GhostNet_x1_3_ssld | 0.794 | 0.757 | 0.037 | 19.983 | 0.44 | 7.3 | 29 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_3_ssld_pretrained.pdparams) | + + +* Intel CPU端知识蒸馏模型 + +| 模型 | Top-1 Acc | Reference
Top-1 Acc | Acc gain | Intel-Xeon-Gold-6148 time(ms)
bs=1 | Flops(M) | Params(M) | 下载地址 | +|---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| +| LCNet_x0_5_ssld | 0.661 | 0.631 | 0.030 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_ssld_pretrained.pdparams) | +| LCNet_x1_0_ssld | 0.744 | 0.713 | 0.033 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_ssld_pretrained.pdparams) | +| LCNet_x2_5_ssld | 0.808 | 0.766 | 0.042 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_ssld_pretrained.pdparams) | + + * 注: `Reference Top-1 Acc`表示PaddleClas基于ImageNet1k数据集训练得到的预训练模型精度。 + +### LCNet系列 + +LCNet系列模型的精度、速度指标如下表所示,更多关于该系列的模型介绍可以参考:[LCNet系列模型文档](./models/LCNet.md)。 + +| 模型 | Top-1 Acc | Top-5 Acc | Intel-Xeon-Gold-6148 time(ms)
bs=1 | FLOPs(M) | Params(M) | 下载地址 | +|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +| LCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams) | +| LCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams) | +| LCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams) | +| LCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams) | +| LCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams) | +| LCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams) | +| LCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams) | +| LCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams) | + + ### ResNet及其Vd系列 @@ -429,7 +457,7 @@ ViT(Vision Transformer)与DeiT(Data-efficient Image Transformers)系列 | 模型 | Top-1 Acc | Top-5 Acc | time(ms)
bs=1 | time(ms)
bs=4 | Flops(G) | Params(M) | 下载地址 | | ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | -| TNT_small | 0.8121 |0.9563 | | | 5.2 | 23.8 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/TNT_small_pretrained.pdparams) | | +| TNT_small | 0.8121 |0.9563 | | | 5.2 | 23.8 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/TNT_small_pretrained.pdparams) | | **注**:TNT模型的数据预处理部分`NormalizeImage`中的`mean`与`std`均为0.5。 diff --git a/docs/zh_CN/models/LCNet.md b/docs/zh_CN/models/LCNet.md new file mode 100644 index 000000000..a7ccdcbbd --- /dev/null +++ b/docs/zh_CN/models/LCNet.md @@ -0,0 +1,41 @@ +# LCNet系列 + +## 概述 + +LCNet系列是百度PaddleCV团队提出的一种在Intel-CPU上表现优异的网络,作者总结了一些在Intel-CPU上可以提升模型精度但几乎不增加推理耗时的方法,将这些方法组合成了一个新的网络,即LCNet。与其他轻量级网络相比,LCNet可以在相同延时下取得更高的精度。LCNet已在图像分类、目标检测、语义分割上表现出了强大的竞争力。 + + + +## 精度、FLOPS和参数量 + +| Models | Top1 | Top5 | FLOPs
(M) | Parameters
(M) | +|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +| LCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | +| LCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | +| LCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | +| LCNet_x0_75 |0.6818 | 0.8830 | 99 | 2.4 | +| LCNet_x1_0 |0.7132 | 0.9003 | 161 | 3.0 | +| LCNet_x1_5 |0.7371 | 0.9153 | 342 | 4.5 | +| LCNet_x2_0 |0.7518 | 0.9227 | 590 | 6.5 | +| LCNet_x2_5 |0.7660 | 0.9300 | 906 | 9.0 | +| LCNet_x0_5_ssld |0.6610 | 0.8646 | 47 | 1.9 | +| LCNet_x1_0_ssld |0.7439 | 0.9209 | 161 | 3.0 | +| LCNet_x2_5_ssld |0.8082 | 0.9533 | 906 | 9.0 | + + + +## 基于Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz的预测速度 + +| Models | Crop Size | Resize Short Size | FP32
Batch Size=1
(ms) | +|------------------|-----------|-------------------|--------------------------| +| LCNet_x0_25 | 224 | 256 | 1.74 | +| LCNet_x0_35 | 224 | 256 | 1.92 | +| LCNet_x0_5 | 224 | 256 | 2.05 | +| LCNet_x0_75 | 224 | 256 | 2.29 | +| LCNet_x1_0 | 224 | 256 | 2.46 | +| LCNet_x1_5 | 224 | 256 | 3.19 | +| LCNet_x2_0 | 224 | 256 | 4.27 | +| LCNet_x2_5 | 224 | 256 | 5.39 | +| LCNet_x0_5_ssld | 224 | 256 | 2.05 | +| LCNet_x1_0_ssld | 224 | 256 | 2.46 | +| LCNet_x2_5_ssld | 224 | 256 | 5.39 | From 80957fe2b7dcd111b0282a44fe90613947f46ec8 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Wed, 8 Sep 2021 08:15:37 +0000 Subject: [PATCH 08/81] Update LCNet docs --- docs/en/models/LCNet_en.md | 2 +- docs/zh_CN/models/LCNet.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/models/LCNet_en.md b/docs/en/models/LCNet_en.md index 35b18343b..3fb2562fe 100644 --- a/docs/en/models/LCNet_en.md +++ b/docs/en/models/LCNet_en.md @@ -9,7 +9,7 @@ The LCNet series is a network that has excellent performance on Intel-CPU propos ## Accuracy, FLOPS and Parameters | Models | Top1 | Top5 | FLOPs
(M) | Parameters
(M) | -|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +|:--:|:--:|:--:|:--:|:--:| | LCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | | LCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | | LCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | diff --git a/docs/zh_CN/models/LCNet.md b/docs/zh_CN/models/LCNet.md index a7ccdcbbd..32926f927 100644 --- a/docs/zh_CN/models/LCNet.md +++ b/docs/zh_CN/models/LCNet.md @@ -9,7 +9,7 @@ LCNet系列是百度PaddleCV团队提出的一种在Intel-CPU上表现优异的 ## 精度、FLOPS和参数量 | Models | Top1 | Top5 | FLOPs
(M) | Parameters
(M) | -|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +|:--:|:--:|:--:|:--:|:--:| | LCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | | LCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | | LCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | From d2adc3cda17b035aeace4d1bd7d23750ec69d6e4 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Wed, 8 Sep 2021 08:18:54 +0000 Subject: [PATCH 09/81] Update LCNet docs --- docs/en/ImageNet_models_en.md | 2 +- docs/zh_CN/ImageNet_models_cn.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/ImageNet_models_en.md b/docs/en/ImageNet_models_en.md index b05c7789b..64476566e 100644 --- a/docs/en/ImageNet_models_en.md +++ b/docs/en/ImageNet_models_en.md @@ -24,7 +24,7 @@ Accuracy and inference time of the prtrained models based on SSLD distillation a * Server-side distillation pretrained models | Model | Top-1 Acc | Reference
Top-1 Acc | Acc gain | time(ms)
bs=1 | time(ms)
bs=4 | Flops(G) | Params(M) | Download Address | -|---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| +|---------------------|-----------|-----------|---------------|----------------|----------|-----------|-----------------------------------| | ResNet34_vd_ssld | 0.797 | 0.760 | 0.037 | 2.434 | 6.222 | 7.39 | 21.82 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet34_vd_ssld_pretrained.pdparams) | | ResNet50_vd_ssld | 0.830 | 0.792 | 0.039 | 3.531 | 8.090 | 8.67 | 25.58 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet50_vd_ssld_pretrained.pdparams) | | ResNet101_vd_ssld | 0.837 | 0.802 | 0.035 | 6.117 | 13.762 | 16.1 | 44.57 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/ResNet101_vd_ssld_pretrained.pdparams) | diff --git a/docs/zh_CN/ImageNet_models_cn.md b/docs/zh_CN/ImageNet_models_cn.md index 35db512c6..6a6bdeca7 100644 --- a/docs/zh_CN/ImageNet_models_cn.md +++ b/docs/zh_CN/ImageNet_models_cn.md @@ -56,7 +56,7 @@ * Intel CPU端知识蒸馏模型 | 模型 | Top-1 Acc | Reference
Top-1 Acc | Acc gain | Intel-Xeon-Gold-6148 time(ms)
bs=1 | Flops(M) | Params(M) | 下载地址 | -|---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| +|---------------------|-----------|-----------|---------------|----------------|----------|-----------|-----------------------------------| | LCNet_x0_5_ssld | 0.661 | 0.631 | 0.030 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_ssld_pretrained.pdparams) | | LCNet_x1_0_ssld | 0.744 | 0.713 | 0.033 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_ssld_pretrained.pdparams) | | LCNet_x2_5_ssld | 0.808 | 0.766 | 0.042 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_ssld_pretrained.pdparams) | From b578662b32cf86ca563fb9717f1252f5ec767eaf Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Mon, 26 Jul 2021 07:36:54 +0000 Subject: [PATCH 10/81] perf: add OpSampler to support multiple ops --- ppcls/data/preprocess/__init__.py | 4 +- .../preprocess/batch_ops/batch_operators.py | 106 ++++++++++++------ 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/ppcls/data/preprocess/__init__.py b/ppcls/data/preprocess/__init__.py index ebd0df420..12bb34b2d 100644 --- a/ppcls/data/preprocess/__init__.py +++ b/ppcls/data/preprocess/__init__.py @@ -29,7 +29,7 @@ from ppcls.data.preprocess.ops.operators import NormalizeImage from ppcls.data.preprocess.ops.operators import ToCHWImage from ppcls.data.preprocess.ops.operators import AugMix -from ppcls.data.preprocess.batch_ops.batch_operators import MixupOperator, CutmixOperator, FmixOperator +from ppcls.data.preprocess.batch_ops.batch_operators import MixupOperator, CutmixOperator, OpSampler, FmixOperator import six import numpy as np @@ -45,6 +45,7 @@ def transform(data, ops=[]): class AutoAugment(RawImageNetPolicy): """ ImageNetPolicy wrapper to auto fit different img types """ + def __init__(self, *args, **kwargs): if six.PY2: super(AutoAugment, self).__init__(*args, **kwargs) @@ -69,6 +70,7 @@ class AutoAugment(RawImageNetPolicy): class RandAugment(RawRandAugment): """ RandAugment wrapper to auto fit different img types """ + def __init__(self, *args, **kwargs): if six.PY2: super(RandAugment, self).__init__(*args, **kwargs) diff --git a/ppcls/data/preprocess/batch_ops/batch_operators.py b/ppcls/data/preprocess/batch_ops/batch_operators.py index 55c1b5a44..1f3bd3253 100644 --- a/ppcls/data/preprocess/batch_ops/batch_operators.py +++ b/ppcls/data/preprocess/batch_ops/batch_operators.py @@ -16,8 +16,11 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import random + import numpy as np +from ppcls.utils import logger from ppcls.data.preprocess.ops.fmix import sample_mask @@ -46,37 +49,50 @@ class BatchOperator(object): class MixupOperator(BatchOperator): - """Mixup and Cutmix operator""" + """ Mixup operator """ - def __init__(self, - mixup_alpha: float=1., - cutmix_alpha: float=0., - switch_prob: float=0.5): + def __init__(self, alpha: float=1.): """Build Mixup operator Args: - mixup_alpha (float, optional): The parameter alpha of mixup, mixup is active if > 0. Defaults to 1.. - cutmix_alpha (float, optional): The parameter alpha of cutmix, cutmix is active if > 0. Defaults to 0.. - switch_prob (float, optional): The probability of switching to cutmix instead of mixup when both are active. Defaults to 0.5. + alpha (float, optional): The parameter alpha of mixup. Defaults to 1.. Raises: - Exception: The value of parameters are illegal. + Exception: The value of parameter is illegal. """ - if mixup_alpha <= 0 and cutmix_alpha <= 0: + if alpha <= 0: raise Exception( - f"At least one of parameter alpha of Mixup and Cutmix is greater than 0. mixup_alpha: {mixup_alpha}, cutmix_alpha: {cutmix_alpha}" + f"Parameter \"alpha\" of Mixup should be greater than 0. \"alpha\": {alpha}." ) - self._mixup_alpha = mixup_alpha - self._cutmix_alpha = cutmix_alpha - self._switch_prob = switch_prob + self._alpha = alpha - def _mixup(self, imgs, labels, bs): + def __call__(self, batch): + imgs, labels, bs = self._unpack(batch) idx = np.random.permutation(bs) - lam = np.random.beta(self._mixup_alpha, self._mixup_alpha) + lam = np.random.beta(self._alpha, self._alpha) lams = np.array([lam] * bs, dtype=np.float32) imgs = lam * imgs + (1 - lam) * imgs[idx] return list(zip(imgs, labels, labels[idx], lams)) + +class CutmixOperator(BatchOperator): + """ Cutmix operator """ + + def __init__(self, alpha=0.2): + """Build Cutmix operator + + Args: + alpha (float, optional): The parameter alpha of cutmix. Defaults to 0.2. + + Raises: + Exception: The value of parameter is illegal. + """ + if alpha <= 0: + raise Exception( + f"Parameter \"alpha\" of Cutmix should be greater than 0. \"alpha\": {alpha}." + ) + self._alpha = alpha + def _rand_bbox(self, size, lam): """ _rand_bbox """ w = size[2] @@ -96,9 +112,10 @@ class MixupOperator(BatchOperator): return bbx1, bby1, bbx2, bby2 - def _cutmix(self, imgs, labels, bs): + def __call__(self, batch): + imgs, labels, bs = self._unpack(batch) idx = np.random.permutation(bs) - lam = np.random.beta(self._cutmix_alpha, self._cutmix_alpha) + lam = np.random.beta(self._alpha, self._alpha) bbx1, bby1, bbx2, bby2 = self._rand_bbox(imgs.shape, lam) imgs[:, :, bbx1:bbx2, bby1:bby2] = imgs[idx, :, bbx1:bbx2, bby1:bby2] @@ -107,20 +124,6 @@ class MixupOperator(BatchOperator): lams = np.array([lam] * bs, dtype=np.float32) return list(zip(imgs, labels, labels[idx], lams)) - def __call__(self, batch): - imgs, labels, bs = self._unpack(batch) - if np.random.rand() < self._switch_prob: - return self._cutmix(imgs, labels, bs) - else: - return self._mixup(imgs, labels, bs) - - -class CutmixOperator(BatchOperator): - def __init__(self, **kwargs): - raise Exception( - f"\"CutmixOperator\" has been deprecated. Please use MixupOperator with \"cutmix_alpha\" and \"switch_prob\" to enable Cutmix. Refor to doc for details." - ) - class FmixOperator(BatchOperator): """ Fmix operator """ @@ -139,3 +142,42 @@ class FmixOperator(BatchOperator): size, self._max_soft, self._reformulate) imgs = mask * imgs + (1 - mask) * imgs[idx] return list(zip(imgs, labels, labels[idx], [lam] * bs)) + + +class OpSampler(object): + """ Sample a operator from """ + + def __init__(self, **op_dict): + """Build OpSampler + + Raises: + Exception: The parameter \"prob\" of operator(s) are be set error. + """ + if len(op_dict) < 1: + msg = f"ConfigWarning: No operator in \"OpSampler\". \"OpSampler\" has been skipped." + + self.ops = {} + total_prob = 0 + for op_name in op_dict: + param = op_dict[op_name] + if "prob" not in param: + msg = f"ConfigWarning: Parameter \"prob\" should be set when use operator in \"OpSampler\". The operator \"{op_name}\"'s prob has been set \"0\"." + logger.warning(msg) + prob = param.pop("prob", 0) + total_prob += prob + op = eval(op_name)(**param) + self.ops.update({op: prob}) + + if total_prob > 1: + msg = f"ConfigError: The total prob of operators in \"OpSampler\" should be less 1." + logger.error(msg) + raise Exception(msg) + + # add "None Op" when total_prob < 1, "None Op" do nothing + self.ops[None] = 1 - total_prob + + def __call__(self, batch): + op = random.choices( + list(self.ops.keys()), weights=list(self.ops.values()), k=1)[0] + # return batch directly when None Op + return op(batch) if op else batch From 2c609edd17922731e9558f9e6cfe80eefcba0e31 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Wed, 8 Sep 2021 15:13:05 +0000 Subject: [PATCH 11/81] Update LCNet docs --- README_en.md | 2 +- docs/en/ImageNet_models_en.md | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README_en.md b/README_en.md index 07b0f3a2a..a36dc6fca 100644 --- a/README_en.md +++ b/README_en.md @@ -8,7 +8,7 @@ PaddleClas is an image recognition toolset for industry and academia, helping us **Recent updates** -- 2021.09.08 Add LCNet series model developed by PaddleClas, these models show strong competitiveness on Intel CPUs. The metrics and pretrained model can be downloaded [here](docs/en/ImageNet_models_en.md) +- 2021.09.08 Add LCNet series model developed by PaddleClas, these models show strong competitiveness on Intel CPUs. The metrics and pretrained model are available [here](docs/en/ImageNet_models_en.md). - 2021.06.29 Add Swin-transformer series model,Highest top1 acc on ImageNet1k dataset reaches 87.2%, training, evaluation and inference are all supported. Pretrained models can be downloaded [here](docs/en/models/models_intro_en.md). - 2021.06.16 PaddleClas release/2.2. Add metric learning and vector search modules. Add product recognition, animation character recognition, vehicle recognition and logo recognition. Added 30 pretrained models of LeViT, Twins, TNT, DLA, HarDNet, and RedNet, and the accuracy is roughly the same as that of the paper. diff --git a/docs/en/ImageNet_models_en.md b/docs/en/ImageNet_models_en.md index 64476566e..8f3b44278 100644 --- a/docs/en/ImageNet_models_en.md +++ b/docs/en/ImageNet_models_en.md @@ -66,14 +66,14 @@ Accuracy and inference time metrics of LCNet series models are shown as follows. | Model | Top-1 Acc | Top-5 Acc | Intel-Xeon-Gold-6148 time(ms)
bs=1 | FLOPs(M) | Params(M) | Download Address | |:--:|:--:|:--:|:--:|:--:|:--:|:--:| -| LCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams) | -| LCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams) | -| LCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams) | -| LCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams) | -| LCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams) | -| LCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams) | -| LCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams) | -| LCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams) | +| LCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams) | +| LCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams) | +| LCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams) | +| LCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams) | +| LCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams) | +| LCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams) | +| LCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams) | +| LCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams) | From d470f34c64205d072af49c0f1df03b34109f5fa3 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Mon, 13 Sep 2021 03:18:37 +0000 Subject: [PATCH 12/81] Update code and docs --- README_ch.md | 2 +- README_en.md | 2 +- docs/en/ImageNet_models_en.md | 28 ++--- docs/en/models/LCNet_en.md | 48 ++++---- docs/zh_CN/ImageNet_models_cn.md | 28 ++--- docs/zh_CN/models/LCNet.md | 48 ++++---- ppcls/arch/backbone/__init__.py | 2 +- .../{lcnet.py => pp_lcnet.py} | 114 +++++++++--------- 8 files changed, 136 insertions(+), 136 deletions(-) rename ppcls/arch/backbone/legendary_models/{lcnet.py => pp_lcnet.py} (80%) diff --git a/README_ch.md b/README_ch.md index b168d23ce..e0f613a3e 100644 --- a/README_ch.md +++ b/README_ch.md @@ -7,7 +7,7 @@ 飞桨图像识别套件PaddleClas是飞桨为工业界和学术界所准备的一个图像识别任务的工具集,助力使用者训练出更好的视觉模型和应用落地。 **近期更新** -- 2021.09.08 增加PaddleClas自研LCNet系列模型, 这些模型在Intel CPU上有较强的竞争力。相关指标和预训练权重可以从 [这里](docs/zh_CN/ImageNet_models.md)下载。 +- 2021.09.08 增加PaddleClas自研PPLCNet系列模型, 这些模型在Intel CPU上有较强的竞争力。相关指标和预训练权重可以从 [这里](docs/zh_CN/ImageNet_models.md)下载。 - 2021.08.11 更新7个[FAQ](docs/zh_CN/faq_series/faq_2021_s2.md)。 - 2021.06.29 添加Swin-transformer系列模型,ImageNet1k数据集上Top1 acc最高精度可达87.2%;支持训练预测评估与whl包部署,预训练模型可以从[这里](docs/zh_CN/models/models_intro.md)下载。 - 2021.06.22,23,24 PaddleClas官方研发团队带来技术深入解读三日直播课。课程回放:[https://aistudio.baidu.com/aistudio/course/introduce/24519](https://aistudio.baidu.com/aistudio/course/introduce/24519) diff --git a/README_en.md b/README_en.md index a36dc6fca..2436554e2 100644 --- a/README_en.md +++ b/README_en.md @@ -8,7 +8,7 @@ PaddleClas is an image recognition toolset for industry and academia, helping us **Recent updates** -- 2021.09.08 Add LCNet series model developed by PaddleClas, these models show strong competitiveness on Intel CPUs. The metrics and pretrained model are available [here](docs/en/ImageNet_models_en.md). +- 2021.09.08 Add PPLCNet series model developed by PaddleClas, these models show strong competitiveness on Intel CPUs. The metrics and pretrained model are available [here](docs/en/ImageNet_models_en.md). - 2021.06.29 Add Swin-transformer series model,Highest top1 acc on ImageNet1k dataset reaches 87.2%, training, evaluation and inference are all supported. Pretrained models can be downloaded [here](docs/en/models/models_intro_en.md). - 2021.06.16 PaddleClas release/2.2. Add metric learning and vector search modules. Add product recognition, animation character recognition, vehicle recognition and logo recognition. Added 30 pretrained models of LeViT, Twins, TNT, DLA, HarDNet, and RedNet, and the accuracy is roughly the same as that of the paper. diff --git a/docs/en/ImageNet_models_en.md b/docs/en/ImageNet_models_en.md index 8f3b44278..740fcd382 100644 --- a/docs/en/ImageNet_models_en.md +++ b/docs/en/ImageNet_models_en.md @@ -51,29 +51,29 @@ Accuracy and inference time of the prtrained models based on SSLD distillation a | Model | Top-1 Acc | Reference
Top-1 Acc | Acc gain | Intel-Xeon-Gold-6148 time(ms)
bs=1 | Flops(M) | Params(M) | Download Address | |---------------------|-----------|-----------|---------------|----------------|-----------|----------|-----------|-----------------------------------| -| LCNet_x0_5_ssld | 0.661 | 0.631 | 0.030 | 2.05 | 47 | 1.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_ssld_pretrained.pdparams) | -| LCNet_x1_0_ssld | 0.744 | 0.713 | 0.033 | 2.46 | 161 | 3.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_ssld_pretrained.pdparams) | -| LCNet_x2_5_ssld | 0.808 | 0.766 | 0.042 | 5.39 | 906 | 9.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_ssld_pretrained.pdparams) | +| PPLCNet_x0_5_ssld | 0.661 | 0.631 | 0.030 | 2.05 | 47 | 1.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_5_ssld_pretrained.pdparams) | +| PPLCNet_x1_0_ssld | 0.744 | 0.713 | 0.033 | 2.46 | 161 | 3.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_0_ssld_pretrained.pdparams) | +| PPLCNet_x2_5_ssld | 0.808 | 0.766 | 0.042 | 5.39 | 906 | 9.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_5_ssld_pretrained.pdparams) | * Note: `Reference Top-1 Acc` means accuracy of pretrained models which are trained on ImageNet1k dataset. - -### LCNet_series + +### PPLCNet_series -Accuracy and inference time metrics of LCNet series models are shown as follows. More detailed information can be refered to [LCNet series tutorial](../en/models/LCNet_en.md). +Accuracy and inference time metrics of PPLCNet series models are shown as follows. More detailed information can be refered to [PPLCNet series tutorial](../en/models/PPLCNet_en.md). | Model | Top-1 Acc | Top-5 Acc | Intel-Xeon-Gold-6148 time(ms)
bs=1 | FLOPs(M) | Params(M) | Download Address | |:--:|:--:|:--:|:--:|:--:|:--:|:--:| -| LCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams) | -| LCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams) | -| LCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams) | -| LCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams) | -| LCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams) | -| LCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams) | -| LCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams) | -| LCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams) | +| PPLCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_25_pretrained.pdparams) | +| PPLCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_35_pretrained.pdparams) | +| PPLCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_5_pretrained.pdparams) | +| PPLCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_75_pretrained.pdparams) | +| PPLCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_0_pretrained.pdparams) | +| PPLCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_5_pretrained.pdparams) | +| PPLCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_0_pretrained.pdparams) | +| PPLCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [Download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_5_pretrained.pdparams) | diff --git a/docs/en/models/LCNet_en.md b/docs/en/models/LCNet_en.md index 3fb2562fe..56a90c126 100644 --- a/docs/en/models/LCNet_en.md +++ b/docs/en/models/LCNet_en.md @@ -1,8 +1,8 @@ -# LCNet series +# PPLCNet series ## Overview -The LCNet series is a network that has excellent performance on Intel-CPU proposed by the Baidu PaddleCV team. The author summarizes some methods that can improve the accuracy of the model on Intel-CPU but hardly increase the inference time. The author combines these methods into a new network, namely LCNet. Compared with other lightweight networks, LCNet can achieve higher accuracy with the same inference time. LCNet has shown strong competitiveness in image classification, object detection, and semantic segmentation. +The PPLCNet series is a network that has excellent performance on Intel-CPU proposed by the Baidu PaddleCV team. The author summarizes some methods that can improve the accuracy of the model on Intel-CPU but hardly increase the inference time. The author combines these methods into a new network, namely PPLCNet. Compared with other lightweight networks, PPLCNet can achieve higher accuracy with the same inference time. PPLCNet has shown strong competitiveness in image classification, object detection, and semantic segmentation. @@ -10,17 +10,17 @@ The LCNet series is a network that has excellent performance on Intel-CPU propos | Models | Top1 | Top5 | FLOPs
(M) | Parameters
(M) | |:--:|:--:|:--:|:--:|:--:| -| LCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | -| LCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | -| LCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | -| LCNet_x0_75 |0.6818 | 0.8830 | 99 | 2.4 | -| LCNet_x1_0 |0.7132 | 0.9003 | 161 | 3.0 | -| LCNet_x1_5 |0.7371 | 0.9153 | 342 | 4.5 | -| LCNet_x2_0 |0.7518 | 0.9227 | 590 | 6.5 | -| LCNet_x2_5 |0.7660 | 0.9300 | 906 | 9.0 | -| LCNet_x0_5_ssld |0.6610 | 0.8646 | 47 | 1.9 | -| LCNet_x1_0_ssld |0.7439 | 0.9209 | 161 | 3.0 | -| LCNet_x2_5_ssld |0.8082 | 0.9533 | 906 | 9.0 | +| PPLCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | +| PPLCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | +| PPLCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | +| PPLCNet_x0_75 |0.6818 | 0.8830 | 99 | 2.4 | +| PPLCNet_x1_0 |0.7132 | 0.9003 | 161 | 3.0 | +| PPLCNet_x1_5 |0.7371 | 0.9153 | 342 | 4.5 | +| PPLCNet_x2_0 |0.7518 | 0.9227 | 590 | 6.5 | +| PPLCNet_x2_5 |0.7660 | 0.9300 | 906 | 9.0 | +| PPLCNet_x0_5_ssld |0.6610 | 0.8646 | 47 | 1.9 | +| PPLCNet_x1_0_ssld |0.7439 | 0.9209 | 161 | 3.0 | +| PPLCNet_x2_5_ssld |0.8082 | 0.9533 | 906 | 9.0 | @@ -28,14 +28,14 @@ The LCNet series is a network that has excellent performance on Intel-CPU propos | Models | Crop Size | Resize Short Size | FP32
Batch Size=1
(ms) | |------------------|-----------|-------------------|--------------------------| -| LCNet_x0_25 | 224 | 256 | 1.74 | -| LCNet_x0_35 | 224 | 256 | 1.92 | -| LCNet_x0_5 | 224 | 256 | 2.05 | -| LCNet_x0_75 | 224 | 256 | 2.29 | -| LCNet_x1_0 | 224 | 256 | 2.46 | -| LCNet_x1_5 | 224 | 256 | 3.19 | -| LCNet_x2_0 | 224 | 256 | 4.27 | -| LCNet_x2_5 | 224 | 256 | 5.39 | -| LCNet_x0_5_ssld | 224 | 256 | 2.05 | -| LCNet_x1_0_ssld | 224 | 256 | 2.46 | -| LCNet_x2_5_ssld | 224 | 256 | 5.39 | +| PPLCNet_x0_25 | 224 | 256 | 1.74 | +| PPLCNet_x0_35 | 224 | 256 | 1.92 | +| PPLCNet_x0_5 | 224 | 256 | 2.05 | +| PPLCNet_x0_75 | 224 | 256 | 2.29 | +| PPLCNet_x1_0 | 224 | 256 | 2.46 | +| PPLCNet_x1_5 | 224 | 256 | 3.19 | +| PPLCNet_x2_0 | 224 | 256 | 4.27 | +| PPLCNet_x2_5 | 224 | 256 | 5.39 | +| PPLCNet_x0_5_ssld | 224 | 256 | 2.05 | +| PPLCNet_x1_0_ssld | 224 | 256 | 2.46 | +| PPLCNet_x2_5_ssld | 224 | 256 | 5.39 | diff --git a/docs/zh_CN/ImageNet_models_cn.md b/docs/zh_CN/ImageNet_models_cn.md index 6a6bdeca7..c5b97cfa8 100644 --- a/docs/zh_CN/ImageNet_models_cn.md +++ b/docs/zh_CN/ImageNet_models_cn.md @@ -57,30 +57,30 @@ | 模型 | Top-1 Acc | Reference
Top-1 Acc | Acc gain | Intel-Xeon-Gold-6148 time(ms)
bs=1 | Flops(M) | Params(M) | 下载地址 | |---------------------|-----------|-----------|---------------|----------------|----------|-----------|-----------------------------------| -| LCNet_x0_5_ssld | 0.661 | 0.631 | 0.030 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_ssld_pretrained.pdparams) | -| LCNet_x1_0_ssld | 0.744 | 0.713 | 0.033 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_ssld_pretrained.pdparams) | -| LCNet_x2_5_ssld | 0.808 | 0.766 | 0.042 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_ssld_pretrained.pdparams) | +| PPLCNet_x0_5_ssld | 0.661 | 0.631 | 0.030 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_5_ssld_pretrained.pdparams) | +| PPLCNet_x1_0_ssld | 0.744 | 0.713 | 0.033 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_0_ssld_pretrained.pdparams) | +| PPLCNet_x2_5_ssld | 0.808 | 0.766 | 0.042 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_5_ssld_pretrained.pdparams) | * 注: `Reference Top-1 Acc`表示PaddleClas基于ImageNet1k数据集训练得到的预训练模型精度。 - -### LCNet系列 + +### PPLCNet系列 -LCNet系列模型的精度、速度指标如下表所示,更多关于该系列的模型介绍可以参考:[LCNet系列模型文档](./models/LCNet.md)。 +PPLCNet系列模型的精度、速度指标如下表所示,更多关于该系列的模型介绍可以参考:[PPLCNet系列模型文档](./models/PPLCNet.md)。 | 模型 | Top-1 Acc | Top-5 Acc | Intel-Xeon-Gold-6148 time(ms)
bs=1 | FLOPs(M) | Params(M) | 下载地址 | |:--:|:--:|:--:|:--:|:--:|:--:|:--:| -| LCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams) | -| LCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams) | -| LCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams) | -| LCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams) | -| LCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams) | -| LCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams) | -| LCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams) | -| LCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams) | +| PPLCNet_x0_25 |0.5186 | 0.7565 | 1.74 | 18 | 1.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_25_pretrained.pdparams) | +| PPLCNet_x0_35 |0.5809 | 0.8083 | 1.92 | 29 | 1.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_35_pretrained.pdparams) | +| PPLCNet_x0_5 |0.6314 | 0.8466 | 2.05 | 47 | 1.9 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_5_pretrained.pdparams) | +| PPLCNet_x0_75 |0.6818 | 0.8830 | 2.29 | 99 | 2.4 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_75_pretrained.pdparams) | +| PPLCNet_x1_0 |0.7132 | 0.9003 | 2.46 | 161 | 3.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_0_pretrained.pdparams) | +| PPLCNet_x1_5 |0.7371 | 0.9153 | 3.19 | 342 | 4.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_5_pretrained.pdparams) | +| PPLCNet_x2_0 |0.7518 | 0.9227 | 4.27 | 590 | 6.5 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_0_pretrained.pdparams) | +| PPLCNet_x2_5 |0.7660 | 0.9300 | 5.39 | 906 | 9.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_5_pretrained.pdparams) | diff --git a/docs/zh_CN/models/LCNet.md b/docs/zh_CN/models/LCNet.md index 32926f927..307708545 100644 --- a/docs/zh_CN/models/LCNet.md +++ b/docs/zh_CN/models/LCNet.md @@ -1,8 +1,8 @@ -# LCNet系列 +# PPLCNet系列 ## 概述 -LCNet系列是百度PaddleCV团队提出的一种在Intel-CPU上表现优异的网络,作者总结了一些在Intel-CPU上可以提升模型精度但几乎不增加推理耗时的方法,将这些方法组合成了一个新的网络,即LCNet。与其他轻量级网络相比,LCNet可以在相同延时下取得更高的精度。LCNet已在图像分类、目标检测、语义分割上表现出了强大的竞争力。 +PPLCNet系列是百度PaddleCV团队提出的一种在Intel-CPU上表现优异的网络,作者总结了一些在Intel-CPU上可以提升模型精度但几乎不增加推理耗时的方法,将这些方法组合成了一个新的网络,即PPLCNet。与其他轻量级网络相比,PPLCNet可以在相同延时下取得更高的精度。PPLCNet已在图像分类、目标检测、语义分割上表现出了强大的竞争力。 @@ -10,17 +10,17 @@ LCNet系列是百度PaddleCV团队提出的一种在Intel-CPU上表现优异的 | Models | Top1 | Top5 | FLOPs
(M) | Parameters
(M) | |:--:|:--:|:--:|:--:|:--:| -| LCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | -| LCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | -| LCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | -| LCNet_x0_75 |0.6818 | 0.8830 | 99 | 2.4 | -| LCNet_x1_0 |0.7132 | 0.9003 | 161 | 3.0 | -| LCNet_x1_5 |0.7371 | 0.9153 | 342 | 4.5 | -| LCNet_x2_0 |0.7518 | 0.9227 | 590 | 6.5 | -| LCNet_x2_5 |0.7660 | 0.9300 | 906 | 9.0 | -| LCNet_x0_5_ssld |0.6610 | 0.8646 | 47 | 1.9 | -| LCNet_x1_0_ssld |0.7439 | 0.9209 | 161 | 3.0 | -| LCNet_x2_5_ssld |0.8082 | 0.9533 | 906 | 9.0 | +| PPLCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | +| PPLCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | +| PPLCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | +| PPLCNet_x0_75 |0.6818 | 0.8830 | 99 | 2.4 | +| PPLCNet_x1_0 |0.7132 | 0.9003 | 161 | 3.0 | +| PPLCNet_x1_5 |0.7371 | 0.9153 | 342 | 4.5 | +| PPLCNet_x2_0 |0.7518 | 0.9227 | 590 | 6.5 | +| PPLCNet_x2_5 |0.7660 | 0.9300 | 906 | 9.0 | +| PPLCNet_x0_5_ssld |0.6610 | 0.8646 | 47 | 1.9 | +| PPLCNet_x1_0_ssld |0.7439 | 0.9209 | 161 | 3.0 | +| PPLCNet_x2_5_ssld |0.8082 | 0.9533 | 906 | 9.0 | @@ -28,14 +28,14 @@ LCNet系列是百度PaddleCV团队提出的一种在Intel-CPU上表现优异的 | Models | Crop Size | Resize Short Size | FP32
Batch Size=1
(ms) | |------------------|-----------|-------------------|--------------------------| -| LCNet_x0_25 | 224 | 256 | 1.74 | -| LCNet_x0_35 | 224 | 256 | 1.92 | -| LCNet_x0_5 | 224 | 256 | 2.05 | -| LCNet_x0_75 | 224 | 256 | 2.29 | -| LCNet_x1_0 | 224 | 256 | 2.46 | -| LCNet_x1_5 | 224 | 256 | 3.19 | -| LCNet_x2_0 | 224 | 256 | 4.27 | -| LCNet_x2_5 | 224 | 256 | 5.39 | -| LCNet_x0_5_ssld | 224 | 256 | 2.05 | -| LCNet_x1_0_ssld | 224 | 256 | 2.46 | -| LCNet_x2_5_ssld | 224 | 256 | 5.39 | +| PPLCNet_x0_25 | 224 | 256 | 1.74 | +| PPLCNet_x0_35 | 224 | 256 | 1.92 | +| PPLCNet_x0_5 | 224 | 256 | 2.05 | +| PPLCNet_x0_75 | 224 | 256 | 2.29 | +| PPLCNet_x1_0 | 224 | 256 | 2.46 | +| PPLCNet_x1_5 | 224 | 256 | 3.19 | +| PPLCNet_x2_0 | 224 | 256 | 4.27 | +| PPLCNet_x2_5 | 224 | 256 | 5.39 | +| PPLCNet_x0_5_ssld | 224 | 256 | 2.05 | +| PPLCNet_x1_0_ssld | 224 | 256 | 2.46 | +| PPLCNet_x2_5_ssld | 224 | 256 | 5.39 | diff --git a/ppcls/arch/backbone/__init__.py b/ppcls/arch/backbone/__init__.py index 08d30ac32..d2efcdc07 100644 --- a/ppcls/arch/backbone/__init__.py +++ b/ppcls/arch/backbone/__init__.py @@ -21,7 +21,7 @@ from ppcls.arch.backbone.legendary_models.resnet import ResNet18, ResNet18_vd, R from ppcls.arch.backbone.legendary_models.vgg import VGG11, VGG13, VGG16, VGG19 from ppcls.arch.backbone.legendary_models.inception_v3 import InceptionV3 from ppcls.arch.backbone.legendary_models.hrnet import HRNet_W18_C, HRNet_W30_C, HRNet_W32_C, HRNet_W40_C, HRNet_W44_C, HRNet_W48_C, HRNet_W60_C, HRNet_W64_C, SE_HRNet_W64_C -from ppcls.arch.backbone.legendary_models.lcnet import LCNet_x0_25, LCNet_x0_35, LCNet_x0_5, LCNet_x0_75, LCNet_x1_0, LCNet_x1_5, LCNet_x2_0, LCNet_x2_5 +from ppcls.arch.backbone.legendary_models.pp_lcnet import PPLCNet_x0_25, PPLCNet_x0_35, PPLCNet_x0_5, PPLCNet_x0_75, PPLCNet_x1_0, PPLCNet_x1_5, PPLCNet_x2_0, PPLCNet_x2_5 from ppcls.arch.backbone.model_zoo.resnet_vc import ResNet50_vc from ppcls.arch.backbone.model_zoo.resnext import ResNeXt50_32x4d, ResNeXt50_64x4d, ResNeXt101_32x4d, ResNeXt101_64x4d, ResNeXt152_32x4d, ResNeXt152_64x4d diff --git a/ppcls/arch/backbone/legendary_models/lcnet.py b/ppcls/arch/backbone/legendary_models/pp_lcnet.py similarity index 80% rename from ppcls/arch/backbone/legendary_models/lcnet.py rename to ppcls/arch/backbone/legendary_models/pp_lcnet.py index 3dd6621de..05bbccd35 100644 --- a/ppcls/arch/backbone/legendary_models/lcnet.py +++ b/ppcls/arch/backbone/legendary_models/pp_lcnet.py @@ -24,22 +24,22 @@ from ppcls.arch.backbone.base.theseus_layer import TheseusLayer from ppcls.utils.save_load import load_dygraph_pretrain, load_dygraph_pretrain_from_url MODEL_URLS = { - "LCNet_x0_25": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_25_pretrained.pdparams", - "LCNet_x0_35": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_35_pretrained.pdparams", - "LCNet_x0_5": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_5_pretrained.pdparams", - "LCNet_x0_75": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x0_75_pretrained.pdparams", - "LCNet_x1_0": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_0_pretrained.pdparams", - "LCNet_x1_5": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x1_5_pretrained.pdparams", - "LCNet_x2_0": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_0_pretrained.pdparams", - "LCNet_x2_5": - "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/LCNet_x2_5_pretrained.pdparams" + "PPLCNet_x0_25": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_25_pretrained.pdparams", + "PPLCNet_x0_35": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_35_pretrained.pdparams", + "PPLCNet_x0_5": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_5_pretrained.pdparams", + "PPLCNet_x0_75": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_75_pretrained.pdparams", + "PPLCNet_x1_0": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_0_pretrained.pdparams", + "PPLCNet_x1_5": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_5_pretrained.pdparams", + "PPLCNet_x2_0": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_0_pretrained.pdparams", + "PPLCNet_x2_5": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_5_pretrained.pdparams" } __all__ = list(MODEL_URLS.keys()) @@ -166,7 +166,7 @@ class SEModule(TheseusLayer): return x -class LCNet(TheseusLayer): +class PPLCNet(TheseusLayer): def __init__(self, scale=1.0, class_num=1000, @@ -279,121 +279,121 @@ def _load_pretrained(pretrained, model, model_url, use_ssld): ) -def LCNet_x0_25(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x0_25(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x0_25 + PPLCNet_x0_25 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x0_25` model depends on args. + model: nn.Layer. Specific `PPLCNet_x0_25` model depends on args. """ - model = LCNet(scale=0.25, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_25"], use_ssld) + model = PPLCNet(scale=0.25, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x0_25"], use_ssld) return model -def LCNet_x0_35(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x0_35(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x0_35 + PPLCNet_x0_35 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x0_35` model depends on args. + model: nn.Layer. Specific `PPLCNet_x0_35` model depends on args. """ - model = LCNet(scale=0.35, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_35"], use_ssld) + model = PPLCNet(scale=0.35, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x0_35"], use_ssld) return model -def LCNet_x0_5(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x0_5(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x0_5 + PPLCNet_x0_5 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x0_5` model depends on args. + model: nn.Layer. Specific `PPLCNet_x0_5` model depends on args. """ - model = LCNet(scale=0.5, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_5"], use_ssld) + model = PPLCNet(scale=0.5, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x0_5"], use_ssld) return model -def LCNet_x0_75(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x0_75(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x0_75 + PPLCNet_x0_75 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x0_75` model depends on args. + model: nn.Layer. Specific `PPLCNet_x0_75` model depends on args. """ - model = LCNet(scale=0.75, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x0_75"], use_ssld) + model = PPLCNet(scale=0.75, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x0_75"], use_ssld) return model -def LCNet_x1_0(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x1_0(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x1_0 + PPLCNet_x1_0 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x1_0` model depends on args. + model: nn.Layer. Specific `PPLCNet_x1_0` model depends on args. """ - model = LCNet(scale=1.0, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x1_0"], use_ssld) + model = PPLCNet(scale=1.0, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x1_0"], use_ssld) return model -def LCNet_x1_5(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x1_5(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x1_5 + PPLCNet_x1_5 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x1_5` model depends on args. + model: nn.Layer. Specific `PPLCNet_x1_5` model depends on args. """ - model = LCNet(scale=1.5, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x1_5"], use_ssld) + model = PPLCNet(scale=1.5, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x1_5"], use_ssld) return model -def LCNet_x2_0(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x2_0(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x2_0 + PPLCNet_x2_0 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x2_0` model depends on args. + model: nn.Layer. Specific `PPLCNet_x2_0` model depends on args. """ - model = LCNet(scale=2.0, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x2_0"], use_ssld) + model = PPLCNet(scale=2.0, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x2_0"], use_ssld) return model -def LCNet_x2_5(pretrained=False, use_ssld=False, **kwargs): +def PPLCNet_x2_5(pretrained=False, use_ssld=False, **kwargs): """ - LCNet_x2_5 + PPLCNet_x2_5 Args: pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. If str, means the path of the pretrained model. use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. Returns: - model: nn.Layer. Specific `LCNet_x2_5` model depends on args. + model: nn.Layer. Specific `PPLCNet_x2_5` model depends on args. """ - model = LCNet(scale=2.5, **kwargs) - _load_pretrained(pretrained, model, MODEL_URLS["LCNet_x2_5"], use_ssld) + model = PPLCNet(scale=2.5, **kwargs) + _load_pretrained(pretrained, model, MODEL_URLS["PPLCNet_x2_5"], use_ssld) return model From 9eb098a110110e8aa72e8a5e11727b297fd90d06 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Mon, 13 Sep 2021 09:00:39 +0000 Subject: [PATCH 13/81] Update PPLCNet config --- .../ImageNet/PPLCNet/PPLCNet_x0_25.yaml | 129 +++++++++++++++++ .../ImageNet/PPLCNet/PPLCNet_x0_35.yaml | 129 +++++++++++++++++ .../ImageNet/PPLCNet/PPLCNet_x0_5.yaml | 129 +++++++++++++++++ .../ImageNet/PPLCNet/PPLCNet_x0_75.yaml | 129 +++++++++++++++++ .../ImageNet/PPLCNet/PPLCNet_x1_0.yaml | 129 +++++++++++++++++ .../ImageNet/PPLCNet/PPLCNet_x1_5.yaml | 129 +++++++++++++++++ .../ImageNet/PPLCNet/PPLCNet_x2_0.yaml | 129 +++++++++++++++++ .../ImageNet/PPLCNet/PPLCNet_x2_5.yaml | 130 ++++++++++++++++++ 8 files changed, 1033 insertions(+) create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_25.yaml create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_35.yaml create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_5.yaml create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_75.yaml create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_0.yaml create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_5.yaml create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_0.yaml create mode 100644 ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_5.yaml diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_25.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_25.yaml new file mode 100644 index 000000000..6773af797 --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_25.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x0_25 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_35.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_35.yaml new file mode 100644 index 000000000..36aa3dcb0 --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_35.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x0_35 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_5.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_5.yaml new file mode 100644 index 000000000..3ffc49385 --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_5.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x0_5 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_75.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_75.yaml new file mode 100644 index 000000000..24ad8f373 --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_75.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x0_75 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_0.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_0.yaml new file mode 100644 index 000000000..0921db540 --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_0.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x1_0 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00003 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_5.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_5.yaml new file mode 100644 index 000000000..290d1c0e6 --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_5.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x1_5 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_0.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_0.yaml new file mode 100644 index 000000000..c8f7a3cdc --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_0.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x2_0 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] diff --git a/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_5.yaml b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_5.yaml new file mode 100644 index 000000000..2ea69e30d --- /dev/null +++ b/ppcls/configs/ImageNet/PPLCNet/PPLCNet_x2_5.yaml @@ -0,0 +1,130 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + class_num: 1000 + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 360 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference +# model architecture +Arch: + name: PPLCNet_x2_5 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + epsilon: 0.1 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.8 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - AutoAugment: + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 512 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] From 8e47811d996d61322ce499d50241f41e5e1ca707 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Mon, 13 Sep 2021 09:29:57 +0000 Subject: [PATCH 14/81] Update PPLCNet --- ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml | 129 ----------------- ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml | 129 ----------------- ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml | 129 ----------------- ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml | 129 ----------------- ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml | 129 ----------------- ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml | 129 ----------------- ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml | 129 ----------------- ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml | 130 ------------------ 8 files changed, 1033 deletions(-) delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml delete mode 100644 ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml deleted file mode 100644 index 40d5bc626..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x0_25.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x0_25 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00003 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml deleted file mode 100644 index 5d321e6f8..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x0_35.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x0_35 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00003 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml deleted file mode 100644 index 42264d86b..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x0_5.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x0_5 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00003 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml deleted file mode 100644 index 93f86cc49..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x0_75.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x0_75 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00003 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml deleted file mode 100644 index ac4c917fb..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x1_0.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x1_0 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00003 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml deleted file mode 100644 index d426d911e..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x1_5.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x1_5 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00004 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml deleted file mode 100644 index afe310b4a..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x2_0.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x2_0 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00004 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] diff --git a/ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml b/ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml deleted file mode 100644 index befa1acb1..000000000 --- a/ppcls/configs/ImageNet/LCNet/LCNet_x2_5.yaml +++ /dev/null @@ -1,130 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - class_num: 1000 - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 360 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference -# model architecture -Arch: - name: LCNet_x2_5 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - epsilon: 0.1 - Eval: - - CELoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.8 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00004 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - AutoAugment: - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 512 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: ImageNetDataset - image_root: ./dataset/ILSVRC2012/ - cls_label_path: ./dataset/ILSVRC2012/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - -Metric: - Train: - - TopkAcc: - topk: [1, 5] - Eval: - - TopkAcc: - topk: [1, 5] From b6fad5e993248e59419cdc4af2d0d9341b09ab75 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Mon, 13 Sep 2021 09:35:25 +0000 Subject: [PATCH 15/81] Update PPLCNet --- docs/en/models/{LCNet_en.md => PPLCNet_en.md} | 0 docs/zh_CN/models/PPLCNet.md | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+) rename docs/en/models/{LCNet_en.md => PPLCNet_en.md} (100%) create mode 100644 docs/zh_CN/models/PPLCNet.md diff --git a/docs/en/models/LCNet_en.md b/docs/en/models/PPLCNet_en.md similarity index 100% rename from docs/en/models/LCNet_en.md rename to docs/en/models/PPLCNet_en.md diff --git a/docs/zh_CN/models/PPLCNet.md b/docs/zh_CN/models/PPLCNet.md new file mode 100644 index 000000000..307708545 --- /dev/null +++ b/docs/zh_CN/models/PPLCNet.md @@ -0,0 +1,41 @@ +# PPLCNet系列 + +## 概述 + +PPLCNet系列是百度PaddleCV团队提出的一种在Intel-CPU上表现优异的网络,作者总结了一些在Intel-CPU上可以提升模型精度但几乎不增加推理耗时的方法,将这些方法组合成了一个新的网络,即PPLCNet。与其他轻量级网络相比,PPLCNet可以在相同延时下取得更高的精度。PPLCNet已在图像分类、目标检测、语义分割上表现出了强大的竞争力。 + + + +## 精度、FLOPS和参数量 + +| Models | Top1 | Top5 | FLOPs
(M) | Parameters
(M) | +|:--:|:--:|:--:|:--:|:--:| +| PPLCNet_x0_25 |0.5186 | 0.7565 | 18 | 1.5 | +| PPLCNet_x0_35 |0.5809 | 0.8083 | 29 | 1.6 | +| PPLCNet_x0_5 |0.6314 | 0.8466 | 47 | 1.9 | +| PPLCNet_x0_75 |0.6818 | 0.8830 | 99 | 2.4 | +| PPLCNet_x1_0 |0.7132 | 0.9003 | 161 | 3.0 | +| PPLCNet_x1_5 |0.7371 | 0.9153 | 342 | 4.5 | +| PPLCNet_x2_0 |0.7518 | 0.9227 | 590 | 6.5 | +| PPLCNet_x2_5 |0.7660 | 0.9300 | 906 | 9.0 | +| PPLCNet_x0_5_ssld |0.6610 | 0.8646 | 47 | 1.9 | +| PPLCNet_x1_0_ssld |0.7439 | 0.9209 | 161 | 3.0 | +| PPLCNet_x2_5_ssld |0.8082 | 0.9533 | 906 | 9.0 | + + + +## 基于Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz的预测速度 + +| Models | Crop Size | Resize Short Size | FP32
Batch Size=1
(ms) | +|------------------|-----------|-------------------|--------------------------| +| PPLCNet_x0_25 | 224 | 256 | 1.74 | +| PPLCNet_x0_35 | 224 | 256 | 1.92 | +| PPLCNet_x0_5 | 224 | 256 | 2.05 | +| PPLCNet_x0_75 | 224 | 256 | 2.29 | +| PPLCNet_x1_0 | 224 | 256 | 2.46 | +| PPLCNet_x1_5 | 224 | 256 | 3.19 | +| PPLCNet_x2_0 | 224 | 256 | 4.27 | +| PPLCNet_x2_5 | 224 | 256 | 5.39 | +| PPLCNet_x0_5_ssld | 224 | 256 | 2.05 | +| PPLCNet_x1_0_ssld | 224 | 256 | 2.46 | +| PPLCNet_x2_5_ssld | 224 | 256 | 5.39 | From 64c370008b9233a369527a3fdd4d5122c127f41c Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Tue, 14 Sep 2021 04:49:55 +0000 Subject: [PATCH 16/81] feat: support pil resize Support PIL resizse with PIL interpolation to train transformer. Almost all vision transformer models need using PIL.Image.BICUBIC as interpolation in resize. --- deploy/python/preprocess.py | 76 +++++++++++++++++++++----- ppcls/data/preprocess/ops/operators.py | 76 +++++++++++++++++++++----- 2 files changed, 126 insertions(+), 26 deletions(-) diff --git a/deploy/python/preprocess.py b/deploy/python/preprocess.py index 552019f6d..aaa0bef35 100644 --- a/deploy/python/preprocess.py +++ b/deploy/python/preprocess.py @@ -19,12 +19,14 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +from functools import partial import six import math import random import cv2 import numpy as np import importlib +from PIL import Image from python.det_preprocess import DetNormalizeImage, DetPadStride, DetPermute, DetResize @@ -50,6 +52,47 @@ def create_operators(params): return ops +class UnifiedResize(object): + def __init__(self, interpolation=None, backend="cv2"): + _cv2_interp_from_str = { + 'nearest': cv2.INTER_NEAREST, + 'bilinear': cv2.INTER_LINEAR, + 'area': cv2.INTER_AREA, + 'bicubic': cv2.INTER_CUBIC, + 'lanczos': cv2.INTER_LANCZOS4 + } + _pil_interp_from_str = { + 'nearest': Image.NEAREST, + 'bilinear': Image.BILINEAR, + 'bicubic': Image.BICUBIC, + 'box': Image.BOX, + 'lanczos': Image.LANCZOS, + 'hamming': Image.HAMMING + } + + def _pil_resize(src, size, resample): + pil_img = Image.fromarray(src) + pil_img = pil_img.resize(size, resample) + return np.asarray(pil_img) + + if backend.lower() == "cv2": + if isinstance(interpolation, str): + interpolation = _cv2_interp_from_str[interpolation.lower()] + self.resize_func = partial(cv2.resize, interpolation=interpolation) + elif backend.lower() == "pil": + if isinstance(interpolation, str): + interpolation = _pil_interp_from_str[interpolation.lower()] + self.resize_func = partial(_pil_resize, resample=interpolation) + else: + logger.warning( + f"The backend of Resize only support \"cv2\" or \"PIL\". \"f{backend}\" is unavailable. Use \"cv2\" instead." + ) + self.resize_func = cv2.resize + + def __call__(self, src, size): + return self.resize_func(src, size) + + class OperatorParamError(ValueError): """ OperatorParamError """ @@ -87,8 +130,11 @@ class DecodeImage(object): class ResizeImage(object): """ resize image """ - def __init__(self, size=None, resize_short=None, interpolation=-1): - self.interpolation = interpolation if interpolation >= 0 else None + def __init__(self, + size=None, + resize_short=None, + interpolation=None, + backend="cv2"): if resize_short is not None and resize_short > 0: self.resize_short = resize_short self.w = None @@ -101,6 +147,9 @@ class ResizeImage(object): raise OperatorParamError("invalid params for ReisizeImage for '\ 'both 'size' and 'resize_short' are None") + self._resize_func = UnifiedResize( + interpolation=interpolation, backend=backend) + def __call__(self, img): img_h, img_w = img.shape[:2] if self.resize_short is not None: @@ -110,10 +159,7 @@ class ResizeImage(object): else: w = self.w h = self.h - if self.interpolation is None: - return cv2.resize(img, (w, h)) - else: - return cv2.resize(img, (w, h), interpolation=self.interpolation) + return self._resize_func(img, (w, h)) class CropImage(object): @@ -145,9 +191,12 @@ class CropImage(object): class RandCropImage(object): """ random crop image """ - def __init__(self, size, scale=None, ratio=None, interpolation=-1): - - self.interpolation = interpolation if interpolation >= 0 else None + def __init__(self, + size, + scale=None, + ratio=None, + interpolation=None, + backend="cv2"): if type(size) is int: self.size = (size, size) # (h, w) else: @@ -156,6 +205,9 @@ class RandCropImage(object): self.scale = [0.08, 1.0] if scale is None else scale self.ratio = [3. / 4., 4. / 3.] if ratio is None else ratio + self._resize_func = UnifiedResize( + interpolation=interpolation, backend=backend) + def __call__(self, img): size = self.size scale = self.scale @@ -181,10 +233,8 @@ class RandCropImage(object): j = random.randint(0, img_h - h) img = img[j:j + h, i:i + w, :] - if self.interpolation is None: - return cv2.resize(img, size) - else: - return cv2.resize(img, size, interpolation=self.interpolation) + + return self._resize_func(img, size) class RandFlipImage(object): diff --git a/ppcls/data/preprocess/ops/operators.py b/ppcls/data/preprocess/ops/operators.py index b00dd139a..77c9f7ac7 100644 --- a/ppcls/data/preprocess/ops/operators.py +++ b/ppcls/data/preprocess/ops/operators.py @@ -19,6 +19,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +from functools import partial import six import math import random @@ -28,6 +29,48 @@ from PIL import Image from .autoaugment import ImageNetPolicy from .functional import augmentations +from ppcls.utils import logger + + +class UnifiedResize(object): + def __init__(self, interpolation=None, backend="cv2"): + _cv2_interp_from_str = { + 'nearest': cv2.INTER_NEAREST, + 'bilinear': cv2.INTER_LINEAR, + 'area': cv2.INTER_AREA, + 'bicubic': cv2.INTER_CUBIC, + 'lanczos': cv2.INTER_LANCZOS4 + } + _pil_interp_from_str = { + 'nearest': Image.NEAREST, + 'bilinear': Image.BILINEAR, + 'bicubic': Image.BICUBIC, + 'box': Image.BOX, + 'lanczos': Image.LANCZOS, + 'hamming': Image.HAMMING + } + + def _pil_resize(src, size, resample): + pil_img = Image.fromarray(src) + pil_img = pil_img.resize(size, resample) + return np.asarray(pil_img) + + if backend.lower() == "cv2": + if isinstance(interpolation, str): + interpolation = _cv2_interp_from_str[interpolation.lower()] + self.resize_func = partial(cv2.resize, interpolation=interpolation) + elif backend.lower() == "pil": + if isinstance(interpolation, str): + interpolation = _pil_interp_from_str[interpolation.lower()] + self.resize_func = partial(_pil_resize, resample=interpolation) + else: + logger.warning( + f"The backend of Resize only support \"cv2\" or \"PIL\". \"f{backend}\" is unavailable. Use \"cv2\" instead." + ) + self.resize_func = cv2.resize + + def __call__(self, src, size): + return self.resize_func(src, size) class OperatorParamError(ValueError): @@ -67,8 +110,11 @@ class DecodeImage(object): class ResizeImage(object): """ resize image """ - def __init__(self, size=None, resize_short=None, interpolation=-1): - self.interpolation = interpolation if interpolation >= 0 else None + def __init__(self, + size=None, + resize_short=None, + interpolation=None, + backend="cv2"): if resize_short is not None and resize_short > 0: self.resize_short = resize_short self.w = None @@ -81,6 +127,9 @@ class ResizeImage(object): raise OperatorParamError("invalid params for ReisizeImage for '\ 'both 'size' and 'resize_short' are None") + self._resize_func = UnifiedResize( + interpolation=interpolation, backend=backend) + def __call__(self, img): img_h, img_w = img.shape[:2] if self.resize_short is not None: @@ -90,10 +139,7 @@ class ResizeImage(object): else: w = self.w h = self.h - if self.interpolation is None: - return cv2.resize(img, (w, h)) - else: - return cv2.resize(img, (w, h), interpolation=self.interpolation) + return self._resize_func(img, (w, h)) class CropImage(object): @@ -119,9 +165,12 @@ class CropImage(object): class RandCropImage(object): """ random crop image """ - def __init__(self, size, scale=None, ratio=None, interpolation=-1): - - self.interpolation = interpolation if interpolation >= 0 else None + def __init__(self, + size, + scale=None, + ratio=None, + interpolation=None, + backend="cv2"): if type(size) is int: self.size = (size, size) # (h, w) else: @@ -130,6 +179,9 @@ class RandCropImage(object): self.scale = [0.08, 1.0] if scale is None else scale self.ratio = [3. / 4., 4. / 3.] if ratio is None else ratio + self._resize_func = UnifiedResize( + interpolation=interpolation, backend=backend) + def __call__(self, img): size = self.size scale = self.scale @@ -155,10 +207,8 @@ class RandCropImage(object): j = random.randint(0, img_h - h) img = img[j:j + h, i:i + w, :] - if self.interpolation is None: - return cv2.resize(img, size) - else: - return cv2.resize(img, size, interpolation=self.interpolation) + + return self._resize_func(img, size) class RandFlipImage(object): From 4d1f61c407bf07bd5a177eab50705fd1b8668a1d Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 16 Sep 2021 07:54:16 +0000 Subject: [PATCH 17/81] fix Alexnet lr bug --- ppcls/configs/ImageNet/AlexNet/AlexNet.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml b/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml index 7fa94c751..6e6b77daf 100644 --- a/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml +++ b/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml @@ -36,7 +36,7 @@ Optimizer: name: Piecewise learning_rate: 0.01 decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] + values: [0.01, 0.001, 0.0001, 0.00001] regularizer: name: 'L2' coeff: 0.0001 From 43aeb572a36fd8991f5a1e90968da4117ec6c726 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 16 Sep 2021 08:08:41 +0000 Subject: [PATCH 18/81] fix Alexnet lr bug --- ppcls/configs/ImageNet/AlexNet/AlexNet.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml b/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml index 6e6b77daf..1df2cbd1e 100644 --- a/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml +++ b/ppcls/configs/ImageNet/AlexNet/AlexNet.yaml @@ -34,7 +34,6 @@ Optimizer: momentum: 0.9 lr: name: Piecewise - learning_rate: 0.01 decay_epochs: [30, 60, 90] values: [0.01, 0.001, 0.0001, 0.00001] regularizer: From 18e1cf040bb2cb27c0fe367217cba9ee6aa50663 Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Thu, 16 Sep 2021 12:17:02 +0000 Subject: [PATCH 19/81] fix pact bug for circlemargin arcmargin cosmargin --- .gitignore | 3 +- ppcls/arch/gears/arcmargin.py | 19 +- ppcls/arch/gears/circlemargin.py | 17 +- ppcls/arch/gears/cosmargin.py | 18 +- ppcls/configs/Vehicle/ResNet50.yaml | 7 +- ppcls/configs/Vehicle/ResNet50_ReID.yaml | 2 +- .../slim/ResNet50_vehicle_cls_prune.yaml | 136 +++++++++++++++ .../ResNet50_vehicle_cls_quantization.yaml | 135 +++++++++++++++ .../slim/ResNet50_vehicle_reid_prune.yaml | 2 +- .../ResNet50_vehicle_reid_quantization.yaml | 162 ++++++++++++++++++ 10 files changed, 461 insertions(+), 40 deletions(-) create mode 100644 ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml create mode 100644 ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml create mode 100644 ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml diff --git a/.gitignore b/.gitignore index 8f00d034f..f56c23c00 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,11 @@ __pycache__/ *.sw* */workerlog* checkpoints/ -output/ +output*/ pretrained/ .ipynb_checkpoints/ *.ipynb* _build/ build/ +log/ nohup.out diff --git a/ppcls/arch/gears/arcmargin.py b/ppcls/arch/gears/arcmargin.py index bab7a356f..22cc76e1d 100644 --- a/ppcls/arch/gears/arcmargin.py +++ b/ppcls/arch/gears/arcmargin.py @@ -24,30 +24,25 @@ class ArcMargin(nn.Layer): margin=0.5, scale=80.0, easy_margin=False): - super(ArcMargin, self).__init__() + super().__init__() self.embedding_size = embedding_size self.class_num = class_num self.margin = margin self.scale = scale self.easy_margin = easy_margin - - weight_attr = paddle.ParamAttr( - initializer=paddle.nn.initializer.XavierNormal()) - self.fc = nn.Linear( - self.embedding_size, - self.class_num, - weight_attr=weight_attr, - bias_attr=False) + self.weight = self.create_parameter( + shape=[self.embedding_size, self.class_num], + is_bias=False, + default_initializer=paddle.nn.initializer.XavierNormal()) def forward(self, input, label=None): input_norm = paddle.sqrt( paddle.sum(paddle.square(input), axis=1, keepdim=True)) input = paddle.divide(input, input_norm) - weight = self.fc.weight weight_norm = paddle.sqrt( - paddle.sum(paddle.square(weight), axis=0, keepdim=True)) - weight = paddle.divide(weight, weight_norm) + paddle.sum(paddle.square(self.weight), axis=0, keepdim=True)) + weight = paddle.divide(self.weight, weight_norm) cos = paddle.matmul(input, weight) if not self.training or label is None: diff --git a/ppcls/arch/gears/circlemargin.py b/ppcls/arch/gears/circlemargin.py index 87baee839..d1bce83cb 100644 --- a/ppcls/arch/gears/circlemargin.py +++ b/ppcls/arch/gears/circlemargin.py @@ -26,20 +26,19 @@ class CircleMargin(nn.Layer): self.embedding_size = embedding_size self.class_num = class_num - weight_attr = paddle.ParamAttr( - initializer=paddle.nn.initializer.XavierNormal()) - self.fc = paddle.nn.Linear( - self.embedding_size, self.class_num, weight_attr=weight_attr) + self.weight = self.create_parameter( + shape=[self.embedding_size, self.class_num], + is_bias=False, + default_initializer=paddle.nn.initializer.XavierNormal()) def forward(self, input, label): feat_norm = paddle.sqrt( paddle.sum(paddle.square(input), axis=1, keepdim=True)) input = paddle.divide(input, feat_norm) - weight = self.fc.weight weight_norm = paddle.sqrt( - paddle.sum(paddle.square(weight), axis=0, keepdim=True)) - weight = paddle.divide(weight, weight_norm) + paddle.sum(paddle.square(self.weight), axis=0, keepdim=True)) + weight = paddle.divide(self.weight, weight_norm) logits = paddle.matmul(input, weight) if not self.training or label is None: @@ -49,9 +48,9 @@ class CircleMargin(nn.Layer): alpha_n = paddle.clip(logits.detach() + self.margin, min=0.) delta_p = 1 - self.margin delta_n = self.margin - + m_hot = F.one_hot(label.reshape([-1]), num_classes=logits.shape[1]) - + logits_p = alpha_p * (logits - delta_p) logits_n = alpha_n * (logits - delta_n) pre_logits = logits_p * m_hot + logits_n * (1 - m_hot) diff --git a/ppcls/arch/gears/cosmargin.py b/ppcls/arch/gears/cosmargin.py index 378e102a2..578b64c2b 100644 --- a/ppcls/arch/gears/cosmargin.py +++ b/ppcls/arch/gears/cosmargin.py @@ -25,13 +25,10 @@ class CosMargin(paddle.nn.Layer): self.embedding_size = embedding_size self.class_num = class_num - weight_attr = paddle.ParamAttr( - initializer=paddle.nn.initializer.XavierNormal()) - self.fc = nn.Linear( - self.embedding_size, - self.class_num, - weight_attr=weight_attr, - bias_attr=False) + self.weight = self.create_parameter( + shape=[self.embedding_size, self.class_num], + is_bias=False, + default_initializer=paddle.nn.initializer.XavierNormal()) def forward(self, input, label): label.stop_gradient = True @@ -40,15 +37,14 @@ class CosMargin(paddle.nn.Layer): paddle.sum(paddle.square(input), axis=1, keepdim=True)) input = paddle.divide(input, input_norm) - weight = self.fc.weight weight_norm = paddle.sqrt( - paddle.sum(paddle.square(weight), axis=0, keepdim=True)) - weight = paddle.divide(weight, weight_norm) + paddle.sum(paddle.square(self.weight), axis=0, keepdim=True)) + weight = paddle.divide(self.weight, weight_norm) cos = paddle.matmul(input, weight) if not self.training or label is None: return cos - + cos_m = cos - self.margin one_hot = paddle.nn.functional.one_hot(label, self.class_num) diff --git a/ppcls/configs/Vehicle/ResNet50.yaml b/ppcls/configs/Vehicle/ResNet50.yaml index 335222ed4..ba2a0b900 100644 --- a/ppcls/configs/Vehicle/ResNet50.yaml +++ b/ppcls/configs/Vehicle/ResNet50.yaml @@ -2,7 +2,7 @@ Global: checkpoints: null pretrained_model: null - output_dir: "./output/" + output_dir: "./output_vehicle_cls/" device: "gpu" save_interval: 1 eval_during_train: True @@ -51,11 +51,8 @@ Optimizer: name: Momentum momentum: 0.9 lr: - name: MultiStepDecay + name: Cosine learning_rate: 0.01 - milestones: [30, 60, 70, 80, 90, 100, 120, 140] - gamma: 0.5 - verbose: False last_epoch: -1 regularizer: name: 'L2' diff --git a/ppcls/configs/Vehicle/ResNet50_ReID.yaml b/ppcls/configs/Vehicle/ResNet50_ReID.yaml index 333b6a243..09a04c13b 100644 --- a/ppcls/configs/Vehicle/ResNet50_ReID.yaml +++ b/ppcls/configs/Vehicle/ResNet50_ReID.yaml @@ -2,7 +2,7 @@ Global: checkpoints: null pretrained_model: null - output_dir: "./output/" + output_dir: "./output_vehicle_reid/" device: "gpu" save_interval: 1 eval_during_train: True diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml new file mode 100644 index 000000000..788a49b43 --- /dev/null +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml @@ -0,0 +1,136 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: "./output_vehicle_cls_prune/" + device: "gpu" + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 160 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: "./inference" + +Slim: + prune: + name: fpgm + pruned_ratio: 0.3 + +# model architecture +Arch: + name: "RecModel" + infer_output_key: "features" + infer_add_softmax: False + Backbone: + name: "ResNet50_last_stage_stride1" + pretrained: True + BackboneStopLayer: + name: "adaptive_avg_pool2d_0" + Neck: + name: "VehicleNeck" + in_channels: 2048 + out_channels: 512 + Head: + name: "ArcMargin" + embedding_size: 512 + class_num: 431 + margin: 0.15 + scale: 32 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + - SupConLoss: + weight: 1.0 + views: 2 + Eval: + - CELoss: + weight: 1.0 + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.01 + last_epoch: -1 + regularizer: + name: 'L2' + coeff: 0.0005 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: "CompCars" + image_root: "./dataset/CompCars/image/" + label_root: "./dataset/CompCars/label/" + bbox_crop: True + cls_label_path: "./dataset/CompCars/train_test_split/classification/train_label.txt" + transform_ops: + - ResizeImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - AugMix: + prob: 0.5 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - RandomErasing: + EPSILON: 0.5 + sl: 0.02 + sh: 0.4 + r1: 0.3 + mean: [0., 0., 0.] + + sampler: + name: DistributedRandomIdentitySampler + batch_size: 128 + num_instances: 2 + drop_last: False + shuffle: True + loader: + num_workers: 8 + use_shared_memory: True + + Eval: + dataset: + name: "CompCars" + image_root: "./dataset/CompCars/image/" + label_root: "./dataset/CompCars/label/" + cls_label_path: "./dataset/CompCars/train_test_split/classification/test_label.txt" + bbox_crop: True + transform_ops: + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 128 + drop_last: False + shuffle: False + loader: + num_workers: 8 + use_shared_memory: True + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] + diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml new file mode 100644 index 000000000..14905148d --- /dev/null +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml @@ -0,0 +1,135 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: "./output_vehicle_cls_pact/" + device: "gpu" + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 80 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: "./inference" + +Slim: + quant: + name: pact + +# model architecture +Arch: + name: "RecModel" + infer_output_key: "features" + infer_add_softmax: False + Backbone: + name: "ResNet50_last_stage_stride1" + pretrained: True + BackboneStopLayer: + name: "adaptive_avg_pool2d_0" + Neck: + name: "VehicleNeck" + in_channels: 2048 + out_channels: 512 + Head: + name: "ArcMargin" + embedding_size: 512 + class_num: 431 + margin: 0.15 + scale: 32 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + - SupConLoss: + weight: 1.0 + views: 2 + Eval: + - CELoss: + weight: 1.0 + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.001 + last_epoch: -1 + regularizer: + name: 'L2' + coeff: 0.0005 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: "CompCars" + image_root: "./dataset/CompCars/image/" + label_root: "./dataset/CompCars/label/" + bbox_crop: True + cls_label_path: "./dataset/CompCars/train_test_split/classification/train_label.txt" + transform_ops: + - ResizeImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - AugMix: + prob: 0.5 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - RandomErasing: + EPSILON: 0.5 + sl: 0.02 + sh: 0.4 + r1: 0.3 + mean: [0., 0., 0.] + + sampler: + name: DistributedRandomIdentitySampler + batch_size: 128 + num_instances: 2 + drop_last: False + shuffle: True + loader: + num_workers: 8 + use_shared_memory: True + + Eval: + dataset: + name: "CompCars" + image_root: "./dataset/CompCars/image/" + label_root: "./dataset/CompCars/label/" + cls_label_path: "./dataset/CompCars/train_test_split/classification/test_label.txt" + bbox_crop: True + transform_ops: + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 128 + drop_last: False + shuffle: False + loader: + num_workers: 8 + use_shared_memory: True + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] + diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml index 683e0145e..a96ffb33b 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml @@ -2,7 +2,7 @@ Global: checkpoints: null pretrained_model: null - output_dir: "./output/" + output_dir: "./output_fpgm/" device: "gpu" save_interval: 1 eval_during_train: True diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml new file mode 100644 index 000000000..712a3fca2 --- /dev/null +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml @@ -0,0 +1,162 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: "./output_vehicle_reid_pact/" + device: "gpu" + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 40 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: "./inference" + eval_mode: "retrieval" + +# for quantizaiton or prune model +Slim: + ## for prune + quant: + name: pact + +# model architecture +Arch: + name: "RecModel" + infer_output_key: "features" + infer_add_softmax: False + Backbone: + name: "ResNet50_last_stage_stride1" + pretrained: True + BackboneStopLayer: + name: "adaptive_avg_pool2d_0" + Neck: + name: "VehicleNeck" + in_channels: 2048 + out_channels: 512 + Head: + name: "ArcMargin" + embedding_size: 512 + class_num: 30671 + margin: 0.15 + scale: 32 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + - SupConLoss: + weight: 1.0 + views: 2 + Eval: + - CELoss: + weight: 1.0 + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.001 + last_epoch: -1 + regularizer: + name: 'L2' + coeff: 0.0005 + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: "VeriWild" + image_root: "./dataset/VeRI-Wild/images/" + cls_label_path: "./dataset/VeRI-Wild/train_test_split/train_list_start0.txt" + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - AugMix: + prob: 0.5 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - RandomErasing: + EPSILON: 0.5 + sl: 0.02 + sh: 0.4 + r1: 0.3 + mean: [0., 0., 0.] + + sampler: + name: DistributedRandomIdentitySampler + batch_size: 64 + num_instances: 2 + drop_last: False + shuffle: True + loader: + num_workers: 6 + use_shared_memory: True + Eval: + Query: + dataset: + name: "VeriWild" + image_root: "./dataset/VeRI-Wild/images" + cls_label_path: "./dataset/VeRI-Wild/train_test_split/test_3000_id_query.txt" + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 6 + use_shared_memory: True + + Gallery: + dataset: + name: "VeriWild" + image_root: "./dataset/VeRI-Wild/images" + cls_label_path: "./dataset/VeRI-Wild/train_test_split/test_3000_id.txt" + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 6 + use_shared_memory: True + +Metric: + Eval: + - Recallk: + topk: [1, 5] + - mAP: {} + From 894f5a5bae8e16457a1822bb793612c5b603d709 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Fri, 17 Sep 2021 03:38:01 +0000 Subject: [PATCH 20/81] Update README --- README_ch.md | 2 +- README_en.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README_ch.md b/README_ch.md index e0f613a3e..74b9c1318 100644 --- a/README_ch.md +++ b/README_ch.md @@ -7,7 +7,7 @@ 飞桨图像识别套件PaddleClas是飞桨为工业界和学术界所准备的一个图像识别任务的工具集,助力使用者训练出更好的视觉模型和应用落地。 **近期更新** -- 2021.09.08 增加PaddleClas自研PPLCNet系列模型, 这些模型在Intel CPU上有较强的竞争力。相关指标和预训练权重可以从 [这里](docs/zh_CN/ImageNet_models.md)下载。 +- 2021.09.17 增加PaddleClas自研PP-LCNet系列模型, 这些模型在Intel CPU上有较强的竞争力。相关指标和预训练权重可以从 [这里](docs/zh_CN/ImageNet_models.md)下载。 - 2021.08.11 更新7个[FAQ](docs/zh_CN/faq_series/faq_2021_s2.md)。 - 2021.06.29 添加Swin-transformer系列模型,ImageNet1k数据集上Top1 acc最高精度可达87.2%;支持训练预测评估与whl包部署,预训练模型可以从[这里](docs/zh_CN/models/models_intro.md)下载。 - 2021.06.22,23,24 PaddleClas官方研发团队带来技术深入解读三日直播课。课程回放:[https://aistudio.baidu.com/aistudio/course/introduce/24519](https://aistudio.baidu.com/aistudio/course/introduce/24519) diff --git a/README_en.md b/README_en.md index 2436554e2..aa65ce382 100644 --- a/README_en.md +++ b/README_en.md @@ -8,7 +8,7 @@ PaddleClas is an image recognition toolset for industry and academia, helping us **Recent updates** -- 2021.09.08 Add PPLCNet series model developed by PaddleClas, these models show strong competitiveness on Intel CPUs. The metrics and pretrained model are available [here](docs/en/ImageNet_models_en.md). +- 2021.09.17 Add PP-LCNet series model developed by PaddleClas, these models show strong competitiveness on Intel CPUs. The metrics and pretrained model are available [here](docs/en/ImageNet_models_en.md). - 2021.06.29 Add Swin-transformer series model,Highest top1 acc on ImageNet1k dataset reaches 87.2%, training, evaluation and inference are all supported. Pretrained models can be downloaded [here](docs/en/models/models_intro_en.md). - 2021.06.16 PaddleClas release/2.2. Add metric learning and vector search modules. Add product recognition, animation character recognition, vehicle recognition and logo recognition. Added 30 pretrained models of LeViT, Twins, TNT, DLA, HarDNet, and RedNet, and the accuracy is roughly the same as that of the paper. From a9a13d5984aeb19d27a23d75262038e2fb4bee9a Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Fri, 17 Sep 2021 07:38:21 +0000 Subject: [PATCH 21/81] update Vehicle yamls --- ppcls/configs/Vehicle/ResNet50.yaml | 1 - ppcls/configs/Vehicle/ResNet50_ReID.yaml | 1 - ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml | 1 - ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml | 1 - ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml | 1 - ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml | 1 - 6 files changed, 6 deletions(-) diff --git a/ppcls/configs/Vehicle/ResNet50.yaml b/ppcls/configs/Vehicle/ResNet50.yaml index ba2a0b900..33b05ba50 100644 --- a/ppcls/configs/Vehicle/ResNet50.yaml +++ b/ppcls/configs/Vehicle/ResNet50.yaml @@ -53,7 +53,6 @@ Optimizer: lr: name: Cosine learning_rate: 0.01 - last_epoch: -1 regularizer: name: 'L2' coeff: 0.0005 diff --git a/ppcls/configs/Vehicle/ResNet50_ReID.yaml b/ppcls/configs/Vehicle/ResNet50_ReID.yaml index 09a04c13b..4e56e8dfb 100644 --- a/ppcls/configs/Vehicle/ResNet50_ReID.yaml +++ b/ppcls/configs/Vehicle/ResNet50_ReID.yaml @@ -54,7 +54,6 @@ Optimizer: lr: name: Cosine learning_rate: 0.01 - last_epoch: -1 regularizer: name: 'L2' coeff: 0.0005 diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml index 788a49b43..cd66708fd 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml @@ -58,7 +58,6 @@ Optimizer: lr: name: Cosine learning_rate: 0.01 - last_epoch: -1 regularizer: name: 'L2' coeff: 0.0005 diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml index 14905148d..9e2a42749 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml @@ -57,7 +57,6 @@ Optimizer: lr: name: Cosine learning_rate: 0.001 - last_epoch: -1 regularizer: name: 'L2' coeff: 0.0005 diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml index a96ffb33b..5d4761c98 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml @@ -61,7 +61,6 @@ Optimizer: lr: name: Cosine learning_rate: 0.01 - last_epoch: -1 regularizer: name: 'L2' coeff: 0.0005 diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml index 712a3fca2..6c70c87a4 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml @@ -60,7 +60,6 @@ Optimizer: lr: name: Cosine learning_rate: 0.001 - last_epoch: -1 regularizer: name: 'L2' coeff: 0.0005 From bd68a9cb8b1b79ddc7f78c63b90dd7e138cc6985 Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Fri, 17 Sep 2021 07:42:08 +0000 Subject: [PATCH 22/81] update Vehicle yamls --- ppcls/configs/Vehicle/ResNet50.yaml | 2 +- ppcls/configs/Vehicle/ResNet50_ReID.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ppcls/configs/Vehicle/ResNet50.yaml b/ppcls/configs/Vehicle/ResNet50.yaml index 33b05ba50..6994789d5 100644 --- a/ppcls/configs/Vehicle/ResNet50.yaml +++ b/ppcls/configs/Vehicle/ResNet50.yaml @@ -2,7 +2,7 @@ Global: checkpoints: null pretrained_model: null - output_dir: "./output_vehicle_cls/" + output_dir: "./output/" device: "gpu" save_interval: 1 eval_during_train: True diff --git a/ppcls/configs/Vehicle/ResNet50_ReID.yaml b/ppcls/configs/Vehicle/ResNet50_ReID.yaml index 4e56e8dfb..ffe983966 100644 --- a/ppcls/configs/Vehicle/ResNet50_ReID.yaml +++ b/ppcls/configs/Vehicle/ResNet50_ReID.yaml @@ -2,7 +2,7 @@ Global: checkpoints: null pretrained_model: null - output_dir: "./output_vehicle_reid/" + output_dir: "./output/" device: "gpu" save_interval: 1 eval_during_train: True From 0bdb16af8b9a0f7e7f64ec77a8bd46c911446024 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Fri, 17 Sep 2021 18:08:35 +0800 Subject: [PATCH 23/81] add pk_sampler --- ppcls/data/dataloader/pk_sampler.py | 79 +++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 ppcls/data/dataloader/pk_sampler.py diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py new file mode 100644 index 000000000..93762ad7b --- /dev/null +++ b/ppcls/data/dataloader/pk_sampler.py @@ -0,0 +1,79 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from collections import defaultdict +import numpy as np +import random +from paddle.io import DistributedBatchSampler + +from ppcls.utils import logger + + +class PKSampler(DistributedBatchSampler): + """ + First, randomly sample P identities. + Then for each identity randomly sample K instances. + Therefore batch size is P*K, and the sampler called PKSampler. + Args: + dataset (paddle.io.Dataset): list of (img_path, pid, cam_id). + sample_per_id(int): number of instances per identity in a batch. + batch_size (int): number of examples in a batch. + shuffle(bool): whether to shuffle indices order before generating + batch indices. Default False. + """ + + def __init__(self, + dataset, + batch_size, + sample_per_id, + shuffle=True, + drop_last=True): + super(PKSampler, self).__init__( + dataset, batch_size, shuffle=shuffle, drop_last=drop_last) + assert batch_size % sample_per_id == 0, \ + "PKSampler configs error, Sample_per_id must be a divisor of batch_size." + assert hasattr(self.dataset, + "labels"), "Dataset must have labels attribute." + self.sample_per_id = sample_per_id + self.label_dict = defaultdict(list) + for idx, label in enumerate(self.dataset.labels): + self.label_dict[label].append(idx) + self.id_list = list(self.label_dict) + + def __iter__(self): + if self.shuffle: + np.random.RandomState(self.epoch).shuffle(self.id_list) + id_list = self.id_list[self.local_rank * len(self):(self.local_rank + 1 + ) * len(self)] + id_per_batch = self.batch_size / self.sample_per_id + for i in range(len(self)): + batch_index = [] + for label_id in id_list[i * id_per_batch:(i + 1) * id_per_batch]: + idx_label_list = self.label_dict[label_id] + if self.sample_per_id <= len(idx_label_list): + batch_index.extend( + np.random.choice( + idx_label_list, + size=self.sample_per_id, + replace=False)) + else: + batch_index.extend( + np.random.choice( + idx_label_list, + size=self.sample_per_id, + replace=True)) + if not self.drop_last or len(batch_index) == self.batch_size: + yield batch_index From 94433634c018e0196b5d05ce911f1bd75a1f545c Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Tue, 24 Aug 2021 07:52:52 +0000 Subject: [PATCH 24/81] fix: fix Linear & support warmup start lr & support Cosine eta_min Support setting warmup start lr and eta_min in Cosine. Fix bug that Linear can not decay to end_lr when setting warmup. --- ppcls/optimizer/learning_rate.py | 66 +++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/ppcls/optimizer/learning_rate.py b/ppcls/optimizer/learning_rate.py index 1e74ae9d4..ea938b123 100644 --- a/ppcls/optimizer/learning_rate.py +++ b/ppcls/optimizer/learning_rate.py @@ -26,6 +26,8 @@ class Linear(object): epochs(int): The decay step size. It determines the decay cycle. end_lr(float, optional): The minimum final learning rate. Default: 0.0001. power(float, optional): Power of polynomial. Default: 1.0. + warmup_epoch(int): The epoch numbers for LinearWarmup. Default: 0. + warmup_start_lr(float): Initial learning rate of warm up. Default: 0.0. last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. """ @@ -36,28 +38,30 @@ class Linear(object): end_lr=0.0, power=1.0, warmup_epoch=0, + warmup_start_lr=0.0, last_epoch=-1, **kwargs): super(Linear, self).__init__() self.learning_rate = learning_rate - self.epochs = epochs * step_each_epoch + self.steps = (epochs - warmup_epoch) * step_each_epoch self.end_lr = end_lr self.power = power self.last_epoch = last_epoch - self.warmup_epoch = round(warmup_epoch * step_each_epoch) + self.warmup_steps = round(warmup_epoch * step_each_epoch) + self.warmup_start_lr = warmup_start_lr def __call__(self): learning_rate = lr.PolynomialDecay( learning_rate=self.learning_rate, - decay_steps=self.epochs, + decay_steps=self.steps, end_lr=self.end_lr, power=self.power, last_epoch=self.last_epoch) - if self.warmup_epoch > 0: + if self.warmup_steps > 0: learning_rate = lr.LinearWarmup( learning_rate=learning_rate, - warmup_steps=self.warmup_epoch, - start_lr=0.0, + warmup_steps=self.warmup_steps, + start_lr=self.warmup_start_lr, end_lr=self.learning_rate, last_epoch=self.last_epoch) return learning_rate @@ -71,6 +75,9 @@ class Cosine(object): lr(float): initial learning rate step_each_epoch(int): steps each epoch epochs(int): total training epochs + eta_min(float): Minimum learning rate. Default: 0.0. + warmup_epoch(int): The epoch numbers for LinearWarmup. Default: 0. + warmup_start_lr(float): Initial learning rate of warm up. Default: 0.0. last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. """ @@ -78,25 +85,30 @@ class Cosine(object): learning_rate, step_each_epoch, epochs, + eta_min=0.0, warmup_epoch=0, + warmup_start_lr=0.0, last_epoch=-1, **kwargs): super(Cosine, self).__init__() self.learning_rate = learning_rate - self.T_max = step_each_epoch * epochs + self.T_max = (epochs - warmup_epoch) * step_each_epoch + self.eta_min = eta_min self.last_epoch = last_epoch - self.warmup_epoch = round(warmup_epoch * step_each_epoch) + self.warmup_steps = round(warmup_epoch * step_each_epoch) + self.warmup_start_lr = warmup_start_lr def __call__(self): learning_rate = lr.CosineAnnealingDecay( learning_rate=self.learning_rate, T_max=self.T_max, + eta_min=self.eta_min, last_epoch=self.last_epoch) - if self.warmup_epoch > 0: + if self.warmup_steps > 0: learning_rate = lr.LinearWarmup( learning_rate=learning_rate, - warmup_steps=self.warmup_epoch, - start_lr=0.0, + warmup_steps=self.warmup_steps, + start_lr=self.warmup_start_lr, end_lr=self.learning_rate, last_epoch=self.last_epoch) return learning_rate @@ -111,6 +123,8 @@ class Step(object): step_size (int): the interval to update. gamma (float, optional): The Ratio that the learning rate will be reduced. ``new_lr = origin_lr * gamma`` . It should be less than 1.0. Default: 0.1. + warmup_epoch(int): The epoch numbers for LinearWarmup. Default: 0. + warmup_start_lr(float): Initial learning rate of warm up. Default: 0.0. last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. """ @@ -120,6 +134,7 @@ class Step(object): step_each_epoch, gamma, warmup_epoch=0, + warmup_start_lr=0.0, last_epoch=-1, **kwargs): super(Step, self).__init__() @@ -127,7 +142,8 @@ class Step(object): self.learning_rate = learning_rate self.gamma = gamma self.last_epoch = last_epoch - self.warmup_epoch = round(warmup_epoch * step_each_epoch) + self.warmup_steps = round(warmup_epoch * step_each_epoch) + self.warmup_start_lr = warmup_start_lr def __call__(self): learning_rate = lr.StepDecay( @@ -135,11 +151,11 @@ class Step(object): step_size=self.step_size, gamma=self.gamma, last_epoch=self.last_epoch) - if self.warmup_epoch > 0: + if self.warmup_steps > 0: learning_rate = lr.LinearWarmup( learning_rate=learning_rate, - warmup_steps=self.warmup_epoch, - start_lr=0.0, + warmup_steps=self.warmup_steps, + start_lr=self.warmup_start_lr, end_lr=self.learning_rate, last_epoch=self.last_epoch) return learning_rate @@ -152,6 +168,8 @@ class Piecewise(object): boundaries(list): A list of steps numbers. The type of element in the list is python int. values(list): A list of learning rate values that will be picked during different epoch boundaries. The type of element in the list is python float. + warmup_epoch(int): The epoch numbers for LinearWarmup. Default: 0. + warmup_start_lr(float): Initial learning rate of warm up. Default: 0.0. last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. """ @@ -160,24 +178,26 @@ class Piecewise(object): decay_epochs, values, warmup_epoch=0, + warmup_start_lr=0.0, last_epoch=-1, **kwargs): super(Piecewise, self).__init__() self.boundaries = [step_each_epoch * e for e in decay_epochs] self.values = values self.last_epoch = last_epoch - self.warmup_epoch = round(warmup_epoch * step_each_epoch) + self.warmup_steps = round(warmup_epoch * step_each_epoch) + self.warmup_start_lr = warmup_start_lr def __call__(self): learning_rate = lr.PiecewiseDecay( boundaries=self.boundaries, values=self.values, last_epoch=self.last_epoch) - if self.warmup_epoch > 0: + if self.warmup_steps > 0: learning_rate = lr.LinearWarmup( learning_rate=learning_rate, - warmup_steps=self.warmup_epoch, - start_lr=0.0, + warmup_steps=self.warmup_steps, + start_lr=self.warmup_start_lr, end_lr=self.values[0], last_epoch=self.last_epoch) return learning_rate @@ -186,7 +206,7 @@ class Piecewise(object): class MultiStepDecay(LRScheduler): """ Update the learning rate by ``gamma`` once ``epoch`` reaches one of the milestones. - The algorithm can be described as the code below. + The algorithm can be described as the code below. .. code-block:: text learning_rate = 0.5 milestones = [30, 50] @@ -200,15 +220,15 @@ class MultiStepDecay(LRScheduler): Args: learning_rate (float): The initial learning rate. It is a python float number. milestones (tuple|list): List or tuple of each boundaries. Must be increasing. - gamma (float, optional): The Ratio that the learning rate will be reduced. ``new_lr = origin_lr * gamma`` . + gamma (float, optional): The Ratio that the learning rate will be reduced. ``new_lr = origin_lr * gamma`` . It should be less than 1.0. Default: 0.1. last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. verbose (bool, optional): If ``True``, prints a message to stdout for each update. Default: ``False`` . - + Returns: ``MultiStepDecay`` instance to schedule learning rate. Examples: - + .. code-block:: python import paddle import numpy as np From 1f8cfbd69de5a09bf3a3212d1b8a44d0037b22b4 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Mon, 23 Aug 2021 16:36:46 +0000 Subject: [PATCH 25/81] fix: fix augmentation Fix RandomErasing, RandAugment to be consistent with Timm and compatible with earlier PaddleClas. Add ColorJitter implemented by PaddleVision and TimmAutoAugment borrowed from Timm Lib. --- ppcls/data/preprocess/__init__.py | 41 +- ppcls/data/preprocess/ops/operators.py | 18 + ppcls/data/preprocess/ops/random_erasing.py | 67 +- ppcls/data/preprocess/ops/timm_autoaugment.py | 879 ++++++++++++++++++ 4 files changed, 972 insertions(+), 33 deletions(-) create mode 100644 ppcls/data/preprocess/ops/timm_autoaugment.py diff --git a/ppcls/data/preprocess/__init__.py b/ppcls/data/preprocess/__init__.py index 12bb34b2d..075ee8927 100644 --- a/ppcls/data/preprocess/__init__.py +++ b/ppcls/data/preprocess/__init__.py @@ -14,6 +14,7 @@ from ppcls.data.preprocess.ops.autoaugment import ImageNetPolicy as RawImageNetPolicy from ppcls.data.preprocess.ops.randaugment import RandAugment as RawRandAugment +from ppcls.data.preprocess.ops.timm_autoaugment import RawTimmAutoAugment from ppcls.data.preprocess.ops.cutout import Cutout from ppcls.data.preprocess.ops.hide_and_seek import HideAndSeek @@ -31,7 +32,6 @@ from ppcls.data.preprocess.ops.operators import AugMix from ppcls.data.preprocess.batch_ops.batch_operators import MixupOperator, CutmixOperator, OpSampler, FmixOperator -import six import numpy as np from PIL import Image @@ -47,20 +47,14 @@ class AutoAugment(RawImageNetPolicy): """ ImageNetPolicy wrapper to auto fit different img types """ def __init__(self, *args, **kwargs): - if six.PY2: - super(AutoAugment, self).__init__(*args, **kwargs) - else: - super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __call__(self, img): if not isinstance(img, Image.Image): img = np.ascontiguousarray(img) img = Image.fromarray(img) - if six.PY2: - img = super(AutoAugment, self).__call__(img) - else: - img = super().__call__(img) + img = super().__call__(img) if isinstance(img, Image.Image): img = np.asarray(img) @@ -72,20 +66,33 @@ class RandAugment(RawRandAugment): """ RandAugment wrapper to auto fit different img types """ def __init__(self, *args, **kwargs): - if six.PY2: - super(RandAugment, self).__init__(*args, **kwargs) - else: - super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __call__(self, img): if not isinstance(img, Image.Image): img = np.ascontiguousarray(img) img = Image.fromarray(img) - if six.PY2: - img = super(RandAugment, self).__call__(img) - else: - img = super().__call__(img) + img = super().__call__(img) + + if isinstance(img, Image.Image): + img = np.asarray(img) + + return img + + +class TimmAutoAugment(RawTimmAutoAugment): + """ TimmAutoAugment wrapper to auto fit different img tyeps. """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __call__(self, img): + if not isinstance(img, Image.Image): + img = np.ascontiguousarray(img) + img = Image.fromarray(img) + + img = super().__call__(img) if isinstance(img, Image.Image): img = np.asarray(img) diff --git a/ppcls/data/preprocess/ops/operators.py b/ppcls/data/preprocess/ops/operators.py index 77c9f7ac7..4418f5293 100644 --- a/ppcls/data/preprocess/ops/operators.py +++ b/ppcls/data/preprocess/ops/operators.py @@ -26,6 +26,7 @@ import random import cv2 import numpy as np from PIL import Image +from paddle.vision.transforms import ColorJitter as RawColorJitter from .autoaugment import ImageNetPolicy from .functional import augmentations @@ -363,3 +364,20 @@ class AugMix(object): mixed = (1 - m) * image + m * mix return mixed.astype(np.uint8) + + +class ColorJitter(RawColorJitter): + """ColorJitter. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __call__(self, img): + if not isinstance(img, Image.Image): + img = np.ascontiguousarray(img) + img = Image.fromarray(img) + img = super()._apply_image(img) + if isinstance(img, Image.Image): + img = np.asarray(img) + return img diff --git a/ppcls/data/preprocess/ops/random_erasing.py b/ppcls/data/preprocess/ops/random_erasing.py index b395d5205..f234abbba 100644 --- a/ppcls/data/preprocess/ops/random_erasing.py +++ b/ppcls/data/preprocess/ops/random_erasing.py @@ -12,7 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -#This code is based on https://github.com/zhunzhong07/Random-Erasing +#This code is adapted from https://github.com/zhunzhong07/Random-Erasing, and refer to Timm. + +from functools import partial import math import random @@ -20,36 +22,69 @@ import random import numpy as np +class Pixels(object): + def __init__(self, mode="const", mean=[0., 0., 0.]): + self._mode = mode + self._mean = mean + + def __call__(self, h=224, w=224, c=3): + if self._mode == "rand": + return np.random.normal(size=(1, 1, 3)) + elif self._mode == "pixel": + return np.random.normal(size=(h, w, c)) + elif self._mode == "const": + return self._mean + else: + raise Exception( + "Invalid mode in RandomErasing, only support \"const\", \"rand\", \"pixel\"" + ) + + class RandomErasing(object): - def __init__(self, EPSILON=0.5, sl=0.02, sh=0.4, r1=0.3, - mean=[0., 0., 0.]): - self.EPSILON = EPSILON - self.mean = mean - self.sl = sl - self.sh = sh - self.r1 = r1 + """RandomErasing. + """ + + def __init__(self, + EPSILON=0.5, + sl=0.02, + sh=0.4, + r1=0.3, + mean=[0., 0., 0.], + attempt=100, + use_log_aspect=False, + mode='const'): + self.EPSILON = eval(EPSILON) if isinstance(EPSILON, str) else EPSILON + self.sl = eval(sl) if isinstance(sl, str) else sl + self.sh = eval(sh) if isinstance(sh, str) else sh + r1 = eval(r1) if isinstance(r1, str) else r1 + self.r1 = (math.log(r1), math.log(1 / r1)) if use_log_aspect else ( + r1, 1 / r1) + self.use_log_aspect = use_log_aspect + self.attempt = attempt + self.get_pixels = Pixels(mode, mean) def __call__(self, img): - if random.uniform(0, 1) > self.EPSILON: + if random.random() > self.EPSILON: return img - for _ in range(100): + for _ in range(self.attempt): area = img.shape[0] * img.shape[1] target_area = random.uniform(self.sl, self.sh) * area - aspect_ratio = random.uniform(self.r1, 1 / self.r1) + aspect_ratio = random.uniform(*self.r1) + if self.use_log_aspect: + aspect_ratio = math.exp(aspect_ratio) h = int(round(math.sqrt(target_area * aspect_ratio))) w = int(round(math.sqrt(target_area / aspect_ratio))) if w < img.shape[1] and h < img.shape[0]: + pixels = self.get_pixels(h, w, img.shape[2]) x1 = random.randint(0, img.shape[0] - h) y1 = random.randint(0, img.shape[1] - w) - if img.shape[0] == 3: - img[x1:x1 + h, y1:y1 + w, 0] = self.mean[0] - img[x1:x1 + h, y1:y1 + w, 1] = self.mean[1] - img[x1:x1 + h, y1:y1 + w, 2] = self.mean[2] + if img.shape[2] == 3: + img[x1:x1 + h, y1:y1 + w, :] = pixels else: - img[0, x1:x1 + h, y1:y1 + w] = self.mean[1] + img[x1:x1 + h, y1:y1 + w, 0] = pixels[0] return img return img diff --git a/ppcls/data/preprocess/ops/timm_autoaugment.py b/ppcls/data/preprocess/ops/timm_autoaugment.py new file mode 100644 index 000000000..2c9b057a2 --- /dev/null +++ b/ppcls/data/preprocess/ops/timm_autoaugment.py @@ -0,0 +1,879 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code implements is borrowed from Timm: https://github.com/rwightman/pytorch-image-models. +hacked together by / Copyright 2020 Ross Wightman +""" + +import random +import math +import re +from PIL import Image, ImageOps, ImageEnhance, ImageChops +import PIL +import numpy as np + +IMAGENET_DEFAULT_MEAN = (0.485, 0.456, 0.406) + +_PIL_VER = tuple([int(x) for x in PIL.__version__.split('.')[:2]]) + +_FILL = (128, 128, 128) + +# This signifies the max integer that the controller RNN could predict for the +# augmentation scheme. +_MAX_LEVEL = 10. + +_HPARAMS_DEFAULT = dict( + translate_const=250, + img_mean=_FILL, ) + +_RANDOM_INTERPOLATION = (Image.BILINEAR, Image.BICUBIC) + + +def _pil_interp(method): + if method == 'bicubic': + return Image.BICUBIC + elif method == 'lanczos': + return Image.LANCZOS + elif method == 'hamming': + return Image.HAMMING + else: + # default bilinear, do we want to allow nearest? + return Image.BILINEAR + + +def _interpolation(kwargs): + interpolation = kwargs.pop('resample', Image.BILINEAR) + if isinstance(interpolation, (list, tuple)): + return random.choice(interpolation) + else: + return interpolation + + +def _check_args_tf(kwargs): + if 'fillcolor' in kwargs and _PIL_VER < (5, 0): + kwargs.pop('fillcolor') + kwargs['resample'] = _interpolation(kwargs) + + +def shear_x(img, factor, **kwargs): + _check_args_tf(kwargs) + return img.transform(img.size, Image.AFFINE, (1, factor, 0, 0, 1, 0), + **kwargs) + + +def shear_y(img, factor, **kwargs): + _check_args_tf(kwargs) + return img.transform(img.size, Image.AFFINE, (1, 0, 0, factor, 1, 0), + **kwargs) + + +def translate_x_rel(img, pct, **kwargs): + pixels = pct * img.size[0] + _check_args_tf(kwargs) + return img.transform(img.size, Image.AFFINE, (1, 0, pixels, 0, 1, 0), + **kwargs) + + +def translate_y_rel(img, pct, **kwargs): + pixels = pct * img.size[1] + _check_args_tf(kwargs) + return img.transform(img.size, Image.AFFINE, (1, 0, 0, 0, 1, pixels), + **kwargs) + + +def translate_x_abs(img, pixels, **kwargs): + _check_args_tf(kwargs) + return img.transform(img.size, Image.AFFINE, (1, 0, pixels, 0, 1, 0), + **kwargs) + + +def translate_y_abs(img, pixels, **kwargs): + _check_args_tf(kwargs) + return img.transform(img.size, Image.AFFINE, (1, 0, 0, 0, 1, pixels), + **kwargs) + + +def rotate(img, degrees, **kwargs): + _check_args_tf(kwargs) + if _PIL_VER >= (5, 2): + return img.rotate(degrees, **kwargs) + elif _PIL_VER >= (5, 0): + w, h = img.size + post_trans = (0, 0) + rotn_center = (w / 2.0, h / 2.0) + angle = -math.radians(degrees) + matrix = [ + round(math.cos(angle), 15), + round(math.sin(angle), 15), + 0.0, + round(-math.sin(angle), 15), + round(math.cos(angle), 15), + 0.0, + ] + + def transform(x, y, matrix): + (a, b, c, d, e, f) = matrix + return a * x + b * y + c, d * x + e * y + f + + matrix[2], matrix[5] = transform(-rotn_center[0] - post_trans[0], + -rotn_center[1] - post_trans[1], + matrix) + matrix[2] += rotn_center[0] + matrix[5] += rotn_center[1] + return img.transform(img.size, Image.AFFINE, matrix, **kwargs) + else: + return img.rotate(degrees, resample=kwargs['resample']) + + +def auto_contrast(img, **__): + return ImageOps.autocontrast(img) + + +def invert(img, **__): + return ImageOps.invert(img) + + +def equalize(img, **__): + return ImageOps.equalize(img) + + +def solarize(img, thresh, **__): + return ImageOps.solarize(img, thresh) + + +def solarize_add(img, add, thresh=128, **__): + lut = [] + for i in range(256): + if i < thresh: + lut.append(min(255, i + add)) + else: + lut.append(i) + if img.mode in ("L", "RGB"): + if img.mode == "RGB" and len(lut) == 256: + lut = lut + lut + lut + return img.point(lut) + else: + return img + + +def posterize(img, bits_to_keep, **__): + if bits_to_keep >= 8: + return img + return ImageOps.posterize(img, bits_to_keep) + + +def contrast(img, factor, **__): + return ImageEnhance.Contrast(img).enhance(factor) + + +def color(img, factor, **__): + return ImageEnhance.Color(img).enhance(factor) + + +def brightness(img, factor, **__): + return ImageEnhance.Brightness(img).enhance(factor) + + +def sharpness(img, factor, **__): + return ImageEnhance.Sharpness(img).enhance(factor) + + +def _randomly_negate(v): + """With 50% prob, negate the value""" + return -v if random.random() > 0.5 else v + + +def _rotate_level_to_arg(level, _hparams): + # range [-30, 30] + level = (level / _MAX_LEVEL) * 30. + level = _randomly_negate(level) + return level, + + +def _enhance_level_to_arg(level, _hparams): + # range [0.1, 1.9] + return (level / _MAX_LEVEL) * 1.8 + 0.1, + + +def _enhance_increasing_level_to_arg(level, _hparams): + # the 'no change' level is 1.0, moving away from that towards 0. or 2.0 increases the enhancement blend + # range [0.1, 1.9] + level = (level / _MAX_LEVEL) * .9 + level = 1.0 + _randomly_negate(level) + return level, + + +def _shear_level_to_arg(level, _hparams): + # range [-0.3, 0.3] + level = (level / _MAX_LEVEL) * 0.3 + level = _randomly_negate(level) + return level, + + +def _translate_abs_level_to_arg(level, hparams): + translate_const = hparams['translate_const'] + level = (level / _MAX_LEVEL) * float(translate_const) + level = _randomly_negate(level) + return level, + + +def _translate_rel_level_to_arg(level, hparams): + # default range [-0.45, 0.45] + translate_pct = hparams.get('translate_pct', 0.45) + level = (level / _MAX_LEVEL) * translate_pct + level = _randomly_negate(level) + return level, + + +def _posterize_level_to_arg(level, _hparams): + # As per Tensorflow TPU EfficientNet impl + # range [0, 4], 'keep 0 up to 4 MSB of original image' + # intensity/severity of augmentation decreases with level + return int((level / _MAX_LEVEL) * 4), + + +def _posterize_increasing_level_to_arg(level, hparams): + # As per Tensorflow models research and UDA impl + # range [4, 0], 'keep 4 down to 0 MSB of original image', + # intensity/severity of augmentation increases with level + return 4 - _posterize_level_to_arg(level, hparams)[0], + + +def _posterize_original_level_to_arg(level, _hparams): + # As per original AutoAugment paper description + # range [4, 8], 'keep 4 up to 8 MSB of image' + # intensity/severity of augmentation decreases with level + return int((level / _MAX_LEVEL) * 4) + 4, + + +def _solarize_level_to_arg(level, _hparams): + # range [0, 256] + # intensity/severity of augmentation decreases with level + return int((level / _MAX_LEVEL) * 256), + + +def _solarize_increasing_level_to_arg(level, _hparams): + # range [0, 256] + # intensity/severity of augmentation increases with level + return 256 - _solarize_level_to_arg(level, _hparams)[0], + + +def _solarize_add_level_to_arg(level, _hparams): + # range [0, 110] + return int((level / _MAX_LEVEL) * 110), + + +LEVEL_TO_ARG = { + 'AutoContrast': None, + 'Equalize': None, + 'Invert': None, + 'Rotate': _rotate_level_to_arg, + # There are several variations of the posterize level scaling in various Tensorflow/Google repositories/papers + 'Posterize': _posterize_level_to_arg, + 'PosterizeIncreasing': _posterize_increasing_level_to_arg, + 'PosterizeOriginal': _posterize_original_level_to_arg, + 'Solarize': _solarize_level_to_arg, + 'SolarizeIncreasing': _solarize_increasing_level_to_arg, + 'SolarizeAdd': _solarize_add_level_to_arg, + 'Color': _enhance_level_to_arg, + 'ColorIncreasing': _enhance_increasing_level_to_arg, + 'Contrast': _enhance_level_to_arg, + 'ContrastIncreasing': _enhance_increasing_level_to_arg, + 'Brightness': _enhance_level_to_arg, + 'BrightnessIncreasing': _enhance_increasing_level_to_arg, + 'Sharpness': _enhance_level_to_arg, + 'SharpnessIncreasing': _enhance_increasing_level_to_arg, + 'ShearX': _shear_level_to_arg, + 'ShearY': _shear_level_to_arg, + 'TranslateX': _translate_abs_level_to_arg, + 'TranslateY': _translate_abs_level_to_arg, + 'TranslateXRel': _translate_rel_level_to_arg, + 'TranslateYRel': _translate_rel_level_to_arg, +} + +NAME_TO_OP = { + 'AutoContrast': auto_contrast, + 'Equalize': equalize, + 'Invert': invert, + 'Rotate': rotate, + 'Posterize': posterize, + 'PosterizeIncreasing': posterize, + 'PosterizeOriginal': posterize, + 'Solarize': solarize, + 'SolarizeIncreasing': solarize, + 'SolarizeAdd': solarize_add, + 'Color': color, + 'ColorIncreasing': color, + 'Contrast': contrast, + 'ContrastIncreasing': contrast, + 'Brightness': brightness, + 'BrightnessIncreasing': brightness, + 'Sharpness': sharpness, + 'SharpnessIncreasing': sharpness, + 'ShearX': shear_x, + 'ShearY': shear_y, + 'TranslateX': translate_x_abs, + 'TranslateY': translate_y_abs, + 'TranslateXRel': translate_x_rel, + 'TranslateYRel': translate_y_rel, +} + + +class AugmentOp(object): + def __init__(self, name, prob=0.5, magnitude=10, hparams=None): + hparams = hparams or _HPARAMS_DEFAULT + self.aug_fn = NAME_TO_OP[name] + self.level_fn = LEVEL_TO_ARG[name] + self.prob = prob + self.magnitude = magnitude + self.hparams = hparams.copy() + self.kwargs = dict( + fillcolor=hparams['img_mean'] if 'img_mean' in hparams else _FILL, + resample=hparams['interpolation'] + if 'interpolation' in hparams else _RANDOM_INTERPOLATION, ) + + # If magnitude_std is > 0, we introduce some randomness + # in the usually fixed policy and sample magnitude from a normal distribution + # with mean `magnitude` and std-dev of `magnitude_std`. + # NOTE This is my own hack, being tested, not in papers or reference impls. + self.magnitude_std = self.hparams.get('magnitude_std', 0) + + def __call__(self, img): + if self.prob < 1.0 and random.random() > self.prob: + return img + magnitude = self.magnitude + if self.magnitude_std and self.magnitude_std > 0: + magnitude = random.gauss(magnitude, self.magnitude_std) + magnitude = min(_MAX_LEVEL, max(0, magnitude)) # clip to valid range + level_args = self.level_fn( + magnitude, self.hparams) if self.level_fn is not None else tuple() + return self.aug_fn(img, *level_args, **self.kwargs) + + +def auto_augment_policy_v0(hparams): + # ImageNet v0 policy from TPU EfficientNet impl, cannot find a paper reference. + policy = [ + [('Equalize', 0.8, 1), ('ShearY', 0.8, 4)], + [('Color', 0.4, 9), ('Equalize', 0.6, 3)], + [('Color', 0.4, 1), ('Rotate', 0.6, 8)], + [('Solarize', 0.8, 3), ('Equalize', 0.4, 7)], + [('Solarize', 0.4, 2), ('Solarize', 0.6, 2)], + [('Color', 0.2, 0), ('Equalize', 0.8, 8)], + [('Equalize', 0.4, 8), ('SolarizeAdd', 0.8, 3)], + [('ShearX', 0.2, 9), ('Rotate', 0.6, 8)], + [('Color', 0.6, 1), ('Equalize', 1.0, 2)], + [('Invert', 0.4, 9), ('Rotate', 0.6, 0)], + [('Equalize', 1.0, 9), ('ShearY', 0.6, 3)], + [('Color', 0.4, 7), ('Equalize', 0.6, 0)], + [('Posterize', 0.4, 6), ('AutoContrast', 0.4, 7)], + [('Solarize', 0.6, 8), ('Color', 0.6, 9)], + [('Solarize', 0.2, 4), ('Rotate', 0.8, 9)], + [('Rotate', 1.0, 7), ('TranslateYRel', 0.8, 9)], + [('ShearX', 0.0, 0), ('Solarize', 0.8, 4)], + [('ShearY', 0.8, 0), ('Color', 0.6, 4)], + [('Color', 1.0, 0), ('Rotate', 0.6, 2)], + [('Equalize', 0.8, 4), ('Equalize', 0.0, 8)], + [('Equalize', 1.0, 4), ('AutoContrast', 0.6, 2)], + [('ShearY', 0.4, 7), ('SolarizeAdd', 0.6, 7)], + [('Posterize', 0.8, 2), ('Solarize', 0.6, 10) + ], # This results in black image with Tpu posterize + [('Solarize', 0.6, 8), ('Equalize', 0.6, 1)], + [('Color', 0.8, 6), ('Rotate', 0.4, 5)], + ] + pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy] + return pc + + +def auto_augment_policy_v0r(hparams): + # ImageNet v0 policy from TPU EfficientNet impl, with variation of Posterize used + # in Google research implementation (number of bits discarded increases with magnitude) + policy = [ + [('Equalize', 0.8, 1), ('ShearY', 0.8, 4)], + [('Color', 0.4, 9), ('Equalize', 0.6, 3)], + [('Color', 0.4, 1), ('Rotate', 0.6, 8)], + [('Solarize', 0.8, 3), ('Equalize', 0.4, 7)], + [('Solarize', 0.4, 2), ('Solarize', 0.6, 2)], + [('Color', 0.2, 0), ('Equalize', 0.8, 8)], + [('Equalize', 0.4, 8), ('SolarizeAdd', 0.8, 3)], + [('ShearX', 0.2, 9), ('Rotate', 0.6, 8)], + [('Color', 0.6, 1), ('Equalize', 1.0, 2)], + [('Invert', 0.4, 9), ('Rotate', 0.6, 0)], + [('Equalize', 1.0, 9), ('ShearY', 0.6, 3)], + [('Color', 0.4, 7), ('Equalize', 0.6, 0)], + [('PosterizeIncreasing', 0.4, 6), ('AutoContrast', 0.4, 7)], + [('Solarize', 0.6, 8), ('Color', 0.6, 9)], + [('Solarize', 0.2, 4), ('Rotate', 0.8, 9)], + [('Rotate', 1.0, 7), ('TranslateYRel', 0.8, 9)], + [('ShearX', 0.0, 0), ('Solarize', 0.8, 4)], + [('ShearY', 0.8, 0), ('Color', 0.6, 4)], + [('Color', 1.0, 0), ('Rotate', 0.6, 2)], + [('Equalize', 0.8, 4), ('Equalize', 0.0, 8)], + [('Equalize', 1.0, 4), ('AutoContrast', 0.6, 2)], + [('ShearY', 0.4, 7), ('SolarizeAdd', 0.6, 7)], + [('PosterizeIncreasing', 0.8, 2), ('Solarize', 0.6, 10)], + [('Solarize', 0.6, 8), ('Equalize', 0.6, 1)], + [('Color', 0.8, 6), ('Rotate', 0.4, 5)], + ] + pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy] + return pc + + +def auto_augment_policy_original(hparams): + # ImageNet policy from https://arxiv.org/abs/1805.09501 + policy = [ + [('PosterizeOriginal', 0.4, 8), ('Rotate', 0.6, 9)], + [('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)], + [('Equalize', 0.8, 8), ('Equalize', 0.6, 3)], + [('PosterizeOriginal', 0.6, 7), ('PosterizeOriginal', 0.6, 6)], + [('Equalize', 0.4, 7), ('Solarize', 0.2, 4)], + [('Equalize', 0.4, 4), ('Rotate', 0.8, 8)], + [('Solarize', 0.6, 3), ('Equalize', 0.6, 7)], + [('PosterizeOriginal', 0.8, 5), ('Equalize', 1.0, 2)], + [('Rotate', 0.2, 3), ('Solarize', 0.6, 8)], + [('Equalize', 0.6, 8), ('PosterizeOriginal', 0.4, 6)], + [('Rotate', 0.8, 8), ('Color', 0.4, 0)], + [('Rotate', 0.4, 9), ('Equalize', 0.6, 2)], + [('Equalize', 0.0, 7), ('Equalize', 0.8, 8)], + [('Invert', 0.6, 4), ('Equalize', 1.0, 8)], + [('Color', 0.6, 4), ('Contrast', 1.0, 8)], + [('Rotate', 0.8, 8), ('Color', 1.0, 2)], + [('Color', 0.8, 8), ('Solarize', 0.8, 7)], + [('Sharpness', 0.4, 7), ('Invert', 0.6, 8)], + [('ShearX', 0.6, 5), ('Equalize', 1.0, 9)], + [('Color', 0.4, 0), ('Equalize', 0.6, 3)], + [('Equalize', 0.4, 7), ('Solarize', 0.2, 4)], + [('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)], + [('Invert', 0.6, 4), ('Equalize', 1.0, 8)], + [('Color', 0.6, 4), ('Contrast', 1.0, 8)], + [('Equalize', 0.8, 8), ('Equalize', 0.6, 3)], + ] + pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy] + return pc + + +def auto_augment_policy_originalr(hparams): + # ImageNet policy from https://arxiv.org/abs/1805.09501 with research posterize variation + policy = [ + [('PosterizeIncreasing', 0.4, 8), ('Rotate', 0.6, 9)], + [('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)], + [('Equalize', 0.8, 8), ('Equalize', 0.6, 3)], + [('PosterizeIncreasing', 0.6, 7), ('PosterizeIncreasing', 0.6, 6)], + [('Equalize', 0.4, 7), ('Solarize', 0.2, 4)], + [('Equalize', 0.4, 4), ('Rotate', 0.8, 8)], + [('Solarize', 0.6, 3), ('Equalize', 0.6, 7)], + [('PosterizeIncreasing', 0.8, 5), ('Equalize', 1.0, 2)], + [('Rotate', 0.2, 3), ('Solarize', 0.6, 8)], + [('Equalize', 0.6, 8), ('PosterizeIncreasing', 0.4, 6)], + [('Rotate', 0.8, 8), ('Color', 0.4, 0)], + [('Rotate', 0.4, 9), ('Equalize', 0.6, 2)], + [('Equalize', 0.0, 7), ('Equalize', 0.8, 8)], + [('Invert', 0.6, 4), ('Equalize', 1.0, 8)], + [('Color', 0.6, 4), ('Contrast', 1.0, 8)], + [('Rotate', 0.8, 8), ('Color', 1.0, 2)], + [('Color', 0.8, 8), ('Solarize', 0.8, 7)], + [('Sharpness', 0.4, 7), ('Invert', 0.6, 8)], + [('ShearX', 0.6, 5), ('Equalize', 1.0, 9)], + [('Color', 0.4, 0), ('Equalize', 0.6, 3)], + [('Equalize', 0.4, 7), ('Solarize', 0.2, 4)], + [('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)], + [('Invert', 0.6, 4), ('Equalize', 1.0, 8)], + [('Color', 0.6, 4), ('Contrast', 1.0, 8)], + [('Equalize', 0.8, 8), ('Equalize', 0.6, 3)], + ] + pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy] + return pc + + +def auto_augment_policy(name='v0', hparams=None): + hparams = hparams or _HPARAMS_DEFAULT + if name == 'original': + return auto_augment_policy_original(hparams) + elif name == 'originalr': + return auto_augment_policy_originalr(hparams) + elif name == 'v0': + return auto_augment_policy_v0(hparams) + elif name == 'v0r': + return auto_augment_policy_v0r(hparams) + else: + assert False, 'Unknown AA policy (%s)' % name + + +class AutoAugment(object): + def __init__(self, policy): + self.policy = policy + + def __call__(self, img): + sub_policy = random.choice(self.policy) + for op in sub_policy: + img = op(img) + return img + + +def auto_augment_transform(config_str, hparams): + """ + Create a AutoAugment transform + + :param config_str: String defining configuration of auto augmentation. Consists of multiple sections separated by + dashes ('-'). The first section defines the AutoAugment policy (one of 'v0', 'v0r', 'original', 'originalr'). + The remaining sections, not order sepecific determine + 'mstd' - float std deviation of magnitude noise applied + Ex 'original-mstd0.5' results in AutoAugment with original policy, magnitude_std 0.5 + + :param hparams: Other hparams (kwargs) for the AutoAugmentation scheme + + :return: A callable Transform Op + """ + config = config_str.split('-') + policy_name = config[0] + config = config[1:] + for c in config: + cs = re.split(r'(\d.*)', c) + if len(cs) < 2: + continue + key, val = cs[:2] + if key == 'mstd': + # noise param injected via hparams for now + hparams.setdefault('magnitude_std', float(val)) + else: + assert False, 'Unknown AutoAugment config section' + aa_policy = auto_augment_policy(policy_name, hparams=hparams) + return AutoAugment(aa_policy) + + +_RAND_TRANSFORMS = [ + 'AutoContrast', + 'Equalize', + 'Invert', + 'Rotate', + 'Posterize', + 'Solarize', + 'SolarizeAdd', + 'Color', + 'Contrast', + 'Brightness', + 'Sharpness', + 'ShearX', + 'ShearY', + 'TranslateXRel', + 'TranslateYRel', + #'Cutout' # NOTE I've implement this as random erasing separately +] + +_RAND_INCREASING_TRANSFORMS = [ + 'AutoContrast', + 'Equalize', + 'Invert', + 'Rotate', + 'PosterizeIncreasing', + 'SolarizeIncreasing', + 'SolarizeAdd', + 'ColorIncreasing', + 'ContrastIncreasing', + 'BrightnessIncreasing', + 'SharpnessIncreasing', + 'ShearX', + 'ShearY', + 'TranslateXRel', + 'TranslateYRel', + #'Cutout' # NOTE I've implement this as random erasing separately +] + +# These experimental weights are based loosely on the relative improvements mentioned in paper. +# They may not result in increased performance, but could likely be tuned to so. +_RAND_CHOICE_WEIGHTS_0 = { + 'Rotate': 0.3, + 'ShearX': 0.2, + 'ShearY': 0.2, + 'TranslateXRel': 0.1, + 'TranslateYRel': 0.1, + 'Color': .025, + 'Sharpness': 0.025, + 'AutoContrast': 0.025, + 'Solarize': .005, + 'SolarizeAdd': .005, + 'Contrast': .005, + 'Brightness': .005, + 'Equalize': .005, + 'Posterize': 0, + 'Invert': 0, +} + + +def _select_rand_weights(weight_idx=0, transforms=None): + transforms = transforms or _RAND_TRANSFORMS + assert weight_idx == 0 # only one set of weights currently + rand_weights = _RAND_CHOICE_WEIGHTS_0 + probs = [rand_weights[k] for k in transforms] + probs /= np.sum(probs) + return probs + + +def rand_augment_ops(magnitude=10, hparams=None, transforms=None): + hparams = hparams or _HPARAMS_DEFAULT + transforms = transforms or _RAND_TRANSFORMS + return [ + AugmentOp( + name, prob=0.5, magnitude=magnitude, hparams=hparams) + for name in transforms + ] + + +class RandAugment(object): + def __init__(self, ops, num_layers=2, choice_weights=None): + self.ops = ops + self.num_layers = num_layers + self.choice_weights = choice_weights + + def __call__(self, img): + # no replacement when using weighted choice + ops = np.random.choice( + self.ops, + self.num_layers, + replace=self.choice_weights is None, + p=self.choice_weights) + for op in ops: + img = op(img) + return img + + +def rand_augment_transform(config_str, hparams): + """ + Create a RandAugment transform + + :param config_str: String defining configuration of random augmentation. Consists of multiple sections separated by + dashes ('-'). The first section defines the specific variant of rand augment (currently only 'rand'). The remaining + sections, not order sepecific determine + 'm' - integer magnitude of rand augment + 'n' - integer num layers (number of transform ops selected per image) + 'w' - integer probabiliy weight index (index of a set of weights to influence choice of op) + 'mstd' - float std deviation of magnitude noise applied + 'inc' - integer (bool), use augmentations that increase in severity with magnitude (default: 0) + Ex 'rand-m9-n3-mstd0.5' results in RandAugment with magnitude 9, num_layers 3, magnitude_std 0.5 + 'rand-mstd1-w0' results in magnitude_std 1.0, weights 0, default magnitude of 10 and num_layers 2 + + :param hparams: Other hparams (kwargs) for the RandAugmentation scheme + + :return: A callable Transform Op + """ + magnitude = _MAX_LEVEL # default to _MAX_LEVEL for magnitude (currently 10) + num_layers = 2 # default to 2 ops per image + weight_idx = None # default to no probability weights for op choice + transforms = _RAND_TRANSFORMS + config = config_str.split('-') + assert config[0] == 'rand' + config = config[1:] + for c in config: + cs = re.split(r'(\d.*)', c) + if len(cs) < 2: + continue + key, val = cs[:2] + if key == 'mstd': + # noise param injected via hparams for now + hparams.setdefault('magnitude_std', float(val)) + elif key == 'inc': + if bool(val): + transforms = _RAND_INCREASING_TRANSFORMS + elif key == 'm': + magnitude = int(val) + elif key == 'n': + num_layers = int(val) + elif key == 'w': + weight_idx = int(val) + else: + assert False, 'Unknown RandAugment config section' + ra_ops = rand_augment_ops( + magnitude=magnitude, hparams=hparams, transforms=transforms) + choice_weights = None if weight_idx is None else _select_rand_weights( + weight_idx) + return RandAugment(ra_ops, num_layers, choice_weights=choice_weights) + + +_AUGMIX_TRANSFORMS = [ + 'AutoContrast', + 'ColorIncreasing', # not in paper + 'ContrastIncreasing', # not in paper + 'BrightnessIncreasing', # not in paper + 'SharpnessIncreasing', # not in paper + 'Equalize', + 'Rotate', + 'PosterizeIncreasing', + 'SolarizeIncreasing', + 'ShearX', + 'ShearY', + 'TranslateXRel', + 'TranslateYRel', +] + + +def augmix_ops(magnitude=10, hparams=None, transforms=None): + hparams = hparams or _HPARAMS_DEFAULT + transforms = transforms or _AUGMIX_TRANSFORMS + return [ + AugmentOp( + name, prob=1.0, magnitude=magnitude, hparams=hparams) + for name in transforms + ] + + +class AugMixAugment(object): + """ AugMix Transform + Adapted and improved from impl here: https://github.com/google-research/augmix/blob/master/imagenet.py + From paper: 'AugMix: A Simple Data Processing Method to Improve Robustness and Uncertainty - + https://arxiv.org/abs/1912.02781 + """ + + def __init__(self, ops, alpha=1., width=3, depth=-1, blended=False): + self.ops = ops + self.alpha = alpha + self.width = width + self.depth = depth + self.blended = blended # blended mode is faster but not well tested + + def _calc_blended_weights(self, ws, m): + ws = ws * m + cump = 1. + rws = [] + for w in ws[::-1]: + alpha = w / cump + cump *= (1 - alpha) + rws.append(alpha) + return np.array(rws[::-1], dtype=np.float32) + + def _apply_blended(self, img, mixing_weights, m): + # This is my first crack and implementing a slightly faster mixed augmentation. Instead + # of accumulating the mix for each chain in a Numpy array and then blending with original, + # it recomputes the blending coefficients and applies one PIL image blend per chain. + # TODO the results appear in the right ballpark but they differ by more than rounding. + img_orig = img.copy() + ws = self._calc_blended_weights(mixing_weights, m) + for w in ws: + depth = self.depth if self.depth > 0 else np.random.randint(1, 4) + ops = np.random.choice(self.ops, depth, replace=True) + img_aug = img_orig # no ops are in-place, deep copy not necessary + for op in ops: + img_aug = op(img_aug) + img = Image.blend(img, img_aug, w) + return img + + def _apply_basic(self, img, mixing_weights, m): + # This is a literal adaptation of the paper/official implementation without normalizations and + # PIL <-> Numpy conversions between every op. It is still quite CPU compute heavy compared to the + # typical augmentation transforms, could use a GPU / Kornia implementation. + img_shape = img.size[0], img.size[1], len(img.getbands()) + mixed = np.zeros(img_shape, dtype=np.float32) + for mw in mixing_weights: + depth = self.depth if self.depth > 0 else np.random.randint(1, 4) + ops = np.random.choice(self.ops, depth, replace=True) + img_aug = img # no ops are in-place, deep copy not necessary + for op in ops: + img_aug = op(img_aug) + mixed += mw * np.asarray(img_aug, dtype=np.float32) + np.clip(mixed, 0, 255., out=mixed) + mixed = Image.fromarray(mixed.astype(np.uint8)) + return Image.blend(img, mixed, m) + + def __call__(self, img): + mixing_weights = np.float32( + np.random.dirichlet([self.alpha] * self.width)) + m = np.float32(np.random.beta(self.alpha, self.alpha)) + if self.blended: + mixed = self._apply_blended(img, mixing_weights, m) + else: + mixed = self._apply_basic(img, mixing_weights, m) + return mixed + + +def augment_and_mix_transform(config_str, hparams): + """ Create AugMix transform + + :param config_str: String defining configuration of random augmentation. Consists of multiple sections separated by + dashes ('-'). The first section defines the specific variant of rand augment (currently only 'rand'). The remaining + sections, not order sepecific determine + 'm' - integer magnitude (severity) of augmentation mix (default: 3) + 'w' - integer width of augmentation chain (default: 3) + 'd' - integer depth of augmentation chain (-1 is random [1, 3], default: -1) + 'b' - integer (bool), blend each branch of chain into end result without a final blend, less CPU (default: 0) + 'mstd' - float std deviation of magnitude noise applied (default: 0) + Ex 'augmix-m5-w4-d2' results in AugMix with severity 5, chain width 4, chain depth 2 + + :param hparams: Other hparams (kwargs) for the Augmentation transforms + + :return: A callable Transform Op + """ + magnitude = 3 + width = 3 + depth = -1 + alpha = 1. + blended = False + config = config_str.split('-') + assert config[0] == 'augmix' + config = config[1:] + for c in config: + cs = re.split(r'(\d.*)', c) + if len(cs) < 2: + continue + key, val = cs[:2] + if key == 'mstd': + # noise param injected via hparams for now + hparams.setdefault('magnitude_std', float(val)) + elif key == 'm': + magnitude = int(val) + elif key == 'w': + width = int(val) + elif key == 'd': + depth = int(val) + elif key == 'a': + alpha = float(val) + elif key == 'b': + blended = bool(val) + else: + assert False, 'Unknown AugMix config section' + ops = augmix_ops(magnitude=magnitude, hparams=hparams) + return AugMixAugment( + ops, alpha=alpha, width=width, depth=depth, blended=blended) + + +class RawTimmAutoAugment(object): + """TimmAutoAugment API for PaddleClas.""" + + def __init__(self, + config_str="rand-m9-mstd0.5-inc1", + interpolation="bicubic", + img_size=224, + mean=IMAGENET_DEFAULT_MEAN): + if isinstance(img_size, (tuple, list)): + img_size_min = min(img_size) + else: + img_size_min = img_size + + aa_params = dict( + translate_const=int(img_size_min * 0.45), + img_mean=tuple([min(255, round(255 * x)) for x in mean]), ) + if interpolation and interpolation != 'random': + aa_params['interpolation'] = _pil_interp(interpolation) + if config_str.startswith('rand'): + self.augment_func = rand_augment_transform(config_str, aa_params) + elif config_str.startswith('augmix'): + aa_params['translate_pct'] = 0.3 + self.augment_func = augment_and_mix_transform(config_str, + aa_params) + elif config_str.startswith('auto'): + self.augment_func = auto_augment_transform(config_str, aa_params) + else: + raise Exception( + "ConfigError: The TimmAutoAugment Op only support RandAugment, AutoAugment, AugMix, and the config_str only starts with \"rand\", \"augmix\", \"auto\"." + ) + + def __call__(self, img): + return self.augment_func(img) From 2949dae9fb321173f1adb562f61cd050931760ae Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Wed, 8 Sep 2021 09:07:10 +0000 Subject: [PATCH 26/81] docs: update faq, test=document_fix --- docs/zh_CN/faq_series/faq_2021_s2.md | 163 +++++++++++++++++++-------- 1 file changed, 118 insertions(+), 45 deletions(-) diff --git a/docs/zh_CN/faq_series/faq_2021_s2.md b/docs/zh_CN/faq_series/faq_2021_s2.md index 4e2e0ab5c..852846082 100644 --- a/docs/zh_CN/faq_series/faq_2021_s2.md +++ b/docs/zh_CN/faq_series/faq_2021_s2.md @@ -7,7 +7,7 @@ * 图像分类、识别、检索领域大佬众多,模型和论文更新速度也很快,本文档回答主要依赖有限的项目实践,难免挂一漏万,如有遗漏和不足,也希望有识之士帮忙补充和修正,万分感谢。 ## 目录 -* [近期更新](#近期更新)(2021.08.11) +* [近期更新](#近期更新)(2021.09.08) * [精选](#精选) * [1. 理论篇](#1.理论篇) * [1.1 PaddleClas基础知识](#1.1PaddleClas基础知识) @@ -27,60 +27,69 @@ ## 近期更新 -#### Q2.6.2: 导出inference模型进行预测部署,准确率异常,为什么呢? -**A**: 该问题通常是由于在导出时未能正确加载模型参数导致的,首先检查模型导出时的日志,是否存在类似下述内容: +#### Q2.1.7: 在训练时,出现如下报错信息:`ERROR: Unexpected segmentation fault encountered in DataLoader workers.`,如何排查解决问题呢? +**A**:尝试将训练配置文件中的字段 `num_workers` 设置为 `0`;尝试将训练配置文件中的字段 `batch_size` 调小一些;检查数据集格式和配置文件中的数据集路径是否正确。 + +#### Q2.1.8: 如何在训练时使用 `Mixup` 和 `Cutmix` ? +**A**: +* `Mixup` 的使用方法请参考 [Mixup](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Mixup.yaml#L63-L65);`Cuxmix` 请参考 [Cuxmix](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Cutmix.yaml#L63-L65)。 + +* 在使用 `Mixup` 或 `Cutmix` 时,需要注意: + * 配置文件中的 `Loss.Tranin.CELoss` 需要修改为 `Loss.Tranin.MixCELoss`,可参考 [MixCELoss](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Cutmix.yaml#L23-L26); + * 使用 `Mixup` 或 `Cutmix` 做训练时无法计算训练的精度(Acc)指标,因此需要在配置文件中取消 `Metric.Train.TopkAcc` 字段,可参考 [Metric.Train.TopkAcc](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Cutmix.yaml#L125-L128)。 + +#### Q2.1.9: 训练配置yaml文件中,字段 `Global.pretrain_model` 和 `Global.checkpoints` 分别用于配置什么呢? +**A**: +* 当需要 `fine-tune` 时,可以通过字段 `Global.pretrain_model` 配置预训练模型权重文件的路径,预训练模型权重文件后缀名通常为 `.pdparams`; +* 在训练过程中,训练程序会自动保存每个epoch结束时的断点信息,包括优化器信息 `.pdopt` 和模型权重信息 `.pdparams`。在训练过程意外中断等情况下,需要恢复训练时,可以通过字段 `Global.checkpoints` 配置训练过程中保存的断点信息文件,例如通过配置 `checkpoints: ./output/ResNet18/epoch_18` 即可恢复18epoch训练结束时的断点信息,PaddleClas将自动加载 `epoch_18.pdopt` 和 `epoch_18.pdparams`,从19epoch继续训练。 + +#### Q2.6.3: 如何将模型转为 `ONNX` 格式? +**A**:Paddle支持两种转ONNX格式模型的方式,且依赖于 `paddle2onnx` 工具,首先需要安装 `paddle2onnx`: + +```shell +pip install paddle2onnx ``` -UserWarning: Skip loading for ***. *** is not found in the provided dict. -``` -如果存在,则说明模型权重未能加载成功,请进一步检查配置文件中的 `Global.pretrained_model` 字段,是否正确配置了模型权重文件的路径。模型权重文件后缀名通常为 `pdparams`,注意在配置该路径时无需填写文件后缀名。 -#### Q2.1.4: 数据预处理中,不想对输入数据进行裁剪,该如何设置?或者如何设置剪裁的尺寸。 -**A**: PaddleClas 支持的数据预处理算子可在这里查看:`ppcls/data/preprocess/__init__.py`,所有支持的算子均可在配置文件中进行配置,配置的算子名称需要和算子类名一致,参数与对应算子类的构造函数参数一致。如不需要对图像裁剪,则可去掉 `CropImage`、`RandCropImage`,使用 `ResizeImage` 替换即可,可通过其参数设置不同的resize方式, 使用 `size` 参数则直接将图像缩放至固定大小,使用`resize_short` 参数则会维持图像宽高比进行缩放。设置裁剪尺寸时,可通过 `CropImage` 算子的 `size` 参数,或 `RandCropImage` 算子的 `size` 参数。 +* 从 inference model 转为 ONNX 格式模型: -#### Q1.1.3: Momentum 优化器中的 momentum 参数是什么意思呢? -**A**: Momentum 优化器是在 SGD 优化器的基础上引入了“动量”的概念。在 SGD 优化器中,在 `t+1` 时刻,参数 `w` 的更新可表示为: -```latex -w_t+1 = w_t - lr * grad -``` -其中,`lr` 为学习率,`grad` 为此时参数 `w` 的梯度。在引入动量的概念后,参数 `w` 的更新可表示为: -```latex -v_t+1 = m * v_t + lr * grad -w_t+1 = w_t - v_t+1 -``` -其中,`m` 即为动量 `momentum`,表示累积动量的加权值,一般取 `0.9`,当取值小于 `1` 时,则越早期的梯度对当前的影响越小,例如,当动量参数 `m` 取 `0.9` 时,在 `t` 时刻,`t-5` 的梯度加权值为 `0.9 ^ 5 = 0.59049`,而 `t-2` 时刻的梯度加权值为 `0.9 ^ 2 = 0.81`。因此,太过“久远”的梯度信息对当前的参考意义很小,而“最近”的历史梯度信息对当前影响更大,这也是符合直觉的。 + 以动态图导出的 `combined` 格式 inference model(包含 `.pdmodel` 和 `.pdiparams` 两个文件)为例,使用以下命令进行模型格式转换: + ```shell + paddle2onnx --model_dir ${model_path} --model_filename ${model_path}/inference.pdmodel --params_filename ${model_path}/inference.pdiparams --save_file ${save_path}/model.onnx --enable_onnx_checker True + ``` + 上述命令中: + * `model_dir`:该参数下需要包含 `.pdmodel` 和 `.pdiparams` 两个文件; + * `model_filename`:该参数用于指定参数 `model_dir` 下的 `.pdmodel` 文件路径; + * `params_filename`:该参数用于指定参数 `model_dir` 下的 `.pdiparams` 文件路径; + * `save_file`:该参数用于指定转换后的模型保存目录路径。 -
- -
+ 关于静态图导出的非 `combined` 格式的 inference model(通常包含文件 `__model__` 和多个参数文件)转换模型格式,以及更多参数说明请参考 paddle2onnx 官方文档 [paddle2onnx](https://github.com/PaddlePaddle/Paddle2ONNX/blob/develop/README_zh.md#%E5%8F%82%E6%95%B0%E9%80%89%E9%A1%B9)。 -*该图来自 `https://blog.csdn.net/tsyccnh/article/details/76270707`* +* 直接从模型组网代码导出ONNX格式模型: -通过引入动量的概念,在参数更新时考虑了历史更新的影响,因此可以加快收敛速度,也改善了 `SGD` 优化器带来的损失(cost、loss)震荡问题。 + 以动态图模型组网代码为例,模型类为继承于 `paddle.nn.Layer` 的子类,代码如下所示: -#### Q1.1.4: PaddleClas 是否有 `Fixing the train-test resolution discrepancy` 这篇论文的实现呢? -**A**: 目前 PaddleClas 没有实现。如果需要,可以尝试自己修改代码。简单来说,该论文所提出的思想是使用较大分辨率作为输入,对已经训练好的模型最后的FC层进行fine-tune。具体操作上,首先在较低分辨率的数据集上对模型网络进行训练,完成训练后,对网络除最后的FC层外的其他层的权重设置参数 `stop_gradient=True`,然后使用较大分辨率的输入对网络进行fine-tune训练。 + ```python + import paddle + from paddle.static import InputSpec -#### Q1.6.2: PaddleClas 图像识别用于 Eval 的配置文件中,`Query` 和 `Gallery` 配置具体是用于做什么呢? -**A**: `Query` 与 `Gallery` 均为数据集配置,其中 `Gallery` 用于配置底库数据,`Query` 用于配置验证集。在进行 Eval 时,首先使用模型对 `Gallery` 底库数据进行前向计算特征向量,特征向量用于构建底库,然后模型对 `Query` 验证集中的数据进行前向计算特征向量,再与底库计算召回率等指标。 + class SimpleNet(paddle.nn.Layer): + def __init__(self): + pass + def forward(self, x): + pass -#### Q2.1.5: PaddlePaddle 安装后,使用报错,无法导入 paddle 下的任何模块(import paddle.xxx),是为什么呢? -**A**: 首先可以使用以下代码测试 Paddle 是否安装正确: -```python -import paddle -paddle.utils.install_check.run_check() -``` -正确安装时,通常会有如下提示: -``` -PaddlePaddle is installed successfully! Let's start deep learning with PaddlePaddle now. -``` -如未能安装成功,则会有相应问题的提示。 -另外,在同时安装CPU版本和GPU版本Paddle后,由于两个版本存在冲突,需要将两个版本全部卸载,然后重新安装所需要的版本。 + net = SimpleNet() + x_spec = InputSpec(shape=[None, 3, 224, 224], dtype='float32', name='x') + paddle.onnx.export(layer=net, path="./SimpleNet", input_spec=[x_spec]) + ``` + 其中: + * `InputSpec()` 函数用于描述模型输入的签名信息,包括输入数据的 `shape`、`type` 和 `name`(可省略); + * `paddle.onnx.export()` 函数需要指定模型组网对象 `net`,导出模型的保存路径 `save_path`,模型的输入数据描述 `input_spec`。 -#### Q2.1.6: 使用PaddleClas训练时,如何设置仅保存最优模型?不想保存中间模型。 -**A**: PaddleClas在训练过程中,会保存/更新以下三类模型: -1. 最新的模型(`latest.pdopt`, `latest.pdparams`,`latest.pdstates`),当训练意外中断时,可使用最新保存的模型恢复训练; -2. 最优的模型(`best_model.pdopt`,`best_model.pdparams`,`best_model.pdstates`); -3. 训练过程中,一个epoch结束时的断点(`epoch_xxx.pdopt`,`epoch_xxx.pdparams`,`epoch_xxx.pdstates`)。训练配置文件中 `Global.save_interval` 字段表示该模型的保存间隔。将该字段设置大于总epochs数,则不再保存中间断点模型。 + 需要注意,`paddlepaddle` 版本需大于 `2.0.0`。关于 `paddle.onnx.export()` 函数的更多参数说明请参考[paddle.onnx.export](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/onnx/export_cn.html#export)。 + +#### Q2.5.4: 在 build 检索底库时,参数 `pq_size` 应该如何设置? +**A**:`pq_size` 是PQ检索算法的参数。PQ检索算法可以简单理解为“分层”检索算法,`pq_size` 是每层的“容量”,因此该参数的设置会影响检索性能,不过,在底库总数据量不太大(小于10000张)的情况下,这个参数对性能的影响很小,因此对于大多数使用场景而言,在构建底库时无需修改该参数。关于PQ检索算法的更多内容,可以查看相关[论文](https://lear.inrialpes.fr/pubs/2011/JDS11/jegou_searching_with_quantization.pdf)。 ## 精选 @@ -204,6 +213,22 @@ PaddlePaddle is installed successfully! Let's start deep learning with PaddlePad 2. 最优的模型(`best_model.pdopt`,`best_model.pdparams`,`best_model.pdstates`); 3. 训练过程中,一个epoch结束时的断点(`epoch_xxx.pdopt`,`epoch_xxx.pdparams`,`epoch_xxx.pdstates`)。训练配置文件中 `Global.save_interval` 字段表示该模型的保存间隔。将该字段设置大于总epochs数,则不再保存中间断点模型。 +#### Q2.1.7: 在训练时,出现如下报错信息:`ERROR: Unexpected segmentation fault encountered in DataLoader workers.`,如何排查解决问题呢? +**A**:尝试将训练配置文件中的字段 `num_workers` 设置为 `0`;尝试将训练配置文件中的字段 `batch_size` 调小一些;检查数据集格式和配置文件中的数据集路径是否正确。 + +#### Q2.1.8: 如何在训练时使用 `Mixup` 和 `Cutmix` ? +**A**: +* `Mixup` 的使用方法请参考 [Mixup](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Mixup.yaml#L63-L65);`Cuxmix` 请参考 [Cuxmix](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Cutmix.yaml#L63-L65)。 + +* 在使用 `Mixup` 或 `Cutmix` 时,需要注意: + * 配置文件中的 `Loss.Tranin.CELoss` 需要修改为 `Loss.Tranin.MixCELoss`,可参考 [MixCELoss](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Cutmix.yaml#L23-L26); + * 使用 `Mixup` 或 `Cutmix` 做训练时无法计算训练的精度(Acc)指标,因此需要在配置文件中取消 `Metric.Train.TopkAcc` 字段,可参考 [Metric.Train.TopkAcc](https://github.com/PaddlePaddle/PaddleClas/blob/cf9fc9363877f919996954a63716acfb959619d0/ppcls/configs/ImageNet/DataAugment/ResNet50_Cutmix.yaml#L125-L128)。 + +#### Q2.1.9: 训练配置yaml文件中,字段 `Global.pretrain_model` 和 `Global.checkpoints` 分别用于配置什么呢? +**A**: +* 当需要 `fine-tune` 时,可以通过字段 `Global.pretrain_model` 配置预训练模型权重文件的路径,预训练模型权重文件后缀名通常为 `.pdparams`; +* 在训练过程中,训练程序会自动保存每个epoch结束时的断点信息,包括优化器信息 `.pdopt` 和模型权重信息 `.pdparams`。在训练过程意外中断等情况下,需要恢复训练时,可以通过字段 `Global.checkpoints` 配置训练过程中保存的断点信息文件,例如通过配置 `checkpoints: ./output/ResNet18/epoch_18` 即可恢复18epoch训练结束时的断点信息,PaddleClas将自动加载 `epoch_18.pdopt` 和 `epoch_18.pdparams`,从19epoch继续训练。 + ### 2.2 图像分类 @@ -255,6 +280,9 @@ PaddlePaddle is installed successfully! Let's start deep learning with PaddlePad #### Q2.5.3: Mac重新编译index.so时报错如下:clang: error: unsupported option '-fopenmp', 该如何处理? **A**:该问题已经解决。可以参照[文档](../../../develop/deploy/vector_search/README.md)重新编译 index.so。 +#### Q2.5.4: 在 build 检索底库时,参数 `pq_size` 应该如何设置? +**A**:`pq_size` 是PQ检索算法的参数。PQ检索算法可以简单理解为“分层”检索算法,`pq_size` 是每层的“容量”,因此该参数的设置会影响检索性能,不过,在底库总数据量不太大(小于10000张)的情况下,这个参数对性能的影响很小,因此对于大多数使用场景而言,在构建底库时无需修改该参数。关于PQ检索算法的更多内容,可以查看相关[论文](https://lear.inrialpes.fr/pubs/2011/JDS11/jegou_searching_with_quantization.pdf)。 + ### 2.6 模型预测部署 @@ -267,3 +295,48 @@ PaddlePaddle is installed successfully! Let's start deep learning with PaddlePad UserWarning: Skip loading for ***. *** is not found in the provided dict. ``` 如果存在,则说明模型权重未能加载成功,请进一步检查配置文件中的 `Global.pretrained_model` 字段,是否正确配置了模型权重文件的路径。模型权重文件后缀名通常为 `pdparams`,注意在配置该路径时无需填写文件后缀名。 + +#### Q2.6.3: 如何将模型转为 `ONNX` 格式? +**A**:Paddle支持两种转ONNX格式模型的方式,且依赖于 `paddle2onnx` 工具,首先需要安装 `paddle2onnx`: + +```shell +pip install paddle2onnx +``` + +* 从 inference model 转为 ONNX 格式模型: + + 以动态图导出的 `combined` 格式 inference model(包含 `.pdmodel` 和 `.pdiparams` 两个文件)为例,使用以下命令进行模型格式转换: + ```shell + paddle2onnx --model_dir ${model_path} --model_filename ${model_path}/inference.pdmodel --params_filename ${model_path}/inference.pdiparams --save_file ${save_path}/model.onnx --enable_onnx_checker True + ``` + 上述命令中: + * `model_dir`:该参数下需要包含 `.pdmodel` 和 `.pdiparams` 两个文件; + * `model_filename`:该参数用于指定参数 `model_dir` 下的 `.pdmodel` 文件路径; + * `params_filename`:该参数用于指定参数 `model_dir` 下的 `.pdiparams` 文件路径; + * `save_file`:该参数用于指定转换后的模型保存目录路径。 + + 关于静态图导出的非 `combined` 格式的 inference model(通常包含文件 `__model__` 和多个参数文件)转换模型格式,以及更多参数说明请参考 paddle2onnx 官方文档 [paddle2onnx](https://github.com/PaddlePaddle/Paddle2ONNX/blob/develop/README_zh.md#%E5%8F%82%E6%95%B0%E9%80%89%E9%A1%B9)。 + +* 直接从模型组网代码导出ONNX格式模型: + + 以动态图模型组网代码为例,模型类为继承于 `paddle.nn.Layer` 的子类,代码如下所示: + + ```python + import paddle + from paddle.static import InputSpec + + class SimpleNet(paddle.nn.Layer): + def __init__(self): + pass + def forward(self, x): + pass + + net = SimpleNet() + x_spec = InputSpec(shape=[None, 3, 224, 224], dtype='float32', name='x') + paddle.onnx.export(layer=net, path="./SimpleNet", input_spec=[x_spec]) + ``` + 其中: + * `InputSpec()` 函数用于描述模型输入的签名信息,包括输入数据的 `shape`、`type` 和 `name`(可省略); + * `paddle.onnx.export()` 函数需要指定模型组网对象 `net`,导出模型的保存路径 `save_path`,模型的输入数据描述 `input_spec`。 + + 需要注意,`paddlepaddle` 版本需大于 `2.0.0`。关于 `paddle.onnx.export()` 函数的更多参数说明请参考[paddle.onnx.export](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/onnx/export_cn.html#export)。 From f5b32a02ea4b62a15611cf70dac480424a233a86 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Fri, 17 Sep 2021 07:47:01 +0000 Subject: [PATCH 27/81] fix: fix the training configs of deit, swin, twins --- .../DeiT/DeiT_base_distilled_patch16_224.yaml | 62 +++++++++++------ .../DeiT/DeiT_base_distilled_patch16_384.yaml | 68 +++++++++++++------ .../ImageNet/DeiT/DeiT_base_patch16_224.yaml | 62 +++++++++++------ .../ImageNet/DeiT/DeiT_base_patch16_384.yaml | 68 +++++++++++++------ .../DeiT_small_distilled_patch16_224.yaml | 62 +++++++++++------ .../ImageNet/DeiT/DeiT_small_patch16_224.yaml | 62 +++++++++++------ .../DeiT/DeiT_tiny_distilled_patch16_224.yaml | 62 +++++++++++------ .../ImageNet/DeiT/DeiT_tiny_patch16_224.yaml | 62 +++++++++++------ ...nTransformer_base_patch4_window12_384.yaml | 61 ++++++++++++----- ...inTransformer_base_patch4_window7_224.yaml | 53 ++++++++++----- ...Transformer_large_patch4_window12_384.yaml | 61 ++++++++++++----- ...nTransformer_large_patch4_window7_224.yaml | 53 ++++++++++----- ...nTransformer_small_patch4_window7_224.yaml | 53 ++++++++++----- ...inTransformer_tiny_patch4_window7_224.yaml | 53 ++++++++++----- .../configs/ImageNet/Twins/alt_gvt_base.yaml | 61 ++++++++++++----- .../configs/ImageNet/Twins/alt_gvt_large.yaml | 61 ++++++++++++----- .../configs/ImageNet/Twins/alt_gvt_small.yaml | 61 ++++++++++++----- ppcls/configs/ImageNet/Twins/pcpvt_base.yaml | 61 ++++++++++++----- ppcls/configs/ImageNet/Twins/pcpvt_large.yaml | 61 ++++++++++++----- ppcls/configs/ImageNet/Twins/pcpvt_small.yaml | 61 ++++++++++++----- 20 files changed, 854 insertions(+), 354 deletions(-) diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_224.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_224.yaml index 951c3ad0e..fb3b9cca4 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_224.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -55,17 +57,38 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -83,6 +106,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -108,6 +133,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_384.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_384.yaml index 3b2436199..d30b5f7df 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_384.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_base_distilled_patch16_384.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -54,18 +56,39 @@ DataLoader: to_rgb: True channel_first: False - RandCropImage: - size: 384 + size: 384 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -82,7 +105,9 @@ DataLoader: to_rgb: True channel_first: False - ResizeImage: - resize_short: 426 + resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -107,7 +132,9 @@ Infer: to_rgb: True channel_first: False - ResizeImage: - resize_short: 426 + resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_224.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_224.yaml index 6d94b3758..8f4207e48 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_224.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -55,17 +57,38 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -83,6 +106,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -108,6 +133,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_384.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_384.yaml index a3f33d04e..00afe54b4 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_384.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_base_patch16_384.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -54,18 +56,39 @@ DataLoader: to_rgb: True channel_first: False - RandCropImage: - size: 384 + size: 384 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -82,7 +105,9 @@ DataLoader: to_rgb: True channel_first: False - ResizeImage: - resize_short: 426 + resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -107,7 +132,9 @@ Infer: to_rgb: True channel_first: False - ResizeImage: - resize_short: 426 + resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_small_distilled_patch16_224.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_small_distilled_patch16_224.yaml index d749681c1..c27bed406 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_small_distilled_patch16_224.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_small_distilled_patch16_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -55,17 +57,38 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -83,6 +106,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -108,6 +133,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_small_patch16_224.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_small_patch16_224.yaml index 0f01161f5..f53b8ec1f 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_small_patch16_224.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_small_patch16_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -55,17 +57,38 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -83,6 +106,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -108,6 +133,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_tiny_distilled_patch16_224.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_tiny_distilled_patch16_224.yaml index 34a1bde09..8b9e00fd6 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_tiny_distilled_patch16_224.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_tiny_distilled_patch16_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -55,17 +57,38 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -83,6 +106,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -108,6 +133,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/DeiT/DeiT_tiny_patch16_224.yaml b/ppcls/configs/ImageNet/DeiT/DeiT_tiny_patch16_224.yaml index 20c1d2f7d..242093db4 100644 --- a/ppcls/configs/ImageNet/DeiT/DeiT_tiny_patch16_224.yaml +++ b/ppcls/configs/ImageNet/DeiT/DeiT_tiny_patch16_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -22,25 +22,27 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 - Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token pos_embed dist_token + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 - + name: Cosine + learning_rate: 1e-3 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval DataLoader: @@ -55,17 +57,38 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: True loader: @@ -83,6 +106,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -92,7 +117,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 256 drop_last: False shuffle: False loader: @@ -108,6 +133,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -122,9 +149,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml index fa48840c8..af54e4aa7 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -24,24 +24,28 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: absolute_pos_embed relative_position_bias_table .bias norm + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 20 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -59,15 +63,35 @@ DataLoader: size: 384 - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -84,7 +108,9 @@ DataLoader: to_rgb: True channel_first: False - ResizeImage: - size: [384, 384] + resize_short: 438 + - CropImage: + size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] @@ -92,7 +118,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -107,7 +133,9 @@ Infer: to_rgb: True channel_first: False - ResizeImage: - size: [384, 384] + resize_short: 438 + - CropImage: + size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] @@ -120,9 +148,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml index aa05383dd..4b9baa1b6 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -24,24 +24,28 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: absolute_pos_embed relative_position_bias_table .bias norm + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 20 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -59,15 +63,35 @@ DataLoader: size: 224 - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -94,7 +118,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -124,9 +148,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml index c4eeaa2c6..58c9667e7 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -24,24 +24,28 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: absolute_pos_embed relative_position_bias_table .bias norm + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 20 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -59,15 +63,35 @@ DataLoader: size: 384 - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -84,7 +108,9 @@ DataLoader: to_rgb: True channel_first: False - ResizeImage: - size: [384, 384] + resize_short: 438 + - CropImage: + size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] @@ -92,7 +118,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -107,7 +133,9 @@ Infer: to_rgb: True channel_first: False - ResizeImage: - size: [384, 384] + resize_short: 438 + - CropImage: + size: 384 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] @@ -120,9 +148,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml index e6bfc460f..16f5a7dce 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -24,24 +24,28 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: absolute_pos_embed relative_position_bias_table .bias norm + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 20 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -59,15 +63,35 @@ DataLoader: size: 224 - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -94,7 +118,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -124,9 +148,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml index f3bcad069..88fc3da41 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -24,24 +24,28 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: absolute_pos_embed relative_position_bias_table .bias norm + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 20 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -59,15 +63,35 @@ DataLoader: size: 224 - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -94,7 +118,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -124,9 +148,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml index 390db2be0..ed9b4d505 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -24,24 +24,28 @@ Arch: # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: absolute_pos_embed relative_position_bias_table .bias norm + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 20 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -59,15 +63,35 @@ DataLoader: size: 224 - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -94,7 +118,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -124,9 +148,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/Twins/alt_gvt_base.yaml b/ppcls/configs/ImageNet/Twins/alt_gvt_base.yaml index 7c06a3ba1..17fd657d5 100644 --- a/ppcls/configs/ImageNet/Twins/alt_gvt_base.yaml +++ b/ppcls/configs/ImageNet/Twins/alt_gvt_base.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -20,28 +20,34 @@ Global: Arch: name: alt_gvt_base class_num: 1000 + drop_rate: 0.0 + drop_path_rate: 0.3 # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token proj.0.weight proj.1.weight proj.2.weight proj.3.weight pos_block + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -57,17 +63,39 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -85,6 +113,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -94,7 +124,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -110,6 +140,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -124,9 +156,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/Twins/alt_gvt_large.yaml b/ppcls/configs/ImageNet/Twins/alt_gvt_large.yaml index 4a56a8ee2..393a63878 100644 --- a/ppcls/configs/ImageNet/Twins/alt_gvt_large.yaml +++ b/ppcls/configs/ImageNet/Twins/alt_gvt_large.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -20,28 +20,34 @@ Global: Arch: name: alt_gvt_large class_num: 1000 + drop_rate: 0.0 + drop_path_rate: 0.5 # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token proj.0.weight proj.1.weight proj.2.weight proj.3.weight pos_block + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -57,17 +63,39 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -85,6 +113,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -94,7 +124,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -110,6 +140,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -124,9 +156,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/Twins/alt_gvt_small.yaml b/ppcls/configs/ImageNet/Twins/alt_gvt_small.yaml index 78cc263f2..b40f5183b 100644 --- a/ppcls/configs/ImageNet/Twins/alt_gvt_small.yaml +++ b/ppcls/configs/ImageNet/Twins/alt_gvt_small.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -20,28 +20,34 @@ Global: Arch: name: alt_gvt_small class_num: 1000 + drop_rate: 0.0 + drop_path_rate: 0.2 # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token proj.0.weight proj.1.weight proj.2.weight proj.3.weight pos_block + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -57,17 +63,39 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -85,6 +113,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -94,7 +124,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -110,6 +140,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -124,9 +156,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/Twins/pcpvt_base.yaml b/ppcls/configs/ImageNet/Twins/pcpvt_base.yaml index 100e87a9f..4c7c0991c 100644 --- a/ppcls/configs/ImageNet/Twins/pcpvt_base.yaml +++ b/ppcls/configs/ImageNet/Twins/pcpvt_base.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -20,28 +20,34 @@ Global: Arch: name: pcpvt_base class_num: 1000 + drop_rate: 0.0 + drop_path_rate: 0.3 # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token proj.0.weight proj.1.weight proj.2.weight proj.3.weight pos_block + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -57,17 +63,39 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -85,6 +113,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -94,7 +124,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -110,6 +140,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -124,9 +156,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/Twins/pcpvt_large.yaml b/ppcls/configs/ImageNet/Twins/pcpvt_large.yaml index ad7b4df54..e0e5c6f53 100644 --- a/ppcls/configs/ImageNet/Twins/pcpvt_large.yaml +++ b/ppcls/configs/ImageNet/Twins/pcpvt_large.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -20,28 +20,34 @@ Global: Arch: name: pcpvt_large class_num: 1000 + drop_rate: 0.0 + drop_path_rate: 0.5 # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token proj.0.weight proj.1.weight proj.2.weight proj.3.weight pos_block + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -57,17 +63,39 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -85,6 +113,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -94,7 +124,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -110,6 +140,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -124,9 +156,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] diff --git a/ppcls/configs/ImageNet/Twins/pcpvt_small.yaml b/ppcls/configs/ImageNet/Twins/pcpvt_small.yaml index dff588cc0..547d2583f 100644 --- a/ppcls/configs/ImageNet/Twins/pcpvt_small.yaml +++ b/ppcls/configs/ImageNet/Twins/pcpvt_small.yaml @@ -7,7 +7,7 @@ Global: save_interval: 1 eval_during_train: True eval_interval: 1 - epochs: 120 + epochs: 300 print_batch_step: 10 use_visualdl: False # used for static mode and model export @@ -20,28 +20,34 @@ Global: Arch: name: pcpvt_small class_num: 1000 + drop_rate: 0.0 + drop_path_rate: 0.2 # loss function config for traing/eval process Loss: Train: - - CELoss: + - MixCELoss: weight: 1.0 + epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: - name: Momentum - momentum: 0.9 + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + weight_decay: 0.05 + no_weight_decay_name: norm cls_token proj.0.weight proj.1.weight proj.2.weight proj.3.weight pos_block + one_dim_param_no_weight_decay: True lr: - name: Piecewise - learning_rate: 0.1 - decay_epochs: [30, 60, 90] - values: [0.1, 0.01, 0.001, 0.0001] - regularizer: - name: 'L2' - coeff: 0.0001 + name: Cosine + learning_rate: 5e-4 + eta_min: 1e-5 + warmup_epoch: 5 + warmup_start_lr: 1e-6 # data loader for train and eval @@ -57,17 +63,39 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 + - TimmAutoAugment: + config_str: rand-m9-mstd0.5-inc1 + interpolation: bicubic + img_size: 224 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' + - RandomErasing: + EPSILON: 0.25 + sl: 0.02 + sh: 1.0/3.0 + r1: 0.3 + attempt: 10 + use_log_aspect: True + mode: pixel + batch_transform_ops: + - OpSampler: + MixupOperator: + alpha: 0.8 + prob: 0.5 + CutmixOperator: + alpha: 1.0 + prob: 0.5 sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: True loader: @@ -85,6 +113,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -94,7 +124,7 @@ DataLoader: order: '' sampler: name: DistributedBatchSampler - batch_size: 64 + batch_size: 128 drop_last: False shuffle: False loader: @@ -110,6 +140,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -124,9 +156,6 @@ Infer: class_id_map_file: ppcls/utils/imagenet1k_label_list.txt Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - TopkAcc: topk: [1, 5] From 7830860bbf90c27165930615ca4ce7b330e455ad Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sat, 18 Sep 2021 11:11:48 +0800 Subject: [PATCH 28/81] rm writer dataset and sampler --- ppcls/data/dataloader/__init__.py | 3 +- ppcls/data/dataloader/writer_hard_dataset.py | 38 --------- ppcls/data/dataloader/writer_hard_sampler.py | 83 -------------------- 3 files changed, 1 insertion(+), 123 deletions(-) delete mode 100644 ppcls/data/dataloader/writer_hard_dataset.py delete mode 100644 ppcls/data/dataloader/writer_hard_sampler.py diff --git a/ppcls/data/dataloader/__init__.py b/ppcls/data/dataloader/__init__.py index 1a08f9897..8f8192101 100644 --- a/ppcls/data/dataloader/__init__.py +++ b/ppcls/data/dataloader/__init__.py @@ -4,7 +4,6 @@ from ppcls.data.dataloader.common_dataset import create_operators from ppcls.data.dataloader.vehicle_dataset import CompCars, VeriWild from ppcls.data.dataloader.logo_dataset import LogoDataset from ppcls.data.dataloader.icartoon_dataset import ICartoonDataset -from ppcls.data.dataloader.writer_hard_sampler import WriterHardSampler from ppcls.data.dataloader.mix_dataset import MixDataset from ppcls.data.dataloader.mix_sampler import MixSampler -from ppcls.data.dataloader.writer_hard_dataset import WriterHardDataset +from ppcls.data.dataloader.pk_sampler import PKSampler diff --git a/ppcls/data/dataloader/writer_hard_dataset.py b/ppcls/data/dataloader/writer_hard_dataset.py deleted file mode 100644 index 83aacb595..000000000 --- a/ppcls/data/dataloader/writer_hard_dataset.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function - -import numpy as np -import os - -from .common_dataset import CommonDataset - - -class WriterHardDataset(CommonDataset): - def _load_anno(self, seed=None): - assert os.path.exists(self._cls_path) - assert os.path.exists(self._img_root) - self.images = [] - self.labels = [] - - with open(self._cls_path) as fd: - self.anno_list = fd.readlines() - if seed is not None: - np.random.RandomState(seed).shuffle(self.anno_list) - for l in self.anno_list: - l = l.strip().split(" ") - self.images.append(os.path.join(self._img_root, l[0])) - self.labels.append(int(l[1])) - assert os.path.exists(self.images[-1]) diff --git a/ppcls/data/dataloader/writer_hard_sampler.py b/ppcls/data/dataloader/writer_hard_sampler.py deleted file mode 100644 index e4ff42582..000000000 --- a/ppcls/data/dataloader/writer_hard_sampler.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from collections import defaultdict -import numpy as np -import copy -import random -from paddle.io import DistributedBatchSampler - -from ppcls.data.dataloader.writer_hard_dataset import WriterHardDataset - - -class WriterHardSampler(DistributedBatchSampler): - """ - Randomly sample N anchor, then for each identity, - randomly sample 2 positive and 1 negative for each anchor, therefore batch size is N*4. - Args: - - data_source (list): list of (img_path, pid, camid). - - num_instances (int): number of instances per identity in a batch. - - batch_size (int): number of examples in a batch. - """ - - def __init__(self, dataset, batch_size, shuffle=True, **args): - super(WriterHardSampler, self).__init__(dataset, batch_size) - self.dataset = dataset - self.batch_size = batch_size - self.shuffle = shuffle - assert not self.batch_size % 4, "bs of WriterHardSampler should be 3*N" - assert isinstance(dataset, WriterHardDataset), "WriterHardSampler only support WriterHardDataset" - self.num_pids_per_batch = self.batch_size // 4 - self.anchor_list = [] - self.person_id_map = {} - self.text_id_map = {} - anno_list = dataset.anno_list - for i, anno_i in enumerate(anno_list): - _, person_id, text_id = anno_i.strip().split(" ") - if text_id != "-1": - if random.random() < 0.5: - self.anchor_list.append([i, person_id, text_id]) - else: - if text_id in self.text_id_map: - self.text_id_map[text_id].append(i) - else: - self.text_id_map[text_id] = [i] - else: - if person_id in self.person_id_map: - self.person_id_map[person_id].append(i) - else: - self.person_id_map[person_id] = [i] - assert len(self.anchor_list) > self.batch_size, "anchor should be larger than batch_size" - - def __iter__(self): - if self.shuffle: - random.shuffle(self.anchor_list) - for i in range(len(self)): - batch_indices = [] - for j in range(self.batch_size // 4): - anchor = self.anchor_list[i * self.batch_size // 4 + j] - anchor_index = anchor[0] - anchor_person_id = anchor[1] - anchor_text_id = anchor[2] - person_indices = random.sample(self.person_id_map[anchor_person_id], 2) - text_index = random.choice(self.text_id_map[anchor_text_id]) - batch_indices.append(anchor_index) - batch_indices += person_indices - batch_indices.append(text_index) - yield batch_indices - - def __len__(self): - return len(self.anchor_list) * 4 // self.batch_size From f6cfa0471ca8ddf3202effdff70f3895299caedc Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sat, 18 Sep 2021 11:13:54 +0800 Subject: [PATCH 29/81] dbg --- ppcls/data/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ppcls/data/__init__.py b/ppcls/data/__init__.py index 2664c296b..fd41ea3ca 100644 --- a/ppcls/data/__init__.py +++ b/ppcls/data/__init__.py @@ -27,11 +27,10 @@ from ppcls.data.dataloader.vehicle_dataset import CompCars, VeriWild from ppcls.data.dataloader.logo_dataset import LogoDataset from ppcls.data.dataloader.icartoon_dataset import ICartoonDataset from ppcls.data.dataloader.mix_dataset import MixDataset -from ppcls.data.dataloader.writer_hard_dataset import WriterHardDataset # sampler from ppcls.data.dataloader.DistributedRandomIdentitySampler import DistributedRandomIdentitySampler -from ppcls.data.dataloader.writer_hard_sampler import WriterHardSampler +from ppcls.data.dataloader.pk_sampler import PKSampler from ppcls.data.dataloader.mix_sampler import MixSampler from ppcls.data import preprocess from ppcls.data.preprocess import transform From 283f9fa9b627ebaa2ad95f2e23cfc633aac54603 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Sat, 18 Sep 2021 03:46:15 +0000 Subject: [PATCH 30/81] fix: fix swin training config --- .../SwinTransformer_base_patch4_window12_384.yaml | 6 ++++++ .../SwinTransformer_base_patch4_window7_224.yaml | 6 ++++++ .../SwinTransformer_large_patch4_window12_384.yaml | 6 ++++++ .../SwinTransformer_large_patch4_window7_224.yaml | 6 ++++++ .../SwinTransformer_small_patch4_window7_224.yaml | 6 ++++++ .../SwinTransformer_tiny_patch4_window7_224.yaml | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml index af54e4aa7..5d976c0b8 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window12_384.yaml @@ -61,6 +61,8 @@ DataLoader: channel_first: False - RandCropImage: size: 384 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 - TimmAutoAugment: @@ -109,6 +111,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: @@ -134,6 +138,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml index 4b9baa1b6..efbd427ad 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_base_patch4_window7_224.yaml @@ -61,6 +61,8 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 - TimmAutoAugment: @@ -109,6 +111,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -134,6 +138,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml index 58c9667e7..6c3abe6ff 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window12_384.yaml @@ -61,6 +61,8 @@ DataLoader: channel_first: False - RandCropImage: size: 384 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 - TimmAutoAugment: @@ -109,6 +111,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: @@ -134,6 +138,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 438 + interpolation: bicubic + backend: pil - CropImage: size: 384 - NormalizeImage: diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml index 16f5a7dce..dd2b2acd7 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_large_patch4_window7_224.yaml @@ -61,6 +61,8 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 - TimmAutoAugment: @@ -109,6 +111,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -134,6 +138,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml index 88fc3da41..34a80d834 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_small_patch4_window7_224.yaml @@ -61,6 +61,8 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 - TimmAutoAugment: @@ -109,6 +111,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -134,6 +138,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: diff --git a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml index ed9b4d505..d92159385 100644 --- a/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml +++ b/ppcls/configs/ImageNet/SwinTransformer/SwinTransformer_tiny_patch4_window7_224.yaml @@ -61,6 +61,8 @@ DataLoader: channel_first: False - RandCropImage: size: 224 + interpolation: bicubic + backend: pil - RandFlipImage: flip_code: 1 - TimmAutoAugment: @@ -109,6 +111,8 @@ DataLoader: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: @@ -134,6 +138,8 @@ Infer: channel_first: False - ResizeImage: resize_short: 256 + interpolation: bicubic + backend: pil - CropImage: size: 224 - NormalizeImage: From 9e97569953318a738c598cc682b4c0f145d8ebf4 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Wed, 22 Sep 2021 14:29:33 +0800 Subject: [PATCH 31/81] update sample method --- ppcls/data/dataloader/pk_sampler.py | 32 ++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py index 93762ad7b..f78bdbd41 100644 --- a/ppcls/data/dataloader/pk_sampler.py +++ b/ppcls/data/dataloader/pk_sampler.py @@ -40,7 +40,8 @@ class PKSampler(DistributedBatchSampler): batch_size, sample_per_id, shuffle=True, - drop_last=True): + drop_last=True, + sample_method="sample_avg_prob"): super(PKSampler, self).__init__( dataset, batch_size, shuffle=shuffle, drop_last=drop_last) assert batch_size % sample_per_id == 0, \ @@ -49,16 +50,33 @@ class PKSampler(DistributedBatchSampler): "labels"), "Dataset must have labels attribute." self.sample_per_id = sample_per_id self.label_dict = defaultdict(list) - for idx, label in enumerate(self.dataset.labels): - self.label_dict[label].append(idx) - self.id_list = list(self.label_dict) + self.sample_method = sample_method + if self.sample_method == "id_avg_prob": + for idx, label in enumerate(self.dataset.labels): + self.label_dict[label].append(idx) + self.id_list = list(self.label_dict) + elif self.sample_method == "sample_avg_prob": + self.id_list = [] + for idx, label in enumerate(self.dataset.labels): + self.label_dict[label].append(idx) + else: + logger.error( + "PKSampler only support id_avg_prob and sample_avg_prob sample method, " + "but receive {}.".format(self.sample_method)) def __iter__(self): if self.shuffle: np.random.RandomState(self.epoch).shuffle(self.id_list) - id_list = self.id_list[self.local_rank * len(self):(self.local_rank + 1 - ) * len(self)] - id_per_batch = self.batch_size / self.sample_per_id + id_list = self.id_list[self.local_rank * len(self.id_list) // + self.nranks:(self.local_rank + 1) * len( + self.id_list) // self.nranks] + if self.sample_method == "id_avg_prob": + id_batch_num = len(id_list) * self.sample_per_id // self.batch_size + if id_batch_num < len(self): + id_list = id_list * (len(self) // id_batch_num + 1) + id_list = id_list[0:len(self)] + + id_per_batch = self.batch_size // self.sample_per_id for i in range(len(self)): batch_index = [] for label_id in id_list[i * id_per_batch:(i + 1) * id_per_batch]: From f91bc7ba0be1f0425c352a122953f8acbebc6757 Mon Sep 17 00:00:00 2001 From: Tingquan Gao Date: Wed, 22 Sep 2021 14:35:37 +0800 Subject: [PATCH 32/81] perf: add parameter validation (#1249) When using warm up, the total epoch num must be greater than warm up epoch num. Otherwise, there will be raising warning and warm up epoch num will be set to total epoch num. --- ppcls/optimizer/learning_rate.py | 38 +++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/ppcls/optimizer/learning_rate.py b/ppcls/optimizer/learning_rate.py index ea938b123..b59387dd9 100644 --- a/ppcls/optimizer/learning_rate.py +++ b/ppcls/optimizer/learning_rate.py @@ -11,12 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + from __future__ import (absolute_import, division, print_function, unicode_literals) from paddle.optimizer import lr from paddle.optimizer.lr import LRScheduler +from ppcls.utils import logger + class Linear(object): """ @@ -41,7 +44,11 @@ class Linear(object): warmup_start_lr=0.0, last_epoch=-1, **kwargs): - super(Linear, self).__init__() + super().__init__() + if warmup_epoch >= epochs: + msg = f"When using warm up, the value of \"Global.epochs\" must be greater than value of \"Optimizer.lr.warmup_epoch\". The value of \"Optimizer.lr.warmup_epoch\" has been set to {epochs}." + logger.warning(msg) + warmup_epoch = epochs self.learning_rate = learning_rate self.steps = (epochs - warmup_epoch) * step_each_epoch self.end_lr = end_lr @@ -56,7 +63,8 @@ class Linear(object): decay_steps=self.steps, end_lr=self.end_lr, power=self.power, - last_epoch=self.last_epoch) + last_epoch=self. + last_epoch) if self.steps > 0 else self.learning_rate if self.warmup_steps > 0: learning_rate = lr.LinearWarmup( learning_rate=learning_rate, @@ -90,7 +98,11 @@ class Cosine(object): warmup_start_lr=0.0, last_epoch=-1, **kwargs): - super(Cosine, self).__init__() + super().__init__() + if warmup_epoch >= epochs: + msg = f"When using warm up, the value of \"Global.epochs\" must be greater than value of \"Optimizer.lr.warmup_epoch\". The value of \"Optimizer.lr.warmup_epoch\" has been set to {epochs}." + logger.warning(msg) + warmup_epoch = epochs self.learning_rate = learning_rate self.T_max = (epochs - warmup_epoch) * step_each_epoch self.eta_min = eta_min @@ -103,7 +115,8 @@ class Cosine(object): learning_rate=self.learning_rate, T_max=self.T_max, eta_min=self.eta_min, - last_epoch=self.last_epoch) + last_epoch=self. + last_epoch) if self.T_max > 0 else self.learning_rate if self.warmup_steps > 0: learning_rate = lr.LinearWarmup( learning_rate=learning_rate, @@ -132,12 +145,17 @@ class Step(object): learning_rate, step_size, step_each_epoch, + epochs, gamma, warmup_epoch=0, warmup_start_lr=0.0, last_epoch=-1, **kwargs): - super(Step, self).__init__() + super().__init__() + if warmup_epoch >= epochs: + msg = f"When using warm up, the value of \"Global.epochs\" must be greater than value of \"Optimizer.lr.warmup_epoch\". The value of \"Optimizer.lr.warmup_epoch\" has been set to {epochs}." + logger.warning(msg) + warmup_epoch = epochs self.step_size = step_each_epoch * step_size self.learning_rate = learning_rate self.gamma = gamma @@ -177,11 +195,16 @@ class Piecewise(object): step_each_epoch, decay_epochs, values, + epochs, warmup_epoch=0, warmup_start_lr=0.0, last_epoch=-1, **kwargs): - super(Piecewise, self).__init__() + super().__init__() + if warmup_epoch >= epochs: + msg = f"When using warm up, the value of \"Global.epochs\" must be greater than value of \"Optimizer.lr.warmup_epoch\". The value of \"Optimizer.lr.warmup_epoch\" has been set to {epochs}." + logger.warning(msg) + warmup_epoch = epochs self.boundaries = [step_each_epoch * e for e in decay_epochs] self.values = values self.last_epoch = last_epoch @@ -294,8 +317,7 @@ class MultiStepDecay(LRScheduler): raise ValueError('gamma should be < 1.0.') self.milestones = [x * step_each_epoch for x in milestones] self.gamma = gamma - super(MultiStepDecay, self).__init__(learning_rate, last_epoch, - verbose) + super().__init__(learning_rate, last_epoch, verbose) def get_lr(self): for i in range(len(self.milestones)): From 6f08fb25b40e8470f66975e3e5b53c0ce1802126 Mon Sep 17 00:00:00 2001 From: stephon Date: Wed, 22 Sep 2021 07:23:11 +0000 Subject: [PATCH 33/81] Add recognition service of paddleserving --- deploy/paddleserving/README.md | 8 +- deploy/paddleserving/README_CN.md | 6 +- deploy/paddleserving/imgs/results_recog.png | Bin 0 -> 28742 bytes .../paddleserving/imgs/start_server_recog.png | Bin 0 -> 126242 bytes deploy/paddleserving/recognition/README.md | 176 ++++++++++++++++++ deploy/paddleserving/recognition/README_CN.md | 172 +++++++++++++++++ deploy/paddleserving/recognition/__init__.py | 0 deploy/paddleserving/recognition/config.yml | 33 ++++ .../recognition/daoxiangcunjinzhubing_6.jpg | Bin 0 -> 45098 bytes .../recognition/pipeline_http_client.py | 21 +++ .../recognition/pipeline_rpc_client.py | 34 ++++ .../recognition/recognition_web_service.py | 79 ++++++++ 12 files changed, 522 insertions(+), 7 deletions(-) create mode 100644 deploy/paddleserving/imgs/results_recog.png create mode 100644 deploy/paddleserving/imgs/start_server_recog.png create mode 100644 deploy/paddleserving/recognition/README.md create mode 100644 deploy/paddleserving/recognition/README_CN.md create mode 100644 deploy/paddleserving/recognition/__init__.py create mode 100644 deploy/paddleserving/recognition/config.yml create mode 100644 deploy/paddleserving/recognition/daoxiangcunjinzhubing_6.jpg create mode 100644 deploy/paddleserving/recognition/pipeline_http_client.py create mode 100644 deploy/paddleserving/recognition/pipeline_rpc_client.py create mode 100644 deploy/paddleserving/recognition/recognition_web_service.py diff --git a/deploy/paddleserving/README.md b/deploy/paddleserving/README.md index 75eb3e35b..bb34b1298 100644 --- a/deploy/paddleserving/README.md +++ b/deploy/paddleserving/README.md @@ -4,9 +4,9 @@ PaddleClas provides two service deployment methods: - Based on **PaddleHub Serving**: Code path is "`./deploy/hubserving`". Please refer to the [tutorial](../../deploy/hubserving/readme_en.md) -- Based on **PaddleServing**: Code path is "`./deploy/paddleserving`". Please follow this tutorial. +- Based on **PaddleServing**: Code path is "`./deploy/paddleserving`". if you prefer retrieval_based image reocognition service, please refer to [tutorial](./recognition/README.md),if you'd like image classification service, Please follow this tutorial. -# Service deployment based on PaddleServing +# Image Classification Service deployment based on PaddleServing This document will introduce how to use the [PaddleServing](https://github.com/PaddlePaddle/Serving/blob/develop/README.md) to deploy the ResNet50_vd model as a pipeline online service. @@ -131,7 +131,7 @@ fetch_var { config.yml # configuration file of starting the service pipeline_http_client.py # script to send pipeline prediction request by http pipeline_rpc_client.py # script to send pipeline prediction request by rpc - resnet50_web_service.py # start the script of the pipeline server + classification_web_service.py # start the script of the pipeline server ``` 2. Run the following command to start the service. @@ -147,7 +147,7 @@ fetch_var { python3 pipeline_http_client.py ``` After successfully running, the predicted result of the model will be printed in the cmd window. An example of the result is: - ![](./imgs/results.png) + ![](./imgs/results.png) Adjust the number of concurrency in config.yml to get the largest QPS. diff --git a/deploy/paddleserving/README_CN.md b/deploy/paddleserving/README_CN.md index 3394ae5b5..02ee2093d 100644 --- a/deploy/paddleserving/README_CN.md +++ b/deploy/paddleserving/README_CN.md @@ -4,9 +4,9 @@ PaddleClas提供2种服务部署方式: - 基于PaddleHub Serving的部署:代码路径为"`./deploy/hubserving`",使用方法参考[文档](../../deploy/hubserving/readme.md); -- 基于PaddleServing的部署:代码路径为"`./deploy/paddleserving`",按照本教程使用。 +- 基于PaddleServing的部署:代码路径为"`./deploy/paddleserving`", 基于检索方式的图像识别服务参考[文档](./recognition/README_CN.md), 图像分类服务按照本教程使用。 -# 基于PaddleServing的服务部署 +# 基于PaddleServing的图像分类服务部署 本文档以经典的ResNet50_vd模型为例,介绍如何使用[PaddleServing](https://github.com/PaddlePaddle/Serving/blob/develop/README_CN.md)工具部署PaddleClas 动态图模型的pipeline在线服务。 @@ -127,7 +127,7 @@ fetch_var { config.yml # 启动服务的配置文件 pipeline_http_client.py # http方式发送pipeline预测请求的脚本 pipeline_rpc_client.py # rpc方式发送pipeline预测请求的脚本 - resnet50_web_service.py # 启动pipeline服务端的脚本 + classification_web_service.py # 启动pipeline服务端的脚本 ``` 2. 启动服务可运行如下命令: diff --git a/deploy/paddleserving/imgs/results_recog.png b/deploy/paddleserving/imgs/results_recog.png new file mode 100644 index 0000000000000000000000000000000000000000..37393d5d64e84de469d78dcc9fad88aa771f57f8 GIT binary patch literal 28742 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 u@vN~u$^CNJe{tgQ!B_{uy`~V>V#|!4w0TmN9 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%0ATp ze{44Ihta-yyu=TC@apbA6o-F!YbNSc-~sZ1uQ+J`d7l5BoLG1XLJe18?&1}H|Jxr= zrHFy}T7cnT^BiQkjel#L{P(<*{bHqE*1Ppu~`e65m zQm_c*_~XvsVu96GaiOx9@BgM3P~ROY^zs9`Md3%FDn!#7jeJPaE13b(dNg8>)ICH} zjKb6cs+!st2qd`<1dK1d!x}X%j_e1@Y0-%4u*o@yXVisqH-Y_<@{wo3Z{6TlTZSvg zG>mxMD#gcW6R{9%k0N3x;(a>v7HrZ=qrlD;mi8jUCL{6D_fk?qk43~hrg0IlK?)s_ z2bqs&k|XO$|LquBrr!6@Fpu^@m<20fUqPMcR#XY#wigNk@ngGT z?Uxi4jvOj(;J^OBq2gjgnOEG1c{0qDAEnQ7`nB2M^*76&jG=M(&-d}q!M@&D5Rn@= zHu1e~1>Z?{=l54b?2|x{F)mX>@Sle~2|j}Mr7iBOR^X0nqj~8hq$gxqy@0X?~VZJ z@1qcq7)shFQGLKfKTbI0d@X4b5v6fJj4eEMzw8)?0mQwQ!7TG{2bCvM7Jqge>Ta2* zn6L=$MjwPOe!edS+n`E)^>xUp$6A`%GLQy1HyXW%N)HCRKYU7ZI_Bj>@a_vwHBgK1T$Vs^>M3)L?JD_nMqNm(2AuqB&bV+(2WW zpcK(Y)`5^!fQ_|+NW%cM*O2`1V}J^_phMc_9)h_aHH{XZfR4ui<*BkL!HZ%W59@f> zsKHR(ISkslAFheLzqWjA17C~weX^H-ZPL!taH8DCQs%QOtrAKHc%WM*-Uw#YOzGJr z^ZA27kR}K!xkY*biGx+vth-jbJ5a|B(h)~xeLH)l3hETd@#AZ|* zQ9EX)v_wvXfnI)Zd8m{+8Di8QiO@#)afN}t?!JoH^^wHOWktoKX&v*}E$}YN8(R<) z5*}k-;;bb{3k3x<)>1fvH@iOdkvI7oQBq!m*5jz{_H9KVsj__?5cEM5?IrwDb<858 zY64T+0j$7C&i;GSGEj6&5wcSd|J&^vpfo@syP)5G_dS@oQ|#xtv>zXkCy^@|(ugZi^5(+AdzD0QY>OnbA0uZk)) zUQ<^9@k)6h9;VkDcto=}D-bFxr=MYv@Li?4nW8WxWRz6&pM?uwNju{ zdkI2m&soc=Bo5W9dtVyv&_j_AV#;(by*tqvkD4uiUI1TSw?FfE3`C-osY$^>q|!t| zHa&o$q?pw=2eyYryt@^_=Wv-aZYh!~jE8@k4K zht_UI1H=)){Uk&D-pwNW7q1Ys`368Fz9^nr)F5%+offiRpv31C4=EpGbgLOHDNKs`I1Ze!KY6NMx#eIkW)aHVwx=VHY075h2q^%Q8{pwXd3{`IdvZqZ_xkzXV~F5Srucxg^& zV2$$~XtPaTAWafJAJEiFqX;f7PSsG1p)UzkCI`!SN%9uWc*rtL37R3z%zR6U{-=CB z7R{8V%i;ulGbX`2=vWU_#7-I`FA+o%5xA@8jF1t=YyH(x)mYsMkO!rhHW!_tM;rjAPmF$;*bX;yJ5facH#N)x~qb zZ_t^=v(Ks-QK{7p?W4CDpZ ze}$mqc_orcHD_Ld(II=Ui{Hvd-Fv)-RBc*q!7TacHAtXhFw@Dg=Y@vw(=)VwIlSp> z9#o#bC)oEzsH|fz+(s-2M$8Y!AIr6mWU=S~K;6{h2){G5VfcB{G3}6t@|p@=d>&4%F6{OkssT;|S_4=i_72198L$g52_C4^F!ipt%z*Wr7T& z2HeZNBAN?IHBGE@Z@0eSlftR84SIBkFY0edqN*@v9o5o zCra1*!=Nl~94(L$x3|VB)*^zho1FBAf!unfpK*vCMQAlK4l5}uPtf=f?)MmTg=h6u{nbUm@e~Ja) zq+hA){l=gzw1KWVcZml8z<%5KZS7 zi2)Kai2*8zwqaiE>`~MV)sW*dHGGhG7meCxZ`wuHV^5|C(pxIf*ws_BS5i#$T6{t2Kzj{p+S%k4<;G#! zKQCD{qr`y?N_D(frxM?Wr^AOc_Fob5Zr1WSuyxZNfu%B&S|(d21n;>xU9UxD>t(Io z)KGOwW7rWYv#WzG{*-IVppNYMi&W&3s&$SSq+j61>1!GJAr?2N$G$PIA|YwHbs~K- z-MjfXU3{3v0qiJ>wM8D@*PpW6jdrVT9Tr;Ib*pn}nkK=i*aJ6o*Gm0nqsq$nj`zkn zCnqukg+JVtCZboaZDp>iyOTL;GkvYVKksd768ywG+@-)PClrR-vaOx)XvHk&peaeCOtbSqLfx#cw>-JlkQGVo`Mg4Y^uOu)3^qqhF&>*H|rOd{e(gv2b z_v`*0;ecuPz&(7G>}P*3=fUN|W1Hp;!BT_ccdG%sf3NcYzKVD}qvpype7C4BXKDIB z{I?uxfh{VKq9-R)(^dX>+JEgCei8=KwO`XDFaLA(@t^&~|Ng_R==;F$)C888SP8-j zI3_)<>hcUP=Jf%y=NsksazNOJMHp6q8HTij;Nm=deJIdmw_875@%p>l^nvH?<)TF?+i?_Z^7?rE(AmBOZU-=1MlKIPLLwNlbY%9AC z_NLLUfPGPX0Za^X|Gr5$SWi*V=on2NBC_?+V=*vIybqFtn4nz`FlAc6H+S!YlvgVt zZ5|Kn<}89d<|M)~R(oa(3|LOoGqAaLJ4X0GUsn)g&2?>(z{j>~QE5d|$}7u$NIb?H z7~oF32;u~j2o8@LGnO<-=o%R6!pj2YXC)AcAx5cbBA&MtVgLGCwoDN1gV@a{A<#qs zY@rPi`Zz=iTxji$OuU5$La@^xBm}gUmaED zwzsQ@NGY+9l!gUJBOxK(9SRHSl1@Pyq+2>91s8(SNOwt!goKh(QUVf-jyqq^*=O(X zerJq(|8nS95^u~oe^2gGOX2P1M$sSB#y}&X6t@qk)Gmbx`wbv6kaPRpev2f~7LCwE zxr+mE_k@?w{~Y#R+U?o#J8Fb-C*Eu8JAaDgGXdL13Dm#CJ+uKa_K(2OYL@*40e1p4 zXv&(dv8*0M6WM^NxQq&vB8|Li=$c%g?J~>ynMTAw?>j*61cb9K79}Zv#^K0fKMHAI zT+ktQ<37W;#%3_J4cLHElR`-5ya5~nI|2>3sej-78+3oBVKA0J)9~FiZyN(}hOaao z%)gMm{}UYJTp-4Gv;Fkc7x4LaLK=an?Ze4xPAfRo)_;I{?pH)`rh)F64@+=$FYHa& z6S7?hme8pn%>mg-QoAba)F7McDGKx(B@IFHf8Rw|4l}w(ZV2&*S3(=MePX;@XMY8L(ly! z4No8u?u{G|$#m&w83z~2!0%&68>cyVZ`omwOGk*ri8vwhRiEnkTgp%HA{bHvSHh>! zDv+o~jCTuWhMIvBu@q|pA-s3HVXKw{3c?$7;Imv4+KAzcNq6~L-f_8Fq3vk;L1gz{AF8nVy=(V$6@DemP+=ViYF`ApZG6V8<;2JtiLy zvwN5m~@|MG074AM^U5bw{>Ofg!(?kVWavt4iS z=}Y9c1xH%;Z-xMK8;9UL!UlU>!Z+-L4)$eA{1`$dlpUcYQ8f~Ek2hD}p8UGU{M=LP zuZ@i>hJN^n{Dk5lB&x72JjU)9&s79e7wjZR^Zu6`=?P`by%! zIyO+6k}u)}`Fd~^Tz$doSqk+iYKr!wC9)=t<1-{I{xI0_(#gCphjf5a;+8{%sRD5< z1EotqT;tt9C-564E5M;qeZ1?tG`aJtUo+5OCFxC%K0bsh!s5Pyxw}|7j?%jiymLAl z|KIa^;E!I|=r#IaQ(gdrph~ysS|NS?eG{m5@KBk!CW&TBPzEpmtI%e#);6qw5mz32 zuf%VYBXxGvt_ZfJDhiBizeX_#DAK|b0I2(d#h3c}7;#;S+}ZY%G>5u<^mrZVGyN-jIE}~~6@qQ%sH=71tIK@q@q1(0%-X`Mlxkd&6V)%4h*CFDLu+VT!ddAW8ZG8LoghWwY zTNynkFaV8V#+zka^(*V6B4*8j10kLzw&#N6}%0Gm#61zcOD z;@J_9nz1FO{N$S`NmLW9_w=f~f(!521wPx_!D7nf(oJ(<+6Fcr$+_ZKQGgcocU=LS z2}`@~4}9KAAT>yG`(BYUj;zx1RQwTPR4i1?jagQi2+VIE5!juJAICJ6o#OkVluc?t3_Sr}@` zNOfU34S_pQuOlwc?<)uE33_SMR#09vu7)}vw4vH^0zr+IOBhd5 zwXPYD;Agvp*s=pob1+_!u#ZgimF{hbUC2{xk3R7G{V?|z7M-2Z%Qy#Li41iiK zYK{xc*y|o-VhBpz&%o)|?qW-U(ao6B^4nD6rA~GsvS_td%ZO9~VB)t#)yZy*M{#qG zrsz(n_+9{0Tq#_+>T_p82kttd9{~3``uUD%lE8U_x<75zQ1O~ss_ZNRORB$*fEoHDZt$JXb_a2mvg zV%^V5kA!m4XzmnADEfYM5>r$1L@x7MLpb6{=_S;{xoFy#te5HZN+HL!A)f`; zE{=$JsI8lOZ|zdGLj^{*XYyKWb>b0>;~RXrcv!UD^aA&u@Tb*#ItCP}73mfFv}-fo zor4Y2zHi<9@H+;V=%fYTQqnV@;tvQg-s#ZVNp)6xFjynxtoG;*%xkwutYfiYkjCRQ zuf^iFKj(UgaOK+^dXsQ2!m!{_SFlB7_@JM}{Jyxyl4{y|V=KKa&b6}*@pfx+a;_-H zYyQ^+3C5QH;)D^MO5GX`ZIk#iPCd2++H8T??_vqMdJ_84UJEg_Kg3Ao8xdvXsu$AQ zC$hp~xP=T$qZl8Btu$ePUT2SMqx4Z{I?7ef1$WXn68+-S-t8goy5Q)aA0I4!Hv8*U zjl(<_Kr>T(*@I@Tjg6kxNU5n!qJ5jkB+ue4NxcNJu@9h+OAe|1o=7WoZdw^01eXqlZBF5;R%6n)94hsyO%?|-knRz)r0SIh|oE=DmX$b^K@L!gMq z?f5elTmnN|8r^*1+3Cl~a#h{baD3er|EPP?lR1&Jy!_N50`n3Ud6Jez!f*bg7e1CfDC`|Mqck~kVgkf$Ejd-CUr15L> zX^|Q;ZLkI)Lg&^+SHbn~77rFkNmn-xmXvIgr}Meon!g;J@9X+zX$BuqG?w{3982St z)m?0*##TKuB;5DLS4&?*wKe`18*h4F8C}W8JJcsGHDpH}Z`k;nw8Cf?AC>L^p{fNXFXy#LypGCnr{@|Lx9@QGk%+!18JD&9 zlBrXkVi#dx9gn5yRCk$s8UQsrdigV(1%eSeHyvE&eqL65#k=Pci18BUExeL>A5)P=(9wz;=WvTZtZh>>Pp(ozGSer zZd? zC3yF&#ukO6EV=8$mX#-nHo6Fj{{4|Gz`H8}(Y3vH9AHNce!s{X>2+@j>g z4XW5n3;DUo@6;44PLY_Z8|gibZQQEO8qj@@Ij{iy&$Gl$sPvjSl>;Sd)hl%!5x9`a zkjnVU!eOGsW-ywPusB_{ZO2}3<&46zQk`L)RuT;|NiM7ZJInT|;HF;wTTx`buWKUW zV_!q5fxD)vTcf}iTW=yl`C})f*45_todX?GrNqEOErX8_8(wi~V#cI+$gXj2pbM5p z?RLtVs;T%`7+gFpX!)*|Me}3gnO5Lhj*8uM_ZwAfS;Ofmsuc%j{VfKnp`yCluNC&V z!)0|Pc$Uf=NwWWhb}k9P_?laFVcapS-t7gTcA7#r2HP8`;pT)sWL* zrXb^$u6-#numz4?Iz_k1hpgr&-(OXz9@^Y`n-wcS-{QB?MR+^=Cd*$dF+{K6-U_I? zhIgw3H{+i_$zPi!*Ek&nYZXjsT6X_^miY(MJ?DbSaNTdxV7l4m)`Vb80)@c9FA6Tbi45s*Bk` zkXH0HKYMj*kKSNM6m@54B!TQ%TJ9dzNCmzHLG6+M9k~XRH`!pX!>evttzxHi!{}Uy zJc{-z*tHtC_x!U)L?er#Kf1uq<-J#JCqO5C)wXp*H0N}zAT-fDR0TaW4e?YVE+wl|Q#2>YrLS*f5k2rV@5 z+5ME2x8dOcEQ_pV_u_{DQc)WWiT^)nFdkl8W*6kz6@wyYWLD< z%JOtw`WTs_Mo}P%KW_@?<3XnV)CXpvOYosomk$r{7+{s}ARWv&Q0hJ1*agb;KU6E> zq1fe;*ayfc(N)HJ0&D@=o|BHuI#4;lM%xWMw(sg+AT>O)AhY#k&{5I5s8v(0rJ1fz zI0i~R8RZb$jZk}RblgD_Iu+J*e+ih@huI7#ul@%>*cjS}6VWD*?*RdTr%T{s=#c5i zNkzvPUK~J%3*Nkeho|CizLJh@)*I`eK-~ep05_#3j>vNECL1W)ge#|U@wRx@pqpND<`uAfX#7z+# zR`c6S+;|go=L2M^vbjJ_a!&pcbbx3Q*5^Xc8x0pvPJlDN0ab0+yl7=8PzzxxVLv5) zBC_{?8vmq}2p@z!DtJ@|0SNuV?So{kyD^SQ%#n-gp3gbI^rs86h5!5oa6>&W3|P$-szP^0D0=*i_hdkNp95 zOq|emKir4*DQ2R?1^~E9Ar@Iu5otTTH+iY9mpe z54E4b!u`)`{fk6+H}-&GBdLkp#~qZ`OLDbB*foO(x083yHiTgk=HIgHg^;pdcwUUM zSKR@CUEI{b$bArnCfe6W*b9X$RM{42<*M1-{= zKUW>kj5p9A)CjTtFQ8y~(>@3fybprJ)Q6~s>EjD=?9K9%weKTg>*I$a*M0M z;E#d^`^G7N{1!oov6O;lcj4Ct5diCsvlL`%0*|2>D>kOv8-x2&t#m3|0*nM{pehBZ zXYOO?B(a2v7jQFok2nn79Li~+E1RDo?~L1op!K_-`hZeOOa*~)#(tHASqn5iB%@FK zzbHqzW>~9hYq|P^r;w%lMmA*>TcteKJ6~(5pLWHaKnfL_WPVGGv6i1d z-34&qsr4b}kjl)0L*AOQBGcd+zi_m!(ai_T^ETj}@-SgxCOY}PfbU1yRn&^o+ zYE+3cnGncH_7m}iZ-@?1;AFJNl7cShbZKo<^C%j%nIKHU4Jm*o)c+BIAP&NLA!kI& zd<(9F{G01DL5(U?dqHile`EngRERmG97vQ2;jLh%^BmmlT?C-2R?>A8Md#vHX5AwX zfK_GfO$!q1N{=8d0HyQXhLC_ML6_->=#I&CphWjK81bUh2BSZQTMRGH=MU%cu|7!Y zfdrxn21U$mSqam6(&w+WCi|U%L*p)*2*@J2i*^n$!rKqtpCOx+-V$42KX~ zsfAl7?J;`Z0An6d^xl1AnFY=1kd!Xh>SC4S6nKz)!mrUE>We}dJ1)^}Q$=_haC`rU z4!QlmXwM)NzOsS;x&g2%vSKHKrLqdSTV3W`Vyt`VdP2y=egOa%y{T}&eW>BeIIAiZ}h%ltKhhZ~BX`%dft?a`1-#GA4Y&r<>c zj@-O_{d>X8np_;)5*iMi@#ZIz0koAgel>1FQ^L0w8ehu2)^APKoR6MsA%pDz?k~ea zZ}?Cj{9KP73A5?p%Q4|HS61PXl0NWgJGSSM4UqOkzkyBUHkio3cKJIt8By=FW`Nj#arAqIexbl)l0PGqNP zzn{-OHV}IPB;f6*i6lVgHmQa40vcZ84-d>b0L12>>>YE%-g+Zv>M%HBLiWg7j3^Jq zt$MB!z`BD!1aoNM3MBcAHAKFYd^-FG0p9a*DuJ-HZ0~90?Hfnc)y*{(t<$7Lp6dtt z;kH5dOg6uhr-+z&^k$4)O>H!kILc~|cSWDd(kTf8FL z{{w<7aCh=DOJDWX8>&@K@ zrLSLm1QevLDj#@Wj2Ce17C$K6MdmS6J;v8Gd*g>?)QxS5dt^5eR>f@=JHPRPI)oy> zwT>ky8jB&@cV(!HGwr=p4&+rNHEtmIQO6Yf=RoBuX^g;8xjh8|gcaxM&-LCzLoscs+At}>EI3M#D3#Ifhp@TJP-Bo(Xp~-#>p~y zbd&eP>OPyI5^avjh-y#pxgG_+C*gvv) z3As=BUYDBL4!rV_&1tUagG&*ELm+IGGZvD|Gqq!T<|Q*1q{ z_9GIBqC?+)BDe9`Jd`KG0F{Er;;UfDR|_fvytwvJ{yLv&!TAfyj5;b%*p7P5AhRbmwR| zZ0hjU4FpAG{!REhuJHm#@Y3w*o&A!-*EXL)FMlu2Qy={z|J*MF0YfrjmR=Q0!H?j% zT^?3>cVeFzBKwMNSxX)hXkgzMO@V8&nW40Imm-M(3+9=F7+|HE zs0Qo0o0ZF2W>`Ysr3$RNn%mm7g+;&KTJv^WElA87wurpuAS7>YP#nTfz|U=x#j9Yk zz+Inst3y%zOaaSyA0}O&ktcp^NQ$|%a6>EYYSsVJvcgr!hy=Z};g^@EZgf0;>GNJ^ zrl_ij76lAVV!Bp%C7hhryB((eUf_$n z%qw`%(+q}7B9w(fY_nnfNu-0eyGD98Pk%2yt);Jg^+Pk*>PB&ZN!D;_$$Oon;+$e~ z6##roJ^y7=oh?2Uuy9kIUhBI#sNO1hl8f7OT82mvE$Y zhMS8o(6*=WY$}QHK|MF4ui`YfB<9!Ua`CMz6>Cgrd{c0j)AJR)*hsq{E~lZbgCeQ> zz8Q=zCDjw;PaQ)G)5Q}Zb>G3x+A4hBqfa{ngPLwjo*YT7AH5C5)uLpl2!HT@=e(c;G? z=clE9L#$}mS8^q=MjX=zYbIGa?MV-8yl1;F0ll$RKg?QYKgsgE3U8@)g^38`l-yzq zEWvbTv(O^k-CP{Fz)sxH)Ai9_nYT2AB(fsOmfewV!05ns2-jZaY*VKr{eF{2`ou(d zf#4mAkKz0BNJ}QdSDA@5&UNT(dn+e)=Ns_;Ud2eR^o+hTZew-s%Sd_=_4E~iFJz7- zOz`dznp%77^ji_a-Kg?kp_XypF*i59KJO#d_Ajmalr{P1;A#b_O77s~U{Sf)NHM{L z(ZkZpGh8dYNV}9zI~55d8c25chiz07l$Yx%Gt7@36r%cLH;aSWyn}^51bpP+<}S%G zo81++kVw+O!<~{f=j@HYMKTbKg@>j`vi_p-o_B9I9I=?Yo6H0goD9dRyH#_2-f{cM z1gCqVA!!|+$-=9(0F%I93j_TGd0&)ybT1`EnCqQiGq=auCPaiaL_~y@7pv~QWD8Tr zuAApmyawY&krRKw6qune5gRq9v)p@GE|84NrYG^@1_i3GnauiG7Ck0|Qdu8Uynubk zvz#L0qHQb8(TDnbtg}u%ld)CYQ?BTw5vy)*W~QPe)R-mcTC+xpLNyi07M?B2Ef$J3 zs(GPTyt18Qv$$)QIXTp+^#*-=@6#iS0MpzRpj zw@;q?*tq2D#M`o4e)iTi)y;;U8=(PbUe%Ab-hKTx&yP|4h$M8nd(+~9L$DHurlUT; zpAF1W_A07LhO$S)VE@$`zbIL%lxmK1W;{hRs_w7+=CLfn`OFzHj z#SnZ!9#&1g`lwZ&In*rLnj&m^?~NVzPgm@M7MA7ci_>KK^@)~X>WFgro;vny#FtT4 z)up!;gPHPk%VZ0;qngh^mP&ZZkg(87z21q}3Vj(98Ylt05peZn_DI9KQ;Y_?~0twz`+OLSPy_Xo& zp>EclZ>U~{l9GMY&^4&L>*di!Za8UbO1W918yAdq#A1>}?QE?|TKC-hxZQ|?Ys6&a zy`J5Sn0~xF%-dnHcxmIkL&Yn%rD9nL8v5JhFWkt8AKC_IkXs`|7bPxGl^e26&963| z;lCIAU40$huqDn&5o=lYjA4*}mPI8+`^Z*mTIFDvtD?pnreTwEWA#@y)HOhaECpc(|MtkOGW8tWG4qMooPC982DfEg@_2b#OY5%JKY;l4g?DWehzGuFNd`bT(vYXG>+~K)UEj{5<{PY&f3I3x zWY_tbdt?=O6aBo0@nNu_?0^USF<6td3?s+^^bvFe5%^~CCOIo1VW%YR7x^0}<|=$I zUoSfey2hNja9R(<-fC$=%=Z%xqJ69CrX|39Q!Pk~p@aA7I4!PR`__Un*3zNX$z!>g1@exbMOM;X8@Y2p zf6*y-;>oGRf7J2jYDIvmm?7E}DVm+gSQtX~ zlmHtKn{0W)qfJHx{gOyo2~-fq84pOr)Hh$P5#4L zz_$#A^nRt0!VG@r_2PhVy<;wcAfR5$*Rx66j1?$ECh1l3KW__g@ilfI1*gCQPW6v zF@A4d&%Di4KC$4RA5R(r(|UUe zX2v-$c0VvaDlf+SLExAuUt?lyT6`zs?9n&;V|r;p+tm+nkh$Kxzm3XA9uGVeugiB3|i~OOWjhp@?a-4p**Bb!h!F(Vc{-i9u}%f z?mMD!mZ?P3`&<-WZlcI2d|y8vul|Md4&9&r$=CIa(`3DdqVn{y5!`m;z3w|zYs5#J z7%RImI??`!(`MBCV)zwYjtJw@Hg!ry3?`$>wQdb4b+b@EFh9e36sDmZ{ zv52gulA-ZhU-@W>6}8kdeq6fky74Rd9Mj%9lb-KwczscBjvX@J>clQtQ)?o=y5!XdrT9CJi%WqP^7TDPnf@E09nfw7V@nJMJGWw9>O zVh&1)E?<5=(QrUFe1u|6NkgyMh)B!aU&b7GQTJHZpgR9MFwBeQYh7*m{~0D8aP0Es zlf9dKp6lt~dKDk``;JrWC5f407dPXX5%T-{(&^J@GpnR-)T_pVy=lx;LyyexBvnOX zx2U;O@{BYiWm*&cpTBRYuKO;UekHszv~S!Y?T9rn6ivpE!c#RnByY@Qw^S8de(|f7 z(36V^sa}w|$33T8X<_}En|y?+JC9Uuan3<*C@SqMrdiW69rdW)4d?8Ij&qh2Z>yaO zJo(_hf062L=yUuUU-^ICq=u8u#4E3DNS<{E8mkxU5p9}uFS^&B%PVCoW0<1x5w9On zFqJU-SY-r_@$RhL5Buguv6iv0KMM^wFS4h}ib`tfIgT#YByf%?$zC34C5;RD(NA3vdxM&Mz zlQd7YCwUg`Ynu2S>fP)MiJgnnVpRr!`Vm@)OH@Yp|lCCXb!k@4b>KVppa3 z?y6@d%-F24;b~r7l@yTNH{yb3-jwq~J=W6Z0$g{5HH%VJ7=T~f5a2r2uL{Py&En)Y zW&exg;>yRD$N8NkDfCt0f74Px>8(X@Tp+-QQWp6ylonuZ(j)=rl`Sc5$@5Pl?LT%U zM-GVe(m#KI`dc3UA0;bdL=+awIPw${=wt3D>LddL^UXRR!%;FDq~s8JUxqgp*-jk zz-fN9b8g*BF2hCX?r?9(O=ZWE;fKwic-2k*_<}lgWk1iEO|M9b>=?~g(6*E0)d_m%KvKTioJ};xr0>( zs5h_JyK?}rzX3Qd*BPPGS5W>De*2zH3RL-11oEA4m$0e%b^n$64)NdMMqCF+3@UG?mz& z*n@uHagdv}rLb28xHoOc_x%CVuqS3x_vL^L@|pwTou65wl^{r22f)>OZa{CVvW}F? zq`LVDN_gWDef6;NJq=nUFLobPdVIfk>-R?wpvcxC;+fO<LuE8HoH@*3VxnZ zwNK8#&-w&1v5JjY{KJ1lDusTTDtH**3yH@+ToRuBOZ~kF+R~TaGL9CuQj9KN0Xu6U zl$`Y#&V;#r)OvN1%LO-0HS+UR_#^qYvM^I#JOS54hw}Mn?r{JqA}(^S_WeHq{Ys4x zpwoI7O1zNap8(yXFUB{10U1G+vOO`QYamnNI8e-rJ=*~lndddh@d<^c$JRL0JJoo; zYf|GtG(7G28~2D<<_FMLXTD=k&wd1y)>GxSwI@CzfArtSpf12p!xZ9+#sTMOXaFdq z&R4>Y|9Iv{q>K^hb+3PsS!Qa;-(}_cw;Ni<`wQCP|H};xcq9FBNB^^Fm+X)&=sXyl z2^D?mz-MYXfA2xJ`d|ottw5%|=UpIxVxa+; z-jcB>K6L^~rY@T+l=wa~hwA;y0IdQCy5#K*(9q23X{V$$mmI+(<7WiKz#HJVErqJP zORekM$V2XFu|fUOjBKpQP4~-0egV8}9Js)BtYiDVA?JKlblZRY&u0j$A0(B)`kd0Y z*?K+4v@Duwb@Iqn#HUL&)S)~GjIHR(3ZIG-R z>uS8ruR7DzS2G2KiGfs!S77kxy&cs3e^%U zISSg4H0fgu4iPG`Y5bAGe_aZqJ~y^zV#;)qM1kd{s!I(ES@QdiW(~+;KziJ&FZ6Zm zoJBeKwpWjU=q0~4EynVbWhOCQ$i2O^_nr6Y$`_CSta-by+QecQf}oIj{63rR&U_+g zZdw6Pi4>Jy49K`0Nkga8YZqs#Z{ta_P55l>h*$3ET!(Mzs7r<=Z%|q+-L-)M+;@+awOIF!&;RV{PSY&>l#Br}%(;c!?t4j-4H^e-IzF7V z0H0^}oC0eiHkLQ=XKbY2IxR6|XA;Mcb3}yMn*UI>Ha5t;a?3r&*M2?j4W$RC^fLw1 z(O%Y?IDtsUq=)gCD0V-F8tUA0)9;IOo}e1zt_$HDjr~b3jf5XcH(0h2Ga$Cbw&`VT z!{`oZvzH9Erpoa0Oz_b!2Z zH}x|24s+euiHARbjP}U`NW}4HoilEt`+ZpPnKViTF$+~4FJjBWM0=-HQfOeN+V4Qa z#H!SHSxn_A72}eqbI38FAkc+AZJu#A7==MV$A+vZeb^xcS#Um)4?LYAnurFwUxl(& z`Xu6os_4n`&-K2`49va<307NW5w5K+;7ug%F!tpgHQn%L-{2#aX#(gKV)3WDa}kcXVn>E2C*Q8VqdVl`Z4>TIpQUdCZW? zdyU2kiQ@b#mpn%3tLULn4S1)6g8uW)qx@<0-%Sv_?l>YDrsuR8-+=Cn*wCmck z+TO4;nuY*x{6!#BZ(oqDfqTr_MSe?pPMT^G#NN7_ z^oCoBF2D5VPqhWdtzqvWTHPY%LFG7ulf5tPXd!jH-LmSm=}H#Y2;BgM#7vT9F6jdf z8g2Q=->0mEFw3M@6Gqp@*@Fw`8mwhoBtC^1pXg?b=x-@;I2-4 za;Ji);?8?Lz9oGzIS$({I|v^PWW8a_6nrw7srOXwmBF z2Xtd9+zQFSn|_maT@D{tdkSJMyqf?L)x7P~kcivJlR$`8yC zQ6z$M;Na2k#t<89svx?Fehl=TMRLLi8ScSCcK6c}Ciy|U6tP{l)Is>3qtuvzb_$Zr z2H>q1nDKOFPTVA7>+(?F8CWq7D~qpjUxE*?%9tHA$1gKg%D)$1l=s{NVL5fo=cahr zX162w$3o8#sE~w!s}pQ@zkalf8N%B`_uV9>vQ@5NGF1OrwWW=t@qqwegxK?0iNwo# zg9)a0qh3H8rIb)CEJ4_~ay@<<4%e{{B)WaSxTTn^SnkC~er~XsW`)7qfdk%x=X+8b zFW=Ca33|a)7ZDJExFN#KHsCiBe}>XB|hwNf9}H_h;AV-qGE3 zuHcA|y3a$n%ra_Xv6Rp&`Kj{g^^niIy64>}2RVxJ6B?L4!jm*Q>6)HEGKSTGMT_ zUigTj(c?P4bw_3g#_Ply5o-#2-&~4q6SwNavC*)F{x@5cyH$sojKmnU^n3H{;J+@A9j6wHy$QyFq}ySb=~x7l@`*~?l*?UR9{5p@#XQLd zx!`gWeJ%kZ6qE9vmiEE4O2Zi}z!|PWo2@UY%XS8A_JRMj*@s79u>qUCG?5V2UT=OP z#j~v6?XdSe>s5aI(iDrYZtsJG`upX7`|Q61Ne_;K318(Ui6Ghg@^0Fvi?!?OS(uvl zXhf&@2>I`AmhmIE%AfYT&vxcgzcbseXcW|WJeopXTE$=a zu_|D?cGjimX`JLmOkOaK5LyUkA`NoF%T_0eOa6U6>;iM|%Y7Bk(B-F=ihtzeDN)9d zopeQCi@W`_cn-mD&oAeN3UR2@B3HlEt;8obUIq<50&KfAzA?yV`qrtEUEQmFTKYb< z<>bjw{&m4OSGon1W|5Y$r+L!FN)+2*=<(7Iy405Ic6C$ZzLhxgRF>N+d#~lMEsBPq z@WaAuZ}RZh<<9Q61%bV_zq6&@kh4^{M%Qg)VX-!XJE{zee?c;ofu!{#HE^ zJoZipU8BOxK^|dSMXz{`!uCCV-qxo(0|b6?Z|ij5{CI1}--8v!kJg1$#UVmTcAb5Da_Y+GE}0geT6vXx6I|331>xpqT8?Dh}eT)UzDcy%%pf27=PcH z9QN{x2a%S?vrWXlw59Gsbj>ML2+@Vwh8TnFl0?hzv6oDK4n;|DxAo*ZtX!HypR#$P z2C8E+=vp@>L~qb0$%w!YG+NDky~40>45}?p*1>i=Le!O=$YNL3JkF&rsut$djKZtrT+Fd1Lw&+)y7`(~ zBywL64VzlbtM!CE~13$Btp2rRSIwF0duh(ev-e71h~y@<1Fp|rI! zQxuG``KtJ14LahF%0`oG<7^d{r>{cH8{~7*b~@ zJ8xz$R_8buZk-g~ssDm!Ib&^_dIkG(bK>-`EP($w`Hc@C8+NQ=0@>HGz?Z0(m>CK+ zSVFA0MJpaeDFew&4K^9e-pN{7nK3=*chZ@NtvD8EGF8URKITd+**3=V4SR<*_5thT z*R9bEm+U9?E#IF$f;F*%dZzNz_W~F0_&+>Q-w>9Cs=v~$X)QOr6&1_QJ;c25e>5x_ zEB@sEs`xh&WsMg=06;NsC-aC6%x_Ko>FS#d1ZD3~%~XejmxCK?D8x*E!IM24GYTXv ztA3ps({e?}B~X|yFaSe-k*h4L;Q%J$hA>v`(Jp+7RV|E#kw0SNkYh*S?a)C#9xMB& zn>$fSf3FJi=(0FK10GA9K&JMW?)#tVJ8g9!5~NW)b~C}^=il4uKQ!Qk0ptMjvNc{F z`sWw@&quAHXoIf~v(Nhe^H2Z$oDBfeN(17>{)-0uk6Q6`?&fJ;6vzECBTpbX{Qxu) z=F0dLLAFh|j)7tLNi~?X@8mxKG1E!zh&y{66S?wp9b!%VtKg8};VHL0hTRE)V}?Bp zSCDG^!~FSAfw$U3J2SJUQS6FMZ|lu|r?wpAZ$MC3Ux7RGhi<^kkPQ>Wb^L*D4c!58;ce({0U zzkxw6Q}k1K+g#ZRRH}FiW?cTHH7K1x2c!Ym(rT-g*3NlJO=6tv*3>qa?45vCK+TJ@ z)~hphBEy~sAWJIw%Fuo3iNHMQAOkT7HoM!C_+@ub{tjRxFE%%z)~v$n3@D6z<%z&w zc?SyVAdJh}b9D;>xkg`g@3Vf8z5Usld<0Q5;QVyCuM)!EYES=CKbJMN6jHBG2t-Ff zHc}-b$W3{_tak6T?0-IDfKs=3<=b@#z^mp{FvkfI?F|@Rx>)S>m>y;yHCe8m%lpf} zz1;4Bpq}yD0yF9t;0_kv5k3AP>7-Ic_Zeg@JT?HTcgF8fb}hY%pYo37b&;QGW*;CW z9(GY5z=nk0Sx25jqFpLbY{;!<$p(7}AzcT>t#8?Z($l8;>heq{R`hhuO3q+$^B{X% z;_IInfo2eZ_5sqUZvY7~Y2x`a+TPjjcGbKVMniIB5wWcg4!$ zt01V&CY0=1A(hYX_CKO|n7n{LC?aD$QfIRRP?40y8~~$=lC__{_W}<-PBuWy?A-p~ zT{e6504PlLfU*y5z(a} z6?h=tB9L7Tmx{c%|KezV{(%_d4TBcr(rvb=;X4N_P>@;hM0WK!m=S8LR4lFl-aNS zz5Taz!FML;vQ=0Uz$w8iU71e$8cD+eHUFW&`&urUqVicc`WC^2Ci+M);p>t{AG(MI zzgiGRk-C=eNa*oDLJG9maF-;6?aq^Kjv^yJ|da^+}N7PN~h(CFPF7b4*J_;R_z4dE7qp_%%RM}9m4G+{-dm7Nk&jIp9Y*p z4l3OXYm)F;*ffZG^e$9(?qNCVWD@iYI^%yO5a3!tRSCFzU(kR$5x8gek+T$@{DD2 ztOwztL^@FSNYBuWZIrk(e~J$~7X;%}N6Q`NbE(RH&%^ap{`-(1{pNN6f$qy`x))pT zB8ZXY4F&*S9=8NEj17R??pbZY2X1N_SCYc~tjmMxtl@(YNAV|d7`aJqQehUutNVb~ z+>H_LTv1w=d7M*LRMygfFjEoFFdf_mL7E8o`JgQu!OWo-)*kH;ynEs@8#sNM9mpE| zAf4kLg4!2+pT}(Uf3gX`+@_3RuNElc ztnxRT;ov@0lt^1Y*cP68Ja*To3?v&qiwn4NS9(APw)9rr3jo-%C?*I+Sj@^vVn$i{ z+A}a>eh%y!cz`3Chaj=fkr6{Dqg&Kco2q{i`E5^@W_Iu6o^(heZ_t~G64b@d0tWRn zY{q0CI+1WXQ@9$W;>POhP_FOz+Vw3j^VF-A=N>L|Y%h&<4eF-vXOBaGp}X}`(2in< zXcP!VaM@c}@Wt+0K|u#g*BNuY35+jr6o@(48k(ns8H2dGu}`4X!>LNHVZNoCscYc< ztU?sT8z9z#q+9E)7oW>aBh97!SP1_m8u~YP0#c#48QY3o;7TUraqrWbjxyRYdqQHJ za%KK_c6i9{2pEItfW;TDp0P0MpJSrIsYSWtCEp1X7zd9^Q{*~eAE}hLNG>UOX&!G2(dqlmz^`k8cj3?0`V73N%>8@Yq1GM^`s5qxb+xY8T)U zOX8TEI|OH5%uYbLhe1T&W>_G2>4nnapV&iEteU0jf5jfkepW{M8h_fj`|w3nqT5)v zg}$>VAx%aPwf}ishFk5K?2?GZtr5e#!J1D437q#T!)Nff-WY~`X|GWA%UY5wEh`@j zYm?WPc-gojm!>LRIDanfg`$_F&#A~3OA^`O!{nlqUe@zAUD-*!hDQ?AJN@Topg+Fs zyel^9Y*&=%Rp-#^ITu_wKsvc&4aX?^#<>5NqY7%=PwZW8Ry<9AtJq zzX!^Q<%+DxlnXwM0gjU1@GmUA?AX9aJupt(#K9RIODX zfr6T%DxW{tYJZM zUWEFHjxB4-ru8JkV?$06hLrW~>xYYtUMB*+c|@O<7dld}sNhm<&) zj;9p(m9hxn-6@?Wdfe;u2uO;{bB-S<|8ttS;w8kadrGIk{eAB*$AA1?Vn*1oQ=iOm zoozRU3_nUjRXI*b7m1YFN?3mnu8 zhHnTlXFg@)vr`}auj_d)2Z`g!yPiQ$FCA@#*U@DNuc;aw_hww=Dfw+osW zIVc5q=5SYi962oz=Pi#u0Mn829K3nP$HlWCp{}%@}C!jr;p?x|zr8$5Q?1*(o z)V983E$^;OrJ^f;9!0&v&`SNyU!$O(Dc0Q1_;Z@eK}A1A^)=Smx2YL{k>1|>BXWjz zO*>1bwH#UMGC@t9)bTWnM~UeJxO#hymSuWUYIPV*2v{>alIBKbztx+iYuBj+?lRk2 z#DueC1d3g!T%2Av^`le*c71{-hCx5M)Rc?*eViB5!Y42vFG+*3I)*Ll?%YUUK3Zw$ z#ln?H)y63Q>;yp#sVSX{>F1_T;w7KUI7^{OsO9L4on^2WGF5->`m?fEzN!eLV@akI z$2daW9T~pPqT0tYye|m_6Mi+J4<8*0lK~%erG~e^jp~{|7J( zr{|xVF9t1q9*?XQLD1%xI#tcC;JT2G_R>nZ-<>&j{^t`{4w`Bj6x_DR#$WZm`&?d$ zl6-h-@OlSRZr2p+6V$)-7l!-!O-3zIzeUsY!g!_>$AFFnQN+y`yOE0%$-FT{P<8u= z^ixooDhgRU_3kYKJr7i5l$Os6fXjUVDW)uA}Cj9)&qv=a&_u_@Hwi zHisvorenNlo$*t1#7;`>HPjOGvL@nAbE~QxQ^C@~!74wy70WMoeN8IcNfqDoh`Z?E zUCQu{&6EA=J8j~#CDKNQ>^by7>lPiA)o+IpsZDN(GbWA;!}BlF&bS*_#-8|DnL33P zc(rD>wwI|9r|#vbcW&pSmC z&m}4|AapKEtv-8#YjGwdmARX!o$*h!;R6^?tY!b*vLGO)P_OqrIqvJ|w&NfU_@h$a zmoQT>IrAj4_og-B#1Jb_ZDzj9$1eMhw`9}t-Y1Py#9<+hBre@X`~B@|F3WF4=|gxghvoZD2a^NHQ9rTS8@ z%GFz<7UHmZtJtNZSL|_H8+{Z}3sUU@qu`}Ld88KYOL!r#}_#M$H#g*eFAQ`(~1 zRSb7G%a^CBIa}6Q48HiIKl^vCfm)K3u|3Wp_OCG0zvYd5VQ?dke993liA(q2azwgM z#YqtlIn*oPmi|)?`TL7VV@Sd9(-B`W|Nr}BAZF)5;+ClP8JAGIbR+@Z2kun^6hEs)Nd%{-Pm7HNOhuI6tstq zt-!ZOJ;)EqDhN3L{>G%@I<)c_bm9#)zGj(QTG~RviU$`&NNGZUKPK=%KS=xz4E<9O zf$N3}B6w`aAmGxR25x!J0?VHquUT5VYiyh(RCwea&&8nsNrKe*FC@rc|1TuSsV0S9 z^+dp&GXsiE2{euq>8NagD>M$B0ju}$0{v}OXk%Q0krD2}A2rn5RlGJxjSEz9c{8^T zD)ZlF=)EKuSBglpp_GB&Ku2hA+6|}xe47v}ZR@-W6v=$(??C(Z^0CmR@EJe~w7K>2 zeILVh_@Q1`YtsDNdhNJ{I-wPD*C(0x{0=&Ie?O7H??+c%TT{jHCzLB$b84k!?@gutpk>l)mEC=r3)dm?Z%;hllFHYh`$8Hk+jZ$bP?ssYmnRyj&X0Zjjx_sx&roC2{`Yz zt^G2Rq$u-vezZ>83gD9!??6D1Ebr?&S+N;7w9~K26qs9X{eIZ3(1xaNz{?iKDq@N@ znoJKnd>yDSW(HufbT30{PfyhUc|}$ELmLsv05$o5SGF=6#cE05>|Ls6|CR^}pEUcI zJOb3HZN++^5agwJ-(8hJ*$>xXL{4Mm^C!TwREdi|0p@Yl&7>uL_LoxbPVGM%*R!f4RYDwtpy)TbRN>TX_ z+u5=l+3Ott5=zAXZ=r+@ny5;0QTq{{F#LSU2pBhQ;qK0;YDlvKysNs2Yp|CD2%$E(N}ZGvL<@h|8!<7I*nR@=0e z^P(AaN0l-HCU|uNU?L+1VTs(&BEV98&K%(FJ$qxiCw@ok>I8cB4y!o-p*)sB zGaEagP5qm;plm$Q+lb^`14#oy>tt0L1R`K#Cz?`c3_L^l? z7pBas$3zZ_oX~*Jxlxgr3%=sdPx8M96&N9pC;gmUpka`kP`=|!ApKQcgV&i_tjZmf zMRzl8JPH;Dj+^iv!8n>9Tu(lHOsKdTQUPU3%>kc#aLKA(@|%_9_BJ2*p{&2$0x)P% zD|neCErW2z`&uU+T+52Lfl$HZl-(7&`Z~A)sE4J55%fLSmP402!1`>4Hre03x)S>5 z1+@sqr*-Cm^RwYU1(O-Mlz)sdP>(OD{vKlpB~4Kz|19j47e*P#w+!~1HLJ)y;|@+n zh1za1!4zXLAYD}MIJbb>BT8GeOD@H4sAHKp6#_hfIubJd-%v+NS3uGY*FgR=*u&{C zg0`y#8kN%``Ssc4^)}Q}$Raj?sEK>5XNLxj^=xEEwo`~MC_%XvcIFx>19wC0qu6$C z;Li$H&A-XW_F!~_Ob=Ri&<(k1{F7!lrzOt|5&hi^l!A08b;ogO8l2`8O+0^@3;>RZ zS}p$tFaT4`Xs1nO){5hb{Qd>+bMD0i%qIXG!RabO@tFZFCn|_KDF@+cyXcWs*hF1B z2>qmsJeAa|#?i(}=)`FwA9k3B`%<$`V7@jRa@vJc@#T)oqC&0?wSZw z2C;U8cQ^i$mcI3Rsv>deSmX#D*NdhC49#YL{Yrfr7Yz!alBVNDE#`xBaZ%N3AL&K` z1`wP=lfXBOjqIXdYXTHsJ4p?lA5%=d*?XK}4;8FN6F-~K@33eVYpp4XVr1W|SV5Aq zQB!RauCLz)$`u-%>tW0u0L(kcqBd zHj*(omB#e>hH#UMsY(5FzX5A5Uo8lL7XIFE2)Cj5N!1s`p9<0V-}?;=CJfN^5`~5l z^5Cx6|I9bQf%yhR{-dy|h}YOFubn!sd+Z>%4Zp`5(sdEP{vL0rVjw@0qCHkgk~ghl zs-#v%HF@4gRPs*9x3tJO&KcM6Ld)h0eB7ehp3TV(kWQ7Qtk_wOsRSM2mq5U`7~gRD ze$J$z^$WQIvCFcLZ!%ZGuvqHEJ6d(=3a35Li>I*145xweNE7P2p z=Wy9Elrcm9{V_wwb&SJlGS}Ff-ucuXgnGUZ-K;NZeX2bA|VO z1ELp@aL}k|cKNCCdEOIPy`~yx!^r2pto}`W$6*X{ieDW%h?4iIjumD*8?8&%crd=h zv|Rhvr|#J?%xkwWT+<|(3}CswaMH_?L-!Qbn7X5_iC$_H$tG64`zJm~*X)I^EFwz@ zq5kX4uIaLr&WFJzq$?ukzFuHdM-}Kcm(c_)Gphck;CauOsI1p{hV?KGE%U=SvWavA z7LgDeQY^X3hepy7~vO^tiq4mH4VR#R5>^&@AiJzUo( zF*}BF?J3t#mwn-}0HV+C^zuEpB=VmxwAJ7)2kE-=MzM zbZl5T$28aTj22~Dpg3z(~G2IJ1Hvu5o#f4(Z;wufG>dEjKqPL4wWun zvMjIlqcm8guhK6omVQ^iPHwVbX5dF|^|3DmaUO)ISf!LzPKm|*WwJP9ZPs?49h63} zps0SMODR&<=js;bZy2=8v82$cnGX$!4T#)O`t{-|AI7(9fumG-EWLa<#mtp+f|<3x z;=blhlT*NP^yYD7Fci4nZ{ReGj$MVGn~W1^+^9nzf2U@?QM3dEy{(^210ZF z4|PeiMJ{Dr^Oy6hqD&29?$+fs``8a#xX{Bc8Dy3*haB&ULI{YJI!-Y?6;o@@Q>LDC z)t5A(Xgo%7U!U951j5Cr8A15;xVC@RyEFRCrb#B#@gIERbNIiF&Zw*C7^fDVb-?l$CcQVi$?W9h1z z+H!17(i6o~jGAfBkBpCMN|;x8&Fn(fQ(n1mSvOzI7^YDn|8sd_poePaS_WiNa6LL2 z-;9>9O;UaO`KlHnEWXRCasSCKmp-1}j%|-Mu7Ejjj3UW&m6x_76^Nz8Jl!Sxb9y31 z{!XMDJ3hzqSb?OW;z>qg9oA5ogO9s?i5c`DgXW82Ufh8qpJ5z z3Z%Nmx#n*z1NQLeJ7N($*rk}fRv0L5Tu*b10477Fyahv7?&yAnKXMk4J7USl{_GId zpDCJ5vNu&u5gQ$xxdqag*>KQV?5?A_{7OYYri7rDDIZT{wMeOH)NpM#tI9PHgxphlC0>`S#Y&4(Z%_YiJl283&N$M$Am~ zE&a!x2R$ffs6W5fLYmw*CfWW}3^b)-i}W`Prih|CkeZLhR4omMb~SxL?4vodj(^s1 z-aLa^R&&cT!n0W?1>Wv=X=+isq7xB@a1ggqf+Lbyt!`m~vpGZSV4&s=(Zkwb_Wu&` zLfS7uoPz1=T9Zj~Qw({#)>Mk}!Iur~d4T!jxEpKEOs$zQu*H!-=HoYf%UDLT38Pg< zIR`1a8>n3AR)76`!KU+TNo1h=feWJbbGr)bU24rjq4Ub9GY_Lzb-zwL{&~;}X^$5N z5`_xxfh^-cZ+ic_H(HhxfhIfJq+Lf0|EZz;<6x>s5e?_^Eqde0KYwR`SDjQSK=7lW zM}3eV)aL&hYx>VmfI=#7-dcsZZ(h9tV!>%-e z5wte|s&ib8I#y4gd75!BhUVdMg#dBp8bQY51} zhqEYvC|A27g6#QI%8FL#xCJ$0JvjWvt1go&e|Ijjj|I7q-zyPd(6=2!l2ED7U-(AB z` !Vbrr6npiCXob8%sdiK>bvl!^f9x(rcihon}fBpO%BsC0-JasVn3vLdyS1o^; zfz*`~XpfnJIf7@DqxZn_E^Gw1ueU#CptBptNZ405_p?n{x;mjdUdve0_{AKrvt*S5 z^sk;#(N@vYJ}W%@28WCTNB&Tl3&Q{%eOAmcMm$=M6ak66oKypw_5DYFTngU-CiH0W zx7iTY;CQ&(Hia#3uk%#C`2*(zPvq00>&0X~_Qi?Yy@`TRE_U!kDm+^wC?c!&5ZwGt z^#jA3jdg;^m*9fX+U^LQlj`^)9J&Feo3mm6P8>kJ@8#=!ZzwCzF_e{f$$0N6mLoKT z)6dPS4LHptCD0bw6ma&FttO^2%H0FMdC_5eDUeb0o(AKeYy}uMp;?!@%dSfY-kqECgce zOSwlMLe3+n8mKPyAy(iGu`Cm5we(~A9TZvvMf*oiO?cn*ddr^Ze{Xg*NTA27qiE0i z1Zp-^1v4Pk)26y<7i~KSIzGR%3T#mxy;tC!y5VM|ao9cU;;LqOHggw8rk-&fuJx^uaE+gKj@Yu!e5 zR(ix%%~rk)6ugSKwM_NKR;!pwfzg^^y)Yr#FT-XA9&rg2zDFgQ(J-F>cq}18DE$%y zZ(h1_q`%|6KJ4gdTPPqsloi%}`2p%L6{7O2M^CmvpLBCzZD7J#mq>T$!_U^RewVcn z1DB89*9G3i(29@4ldKmn_5nq5JFPIyIvh&0pMn;C1VAsZ1bTvXh{XE|n2;|Yg`I>O zAgL20O5t(cY@4C2bi8+1f<~B%?x6BlR^q1UTs?S=Qh!O*S#dAIWxj=O*cF2uLV>zo z0M4<@(+hmvz6%Gv9cOS1iYONV1+iGXn5-7ybTZmlIVg2Tx_O3i>=;r44qeERUOkk6 zh)7`6k*wENBA|8QanZDt{t4<@yka*YoCpx`)@L~E0oMyHAd;FGs{ zr}0Rd*{{7iosSYD5QCJ7QLppQJL_=@@;5#QNBW0GURtx(lDeRXxEnrVW3mU5F$vwC znrw;j9U|<04|>xpKWeIc>mVy~vQKFSqAg#nUoci5;VO%W*2$>r2(@FR3}`fJK9jV~ z{RTv`Je*$gRhH^NS!HV-K5bC@%n!C-a@@EXB3HDjCx9%eZCYxJiyaYw1=p^5f!0+~b@| zNyJ??Uc=_vD(Rq}Bu(Q51U8A0o9!Q+W$zQP;C*~wP{62=w?j{sXV9RR))2Bc=%|Dv zl>`2mbPSFjh0R!gs+Br4upkXN{tDI7wl6{Tjs^1=T4~$}f|vv4%$_Ry{es^YLduuD z2$Ymy2_=zxp?=Q{Z7tG9a630HR?i+Zl@yBn#wfF;T&6}z9KH(oAL5A*(>=$WYJu*M zl9!)jJbpOJ7+?cX|07}fw?V{ig^eIC7@Nc*-zX`jDLz2^URBdpJZ*nb*(3r{l!XTQ z)%*u&5^7sn$=Kiuyk4H)=CX%&oYoZVWkB^{<5A(?myqT=ACM#^9oNJou`^7OQL0#^3z~uJH_pWrC?|%q4@i>7%*pS-@w+7PJUtAq~nZYyl#6k28O^{26Q3@pg1$wIO8oTFmRjASV8X3JM$V> z$p$Go7!5wcc%I)0Eik5*HCq=whGUkX=dmfh;ET;CYe;8$?UlG}Uo=C0A{*h0vAwig zEnLGEwyZCRBl4cRE(%820J>M&wOJ&zWk;9V&eI$+x;5n7sHh!IC5rhbxO0>r8~?Ra zp9`&I5oJ>R>k0%QNfB|`Q3f+8R!z!i2Tw{k$)uumJSP-z(aKkl2QN~qoh48Sjj5tI z#M1t57$-MgqjkbuVVveUqH^+}6?8=d$Iy8}?JAC8Sy60Yr6L;MvOYCvnz&issRHCC zao{HjUl8ZK|Lo{Pxc(x7XTxLuCT<&49xyy2+D_k`NW%s5A96oQTO8$QyXPh;^8`Fd znOIeps>EdT^E0T1D9z?eh)E?mZH5|o&HHt_y!@=9EPTx{tdA0FSzXJMwq_CM0lV45vslm zoRa1geoP!Uu?A9nchnL3C}+Ueby;1px7E;F4BGLgbatJqX@!?%KRgs?KCmDmS`1{0 zN9#pan@YLQ?MoN!DnH7xT(h8ifD%fmA|ZUJt(|0xw>WY|gYvlN6&7pC;p5Kfjwwl( zc?ftf%v4Bvxs*$bFVywHFpZD8Bd-kGE_axv4M^aaLaVR?I5bG&;$12__!TJTFG&N~ zrCeiRP;m!Zu{}0U8863b;`y2{b4YG!L%FPm5l6_^B*s)9X$xP6(u`zS>naaa$NA); z%?+&RwzApWGHeRZJytylt3sa)@G++qAdZQ)RC0d7dJ2_3PyLuJYKRblsvWSs98$Yh0b;qWieLxI|R(`sD zU!?)n%&9lRgd@-*q!m+9$k8Bea$-KBo2a4A)HePR?DxtmKHl0OQ&vFVD;J?ERs2y0 z-D35gX@Rcs@>M3c{@&xZ&ItC-MqlfGm>Xidxt6(2o$4G@WSNm?5)7iAvqnio;-L$1 zKRF{W)tfA0++*042lv=DheB*h*|Lu^UODpX3ZY!R5+2R0J*y~CGaCf=1nHM4H_PF| zn^(@jCo+W7*n+Sgifh&Lp*eGF+#m88Oe~;QQEyW;0KchCpud@WrgpERMPa{4#T(+B zHjW}{SEO)WS3?yk8HmAecMU!$eI2D?YmC?MZ$nm3Z3Qim-e7(Weq_b* zM(W(YHCjcqkJ~@ay6;T1BBlu58S){?MbLz6CO~={c*n^l04SE zYa}ZSk#}>EIcQrO176_S{WYpx7nYm&xWMIvn5`Dl?vIoGH4KwwA$Rb|L1kz+m%*rP zyW#B^D)TpV_`3^&=_ctGdv3wNjHz`xP-+%h{NJ$DoXXALyRYT>Zl}}74&%a1 zUUtPj(z8LKdK6)sN6Okmo_AVi?P+f2XOAmI?DX9O>2a*(`?hG4qHtt`9#*lHysxVm zp`@4rgZ=f=_-7~ucgm$kL?5tX-s%lr258|0j74ru^|o^q(8&m2j&(4(J_dAB_ft&@J_xTxBd zIg~Kn>x!l1T=k_TaHy^PI&~z5b0^Yl-uF0YVaac!z%Ofw8;^1}1m63-gd!0+KXSeH^et;jBQL_2tke?a0 zpKzbzYVuERTqB zjkcYkEyrohRd^6jG*48AEfMMntx%*Oq0vAssUkWgvRy~BRKN(*+9#t-`w}j$`{gr+ zY|=1Xm;J=|RD{QU3Fh%_&D^^!lNP>jCm?-?tJ#w-2eX;bQ= zXnc29FIeHXLZH+gJZF}9EgBNer8KaU8+94HCWa3#P>oTL?{hFJ*rWaC$XlpLu)^*~ z%?JeWHoTL8U!r;SsGh`Q4B=x=2Dq2Y_MV+tY6#H<5}F)7$v`%K6mArz7`p;1WQT$; zInaFdLsELoqwybdg)+kP_0^|5FW<=Z zClW;DF<>mO;~e0!Or37a-`(l8qasm|q|BTMyqT1^DYA6^wR_fe@8h2I1usgWYoXE$ zHH&K@s88T*YgWs)P1A}%rH#yEXiRt{^isM_lF;N3+X z=4w_F99Q70q&@({kM+K22zEjc$h z8n%~nhgd}CkS9Tm1*t>NqHY50UpLsEt<<_FtU+(T`C$B>=)Ok(fLrrn88=4p5yClt zvCR7;tH-Ro{1cRRvV|LBv&&j}4k2Ps^2*N@*{V7F$Q5;R#dux#T!rxcgE1|$(Dg*) zpF9eV7wx@KAj_|J8FcWpwadsl@IvY1@{I`23ZPZy7Hxh^){@^G6S)J|l|YJbFf=UYwJ3v@tR&v~bDyPY_z(%~ch z{CY`}eOoQ)HBXs&Yba`!-3GsoP|t;dyv=6SkWA8ou2wmzGel%Nplk^Kc$70H;vqJh8fq=cxEVoNi}uFIXmqbLNOL&c?=k{Ql*Z+XQSZVcw4&st*g zoBpbz@;pqTA5`%6ZHdzCkk&#miAP3mJbu;}oq(;&>>-@};@sd1IRsHXEXde^wliE(j77zeFvnjdcg^+1 zXz9_8QZ|CUV~>E#h?okZcW4j?%hnE(T%+`!f~vcVd`-YA9p zp5FjlXaH555k)_=SbXU-2V6q!CI&BZD=+>KPYAU=%nv+32SQEuy7(3{Y)1l><{&g~*kj`g-BEOu z)^OJ{uc84L)I>6xzy;@V^J*W9m}@yYw%$%iI`)N$jEYbv`cWUOhX|?|K3&8iH5QMe zVjt7XC#|`pN^1S}3nm5+)?T-{{3P9>n&-!oNZcWD&GPI+KGtRY3}kzbD9X-Zxb2mTuMoy!hx=UKA~F4n+Hi;xRSn*q4@q)qc&!_< zId@(BZs#H=w%4O1SKOPsb^Q`> zYJ6+13uKNQW!|C#F<*K*Qw(KS@vW+o^mN0;>c|JuXJ-AI8VvUIE_adnhwM)bYE6O{ zQn%eZ`KEJhxHAQW%%rwh3XWjbM){Y!s&QSq$Pl;;;RTB(9M(Vm#6D@~9YDkiRJ$d< zy6^e$lax8y^Tf6>cWF~dYdA;zk`UF?U+0gfYR8W=J1+b89{yRJ{Dm@T$KlUWhTmS= zbEuEz=b-g^4*q_}4Q%&iI=nIx(-%!f&?8!Uc zbv$auYr4jtYCxFY&RJ6k)u1yL>QS3NkeFKO=m~zahmzm$A31sms5P z4e4RMDP%s{ru|^kGszmcYml0$YJ9WWj29YA&F0p5m{+=aqiP*?&-lJvN#YcawOGv6 zt~e>eK8-emxhqExrMIEBEEv}Kf#|Tp;$|JZAPsYkFdO=@6qQl`s+`YZ95J(~aEikn z?F+*Qe_2J<7O<1W^P&i0_1o9yJ+8FBcq-hy!Z3N@E($+e&QB`f!;kMMi8c8?hCEab z%-PFc`_2`~Ehy$rZvW7`n*x7CGWojoUYtFbNaf|NCILRzj&XC0wHNLBr`u-j`>ota zm!7Djb;(V32|E2FsmJ0Ua_Dw;QoLPn*Y=w-xw=zUz1EfGH(>~m;I-Lx#J|vHepfFm z^BS8LT$p0qQxn*s{#ELj7Gs!HN&PsQ_k=afV0*n?XskScFi(Vy+MqR6Bl} zsM;!nmaQU5{a_LMJ_j7ti+qm@pm&Gi?K;C3b7xI6W>ZQmxqD)BHfftJm^BF2ZwNE@ zEH&I0R8w8%L~PUdY8|FV?NLeS${H|Yl48#LJ`Lg(-gtNl5hAA{#2>g7D=vLFtU6iP zG%dRlNST{(9&Nsn-Dm$ncfjAd#_&qZ@_p)En1{-@cd?VheO%M=ep!^qaL$>5AsnY! zMxIELLi)CxZ?t0Km+_Qjn(2;HPl(qhviGSt;>Y;--0zSkxg9Y&?mT#?#GUYNV7KtVKI4a|MY!P?o&4bSF(vZ?zsJW6x!aR?M!UNr zgqiz^Hx%=*O=M@ofj^1umh9xfQS%TFj+*WLy>jzU95^JHFjyF6RPpA#@z4&JE6iAV z#^SFMo-tP}C@lgG(s^VMeyr<;+(-peXJK7TIDYcUcZIHmt6xRY(sSoR23`1{ea}*? z@b?izWt5L6Xm79`!N-Zhmb5d_XBNB<6`~Jartmc&SFmmv z?1NVBukoN+#8U;&9Tf`L3()S@I~$Ag;y9bBTN4C!FO`9BG!*0Z`bzc-G!zV7imDL2 z{DKy_bCz-ooi3e%{$mH8*+uva;AY-c2VZ`;1mu>Df#-MEg(U(5ty=;IMcFPkA2fEr8>Oax$O0r_FS;t58D4jX4$%CEtKFUx8Rv;xTsK!f9X zF6JPxBzt=a;?tVYrS_1eV+DmDYk&eUwF73Ay=uVp4`u5qQ5WzMY`RDU(|wq&fkl{4 z9dGaiq1^`jtNE?PBXRVoE3pYM1OH7?^cUF@Cl~YPaO#Yg5)V5r3?UM9U&+qx>YVio z!!WHDI%^MrXXQgV%zz|0)v%S3D(^hp2E}mEFwFb2h?AK zgZDEA21_Y){p}Pq+z&FtNtS{QnveR}_@kqmncHrT6U+sVW1@1U4nXxRVI*Y0b5~=j z*a_f`%z(942J+=wRC@cl-Ru8$*b+E^!!`<-jv4W67oK2`DtV_3uri;TnF{7c;Npks z$^(J`b1}#svYlN3VnQ*r47(0LQeir0{GJS%M)npuxipps?WTtovsc%FhY3Ft!DRML zsp8;RJUzVYV>v5^?LRy=TM#~v+EF>6O45ui&SlA&?Gjl0xS|ja|D7B7#&NzF&Al!{ zV4rUMLz>HK$W$7SeKFKF^HJvp@PGMMKNpb6r@3ptaEXak0>&uFLu+<7Oh^!_LtVIT zJys_CLE!SbEa-1s;SH+|Q_Jc2n6oqwN*P()3i-+i#={e#-) zRXZT0Esg5*$lAl%1x!}SnNL7BeoQNV7DK_Bh+Rwqh2~bzhIr(sQ;e^g9j%np#b!l* zUvrmdL)+sK4nYQA=N6R8ZUPcz zhVtVa=^akLa<4nEI`=ZLjn+(FkCHckKZ0wn5Yx6MN-VDpTw8Mk%z%p~-+L^Sh6tXN zI%lABVe0TrfE|C zZm(uM!(WSXaKCZ-0*{wl3+(!T8)r2u;*s&d1m+VjNso48PCo|71ey8zOXxRSeS5zI zlHfSsbxiXTRvmWV+(&V*24nNQMN`g?GbGue7~7qwR77^a;HcH0{j$`9nej_ z7Lb~eKa0g9uZdY4r}jI1B8jG*v>i7AIs&QJ&H#Fl`xD`YvWk+R>n~pomEjLx?Op$W z@zt_tUDAJQgZfP|Ft5ptItwMao}`r_XO9sUvKFLSr+Txc&js`QRvcY?h`OTG2<4A% zL^8f$j~WshQ8Zs-YWf+wjuT#Lw3*`R*#@F9v7%9bcxxm1e|c*~;qKnh^-91LILJGb zcRxb*eE5ALtFHB|%oK&c7|z*#uH~!6fS)bvS7HNbJT(_+Y(3{ z6IyPHGYhXi2#Qd5Hd#Hm*rpt-tz!K1mZFW6g$m{-Nk!#?_K6T+bre5u*_PXGP!GLj z8)S%M%Kf+jystsvebxTM`+64DR=p;;V}_O!YkLT0-Wch&ptf0o%MD{~c&i&9W4-bR zT0E_;c43q#($ErK3OmeR0~sGoB_n>Zby*G=TWXeUh#TOi81Qnfqiq1`vN-!hSj(V; zHkVuFPV9zafW$JCcUj65D;Y6`X|9%W!BerC@FRxe+YnKQ>qvsTuoF1;`0?MP0tH91Ic3!akZ*?V;~f z?@DDQ$h`rk`}MkR)=!x)s`5*|BwB-o-CDB(7)EK?6)4TVr#K2MEIf7p z@xhckZXhUs_+Z&2P#?_DUrHQrmL-GdfB0ZCW7SL33g8^v%EAPBONhFO7JE%Ap!Bj# zDvHAzLOz1>D6y9Zk`eF5W{Ti?o{+ZDd}sngl^Wx3Sl0~x@^YwHk2nQ6sD&-rg86lC zxG)~$u{kR?v-=oWrClW`obpVwy4n<;CEAZL#OR;9axy_vLsK#lQ{DRy#H*wy0IR$G)%}8ub zplHXXz13AHer~$0_V_aBB*;Po@K&(e?89wLi&N{}90SnhC1q?YdJ6>6=>F)`MPxT<(g?R(x3r{?pT3c0ZaLGj880c@+6z0Fsn zXzCvY6sR5PXo%lF5)$g!~MJ_viWv4-)|X(13V z)rUeP@Ey!Wq5F^NTX7kDtZN#0B5U{qae>G$QJDSj#WEh^O}$gBi3yHQN)F~f6_+{T zg>dw|p3Gpy&2Swm`XS=kvW96Kz+I5K%G5_^^{<*2SX*SwDs=r1xPO ztP_ix>@PI*f743~c{IR+n4$OJOXdxF1pD)o3*_qlkLACVKlN*lPO#gM@5po2&qQC% zy!M{psghXyEbX{(6vfIiga7P2f`H9cH=SMTKb3?d99Rf zCMNvhNg9exxDNr_w5z4A;g^u`qsTxTI6G1sO|@M@cAZtQKm4oE^mdm%hmqxTWPew? zv(~Pmj^{|K=sNLL8JZzUkkud8w_qo`P@DYdq|3mc!ab3ZT}3E(k@UC6gGDlE0~G|u zZE`gD1(9}^zn@huwWNnLqI@q4DVU|RXy0DuT1mHwV;gEKFiO>0i5)s}fo0!jo^t9)zY8+R&cFQ(};X<`*i; z@6TiN)P4$zEhf&;x2(Dh%2sIjxDFVSGAegq+cZ^G$Vdo9g$4qP@{HM4|AA8kV)uYP zqy9&&>kw{EJInz6W%%!vmTP`w^;>6Xi^l8ry215I!L(i(%)=ougWnS53HLZMvbD4G zQ6OTr1{~sZ=0Tl2qYv7HB43}%?)2?q*q)0SvOF|^pdH8W>~D?jsFnXSNEK5d`t@XKZlH775Lr}v%2`aa73{tbb zyCycBJ~Kb`ibE;ZQN1V0A1@X!5nnr05(^9(YIF1Syf5L~lwGP>OUuM|o<;BD-3;PK zCC}q5$iGU;w4Uqr-o)>a%x(`xc_$Vlbl5&mTZ?i=x@PvhK5+uu>5_6iyr%y9H0^w~ zrTTqJc?n6}lzzm!<{6dk-5Jy?5)zrEt#26pZ}L zL~POBrD!7<-rwAt?swxswL3}Jgk9>5;ItLS!oUJ{2vLsU9U zi<%Q<8`0R~&xVSI!5Gu#vUUo4LQy7+O}`fxn?B)SJG%R{aWo54iOwiDVRJj}XrbOj z3&N&UXOeI7R?I{ou9tKehhadl*U#Mb!az1V(ze#cSD6MYJH*b2R*0EAK+LIn^m46$ zpXlqAhxV0_)O95uBI}PMZ^$RxR?}TxB+AFZwr6&DUF}E%Lc%$6JOI@kQe`Njjy?VIu>y6yd3c@DY4q^oMk{=_}$m7~|P21p58VwVi1M^VpPAETWOx4CJF-fh7p_$`BqHh;8>h`JgEq^%?kn5c@6AMO-SwD0y`pC|#oET))M5q#?EHwp))w9Q z^7#^L^O7fHib%P3@96>R;pa`4_!myEnKI`Gcm#=DSr8vhhpL$`k}wCtCYNrqB<%%c zLOQQ%wA`W_smX38^5SHbjp_b89Bw1rP}r7yuGT0oyCQxI z5GS3#>pQ-rD0r}u=^OiWUz=s5pIznJxHMOb?hmsD{;71s1oa~>nimkgkr4H_&AS8J z4z{Wn_5MM$x7mdm4Xf{iWgotTSl}O%liGx`4)?~!zR%@QE*FFPG*}TyOc8B7`uU>d z&he!3@b;z`?FZ`uLsp$X(nYHHBgW*?1HJ7sC7^e9ls4n%`kRoR&B5Q#4b*zaFsTU6F|r zRm=3Kd~h!x6kf#2K_Yg5AA@1KSK{1wtc!_qnBnR-S~>{$zLLtt7>~_{IOUNHQERq^ zM|#iX>?hJ}6e3BvXP>JoWy;4gJ@fuckb4M+>)Uo_XjWug|uZZ-j6eBy6G)Dw96IZ6n|CtBN5SeCRq!_>afp3U0rT zL99V9iHXtZjabH5TvR42h7S@f-A-LP)4r>g=migpNPkd3l4O8rrv^`qi8BAhKtsDr zA011$!6k;*|2A2Yq(lB?vfw+ED^X`Z3olmun0@Cq%RTbp(1VQli2?UWjiq=aH!Q@= zYlo{1Ux>%ml_1t9TJjm4Ted&8w2-L}8+NYrmT7$qCnlS))|pq={pJG$nqULbCCFCH ze)7q)ytp+pgncDrs4dNglslCZjQI#n@qHeOqI*VJE_^mOZ;j^sq&mWjJ*#~=9#(ir z&$cyeFq!vwWvRSj`-nvCu_POxX1WwYsA(9a9F9i1uvhgw&I&RRkQ7@tO!uTZ| zYxTBR%hHnYkGiUxYkSY!b@ZeQE4W_VOmQR0sFEz_-;*z$ZDlqMJmD#@lBliBM|eUN zL*ubL!4iWjR9$MkeM*7Na-$bMPkSjoiD?~rv)SBxiOq8l_>MmHYjj;a*9CD$ZZpGJMOt>|mX-Eqt|(M_~@NF<57bwT2s$-rsN zv)@BCOWo76M(;?BUso>f*usNkZ;;97Wt=VG=*oiSSnQW05~w!OOwHUknY=$JThlW6 z=exunf~?^Kp?P8YTV@0XD+CwhqTsq$>Z3wE&#T4XG=D3M@Yu`?>tH$UkD^Kl)IF5_ zu@%cJn78;ZUjUaHMn(A}-sbRN3-q#(Ec}8})lkz+8`5TWURqauDczsieX`|4Q)aA` zNZ|X0Ju5Q|bItDHAI<_2j0_SC6-;w(_$yQ6zx{?kuQ7ids$`LUwq#2j=rsSEGx1wI zhx5^Yz)S}FI_2$u|BwmMkC66>&Wd=Z|L#3d{eORfKg@^wP3yb-qmkB-?IwT6Aaa0Z~9eK)MBK3F+=(KpMXH;O^Ny=j{1?et)j7 z4OeEKdFH;J>v~I`!gTqHwZY5_#gLa(+u8e7wY}MeXX_-@u#)ZQJ`67vi>_q-H+smE zS%7^hI0ntx*SS1_mn{iH2mn3ce-=@eVM|vw!e^LLSsOM;e_89?Dl)tI3>H1iXB`_G zyhTDO;0$tz(tDX?z8H2oidb(N-_W4+mG}0c-x!D4b*uHG|wSRNHUl!xN zW7?ZSx#D|VX7u#tknKkO3Oo~9J^{u|w!i|*Lh&5<*`Scr-#BWPSe9EX+7 zh9$4FZ_cu}U`g0xyPIEj=+(KK*%iO)u7Loeavbg2NJHd)eKV5Wtm(b_$f!y%6hMm4 zPj4=VZZf7}%RU91ulVLczxO}TDad0n*bQ6Pq$Xzt?DM%uyq}lB6w<% z<^IC)xvC;im3E!)W%x%zrAcfOudi;w1ki29!MUp8nc|T1hD)WV!7*s18FzSs=hdxT zTJ9njJpAbd;~2|3Sy&>c#!H_sv(8UpgFGJKk!K^Df0mBw075Ao9kY0_;j>iU|pIa z&7`4lkAof{ZR!35fqL9y*ZZ^O8nEndJSOL`5;pyFtu!=AC* z+=4D>cEmHVel4`jz8j3fmU5m6G7I?%F<^iS@2SO0SalgagT!F$L;l>&dF2i9c&{wR z7BMakL2&=^q*3+T@KyhsvHKd{ z{u~HL$P``&b)9|3EGYJ~MCjo-^=}0QdT|byK>SD+le#bMf6@>&50G31O@AB*LR|^^ zlF$3ioSKC5LHMk2Loa5(9OY2uV-My~O)8(jUIC*`pARDt#wktqmi2{U5>Pmzo$52?=hVxQN z%?XWWn8PLFb$H#h3@L)qX&XVjcpVT5i%icb1_ME%w(=Y-Og$FZA;-Ck&y;`15WI=< zq27Xcx~6T|17UH~JoLaB)@jfjo6?pqf&4{BD1!xyf*3zM?#v(@6N@yl(F1za@^?u+ zrtxXFTUr8X37516v7)dOWe`46YjTTgWf5S}jVfTXTnBxh+9#eS%OQU$Mw>SkqNmXg zLoW|JPZvWHaNHoB1X_)4q07bqkjOSNkcbydH8JntKZ#Rw>bPEx5jETedXlB>`>HNt zCg2(QZj}_NPqcy6D$!S%0apMWn}?q(7`R3u5+i+x3C;3gi4URhaii17kC4uh)s6XpdSrzv4VY0ig)YV4u1#6eU-E7N^U!2={+54PIhn6jAp*WaHa z=;I8PRR0gv2!#$FYrBm9HOK`O$i8uMgWiUT{#1=P*LXv+-5EJccVZ!`Y(c$^8qb>Y zuzHg_s@k|`D&PLz53qXn0^s0>&fX-qT@UPtg0m8~Di!3K6)Ok|M_ z9TR%PW`P%P@;ERWbVF#-`S9W#Pam0yk?gJI)*x3V`~=X;5Vk5;U`=@3Q|R}}MeK1J zJoj26)CP+6!bXz3b@J2+B|Jx6y%(M-(f+k4wC8deqvqEx09SH`tv$NIH#^%Gpm3Bx z>al{|j-pe>&$qSxMDI%jID_VlPGB**n4Y{JY(_t|nq^f{|6)nUd#U<^;lg69uR)!s zq?}O?5g%jwlw5NzEVv>!$j|UoWW`!w>aYmiRjOen3kq%`KUdMj9IwN|Yn3}%wP6iM zIfdKHDg2BZ*uh#ox`VpWI~6su&e*S4P$MBY?!;E^v-!=L(T2||%dT(02Gc}f)2|fM zl@r)*o3l>x4)0p7t90h6!bBQmGY#E_lVi(rMj9y+>Ms(h;`C#7cLWgzC<>URx3iC_ zidbRSYr{luv1GB8C(1_x9)8Dq;m*0R`#Unf_3G?2kKmC5@Ovxp7)_r`%qpvqW%)F~ zxhamOn|1vnckpJ&%Cqy4VO-+rrS?ulv98cNnG-d@CzJ41&}$Ie+n%1B;SAw+q}B$$H)38xuEkj9g+1MpU{JNPo!Ynwr8T%2f(n?6!J zoNHC3Lp1MlxUd*L8lkyIF}4a7Q8ap0&qXzyFz2)dj+ZP?zuqXC$(+)Sty0gE36cLM zaIq!j6}2?(web(#x^vm=NC-?2b}l(m3Xg|KM9Ov5gL&s^c`BJ{3@EVJSRphbAKaYs zRQDu>j*}pL9|{rF`2rGwW@(g7%ADxUblSJ6caU$z zKpW9o?rm9_9Z~KJ6nXh5B;ZV*=)ja6!IDD%8=Jg?Dgjc(qCO-P0qYB7AWYm5xZZ^n zUM6L`#poxE(S=vFJ`K-|j)NRpdzS1&E!r4U<~S&8pb`}4s`Aias-*#AWA}`JMN9HP z@oZo(wbx4yIvRR`&uZmxOd8Z)TX*oFNjfP##w)Lvir{flZ1I1U3a zDJj>_mpaOBcFb*D;lUBQztZJ=kYGMm_7<+p`>O@lI<;rzk51n*x9ry|TBrLCM;Ed% zGrfz%3O)yKfb@^b`rBD%l@1+~)8-Mg5gBp1w%LAG%7LayI8i4BrOS6O)~-}v>475( zs|aQ)FC`!QE=uSn$UNC{dXzh5l>FUAf@i@HSuc+}m#u)&4;9WK2&`vJn#2B|@lwT-CAJenpTiI>+S6DiQyYwzL zDqpzF^6U4S3D7o85Jw`%Rt1&UcU7qtgLBvoY2y!ft(T}uZ1dP7S&-}rWAHrY_{GH= zNb|#<8#0(PSt$)wg}!!c4Q8~@&igS*j>h}WbnbE;St>(Fh2EPMaGwZ;y?b&m@}Vn= z6bcq|#f|Jvl)nQY^(v<47^N^*iKXV{pZq7uzk~1-B@Mal-}n!+<{$j$4k6$_LCJvs zF#5|PQ2w3&{Fkc~_{dxUMKoMIBCAAYWpKg)?e53@A{MdpsbC8dq&`kQA}MzRH3srY zHbduK_Q^KO;SkTU;aZ!mA}b;W^6UjloF<7lrYD97o49n}PRGKSIi&YE0>L+=^hCj~ z)R#FJ2@6C>*q6OnI{0fZu6_J?h{jxuaNs#7p+_0KI4%q%vsOKq+ofMONpwuYdd1E-ijhN7axfw+4#!_Txd;Ogc#sS^S^gbuQl?h zIxk9gg}x4=iG}xDZqr>)IlCW2%QPdSv~6TWwEj(H`yMg_M){0vAV&5Q(P`L=gr;J2 z*YGbjSiW2%2Z9#3BABoVjaX7yQrwlxGQ;CizVRrPA z^!-wh{VaGj%b7i|mU{s|HAE#bH&LQQ@ZN}b@5W_z6Uly=@%f5wSugW8d7fT8O;^1t z`05SBI-?V#Ltm{9^dKQkl>_xU=4S|;d6ZmUjdi`xS-2m@=+{f@*u^F8J*OH9Q+@kd z5htJVmzt4h2EzvwImysYaWWSx-%QC_Mm(W!(+`QCsNp`qG|!5srIsw#Ep~F8FYMtk zp^z{2aC=eH>yHgYB`H*6ASw+W{vj&)*rw>t5^5SAcHG$^#*KI=rO@%@%Mq@_s})+b z&KC4WsFTfwx^vqHe10?iu$M|9glD9L)VBC?=-KNEPS01=>m;-Do4Q8Zm|nfYkx+k0 zq_mhDbk{I{va71lK-zIcN`wHN$28w0aPw+^2CfZDO|MRQmmgE zy}dnC>l3bSpVP|g#+pII{+2~tE*R^whr^7N3A&a~VId}IyLD1=Q+ZuWaF2+~+pfRu zWrEnT=h#h1N%bonY8+xOJhZjgGp)+XeP19hZGL&30sT{4qNR&ljMEss>>uLYT^5C6 zEYheMOCo8Q^-{Pk*x=0w#hdDXBbjteYs1VA%&5YjoJo6Vbv9akU8CR7Vv>TexEZ?Q zG##5g9V%MGV&|J!Vh8WKtwqwgjEiAuc10>~Q->AjxfK&?=u>%XE;*dm0S>v(<@~i$ zc$(laCNe=f;;TQ!mp!fu!-=QKPG)aqxcMNGNBnPkGKE6FKSl0lCVRuhNRCUwNkE?z zHhq6ePkpN?Tk4m@`zW_YC4!wTnfZG9S<}W1)7+C@@X+%Db`|C@$!7U!x^g#Fm%9e+ z`Uy@PjN!G_(9=;Vm`v$)7~%0Ygi8BUC}#o08sUUWMCjZh}mIZiTkg@)4`BRfB*udFJh z){+=R598Ev`&=0BxnGR;h9-aqb@4)m3Hrn)Q&jaAsPgCz3G6cPH&OGnEwdAMRcaF(YqweDumf@KvcH^woD zucEzSF8)}Jk@?sO(uRTd?iZ2o<=diAri7ugM$FA_L)wmG887Z?cRLw zM35nnp3UT})^AU5_=xFwbT!O?OGE;n(p!0A< z0|aNT9T|V*a@F;N1qzZ;SL188KV&A-Y?cwEk3rOdfRU-9!5G;zjFBA(+?q_An}3>2 zm_%N3a;bkHGHH&^--t}1Rt`#|a2(V4U4-`VuGJL7N>RPcAYx4~<+_1YbE!u5PCItk z0>AWI_K_Mv^3{zr-;x6a-=3F_So@aAuQh(~J#%`l8D`cZdO(wqH2f^aGSEq(US!sw zeX3=FbI9M9&}f2+GxG8cM>GAEN0*V>6#6lBGJ!iv4Xg;JnE zK_T@)oW;?~vW22#j<{OrxeZI+NMyi4cc2kH8BEww?$T?}qfm-P9_?2N`Sq~0ezG)XIP$jIi? zDzDyRAX<)`<=^=`dg~PU3%!+E5b;+8FOa*`+N7T|Dx;gska(nPD&+GMi4o z!TolgVufFN8bmD|T8VKSFTHot(Ush~glB3a)o|v&(^MxxdC8SFM-hADb9#`V>cV66 zA?Bpco9$*bE`yDe3<9beg^RRTQMGSv#1^LfVdj%$)D`}HwBRse%l5nYepIi-tsg8N4@8AFGUq3#_7l|pHlG6NSHDDi&ZlU{y3s(Z zC(ebtu8c&bUW{3qWHUYgt}!PJ&!quJZhOvlXyAM8-mv?Omu5zr%O^Nqik!l2+oGTo zi)H)l;gvBv<$oi+ll#Q-mRZC z&=`4WpcjXJDMxP)w^ATX4c#IS`Fbd@b&)9G9XfP>b$w=y!UD1Lftqx;dnDZ!!SREi zA73wg%0-y54_q}bj1U}H6pUsb+!x(z<{oHVts_Y`OD80WsT^}Rn{5a-R51)a;4u;1 zBYr!tAv(a|72BVCnf8OXYwb1e;v(2stLbej6u5|vo52w(HY#IrnNt1wg6DcY7TbL- zgGUQ)l8V!TXF6Z`gy-u3bhcL%_auF@o^r(2c7(SOsy(K7#8MfM5DB%6=ccPH+95#5 z`gThN)9*g=22?N_MV5c1A#v`HKMw#M=CE7K>-P{oXIuag>XRic(W=^V*71ji_e7X<(8 zCW(tJ@T0G?uG0S-+x#1Y)b2pRgLlKhdQ$KG&xn$l6e0X>qrE+=E7Ly{MyW`kKM_Fg zB+mSg-$R=ij4-+y{-HN@K4X~of!b)Br^^z}^P~qhOfNS#D{Kg6aY;2l+VM>NBzB%> zJD;7-^oj)cxV^i`=|!4X#JH=lMVgnB=1-Tyd(w3^O%JMlyf3lVlN(p)xcVs6T&JFe z#mpa9xGDwiml<7cy#fzSB6bBuzOi%u4Haj~Z=s^yh{{l`XR}7y{0WPlyTUP8krcfe z7#3IHfdf9r=u07}LzZpj3n$?$+nKb!p^3B^=Sn`LeYsWpF%Atqn@wG(qI~q)8i!Dy zC!(2}$TnZ>_zl62oVgiA@eE@Gc9O|N;p7WaBFQ3fgRy`mig0ty45d9(X_A~Fs=R3VBN8UmG`KetPSXh&5&#wR(anmp6974ddLKIKPdS7REBJJ+oBqKI<>X+fV{|`GhFlKnk${X)M8pr~JC) zDi9Fto`L}aC)k|qvmwtpXgsA;{C6sQ6sC2eZoFzL@}-Jj@0-uJqt+M zyNszZl@5NNdsSAB&&S(PY$DTi1G1Lfr3rC_;Km;x)$de`^pfyv^X~dR&x!wPP_3OudX#`1xfibnDzj z6nkg;y=ojfSJ)IMA9EL-srx}Ia*W=4{dxbE2&2oT$M_jmE7P#?zJ_>GDmE}29=$@j zguofX7pXdV>6G=Ug!K{Gem)2kvL?~+@(IBNsTFO_==Sf#oN`UxC*GS1MrqW8-jhjC zu86@*^84Np4H zbZhQqe$+!8t&7lr1$3iR@%tk{Uvv?8dqeedAH1TsU^eB(2Tw@^?E*!!Gaw5RX711A zekaED7h#Zo^+$YgL|c;?!DobMdl{1=8dZ(3!+yQm_=mj40BY7WTYRJs$o@Tp?X&yz z{V)TV4{*fz8N=HK0#ExBN+M27eJ|0sS7TPJNajT32nYVHC#YwLGK*9h^!ueXRLdBowzrl z{rezAb}i})aUG<%$oLGtEj}q#lD$o{?(ABhht%TrzeYb!?i2M>Xm2oe`kcL zBK+mx0e});iwpdGzZJok$Xbk3&HhL8sAlIK5gb{?Y4e4#;b((E!xiz1MKB^8nokYh#$pX8y|^o zZA`x|ulH`C%p7Qtlpepgz?w;G=Kp&97~V1QJ-6M4n%SmljBxD}f@$YgMAnBlFc(~b z6qfWf5T@Mm1zkf;J3#YBoNDkcz+5N&gWOxI3RV;}L?<_YA10~ke)%tsS5WD_YbDnM z`^PetASe1Cg5J+5qt&6%tmXll7rKZenNrF{&EOzZi%)~|v`a#A&@C6o0*)p;_cOSl z6q-t0t2Q4edB}7;{3lAD?Hl2l?J<58PH~7XfM9=13u)%01^{y>sN$d zn|Vk5AZhgTg3Ki6E65UTX^U~XF3G=7N0K9V;}Lu%rFSQ;#SrhmBrEw`b|a=dABF_ zt9}K{FLtGx7_@#iyX!M#{(R*Kj$ssHPvko`9c*WlTh( zJp-ltAr0}q+|zFOU*9fr$7-*SlkD1!N6swvIF_Ez#fBVn*HgB&f`-Fv*3zrog0Q8ne3U$kr+-fd=9+ce6o_%!Z&`{tG&isQ!0yCO(gsRef|g6h z7}Uf2+Vuu`P3CWc7E$+tg_!9R>Y+Cguege*WhF!J(=3e=uzcG#{!XZ+=Gmv}_6`vJ z&xJgE7Jd&h*OiJksHTuk(Zk;M)v>=ImB2$oV<9&=rOf)35t*K-7L9oIPP<=#oq_sO z+gk|w7gn@lFrhyy(_ zV>_)|aPD2-!(|XTFicB7yFi>aQ=Rl?N;a#Ik>a!apN^K4$IhRK4r%cJcC@V0q0^M| z$PAp)FO>M;__6s|-@8PovO#3yKYD}1>f@v^s1l)~>$TL+1nZfJ<=TdZ(;&f+0vcA>>!q{819iXhLB7YlDA>sAv@9izLHOq2D2T;^h6WUjp0#Rtj=ak@jFPt+Lq@WhZl8}WG^&HD`6 z;Xy(xQbj9gtb7ZPu7b zD^^0{Hb|!kC|UmnTrN7uZ-eauHa0s&RB%N<_$<6))t`VE3*8Y~)0#-6fwY1J?kYih6!i9J-BtX?Fa zT#BHC3b-*ubK8Ve)GL{PiFN@FqNBs0pp01(ihQ7T__gylL;>|I%Lm8zrod#dfqZ>k0kDeyN46i6h18}ZcapP8CA3B(E>paT2O~)@S+$Bikrb45deF z|Lnt>C47HsSY$aSdnxs@P9$5)Vc^+$)%s^ff&(mgp0D8eRiKXtIrQx4V{#L-L0b#j z^Gifajc89jqowoO-GbFIhM(H$%bki2F(R|e4Yf8Jq7Z1tnVOe8&hlFtj2NjgMCAkX7 zkn=U8rV6T?h!=fN4hGg?t=vKC6;)+7fZS+pc#2qOmj!_Bg~lFY%qIwiR)N&f(j}kX zP*ay1N37HWMAf^zbdo3<6~}rALq%L^d;Yse8apB9O`D1B&hw@S?y-hED`+q_v>WwF_rboxAdf6}sZb%XZcyQW20Mb`hn=|<-tk;S zr(W3UhND^yT9UW?rA{SIopsNgRvlLGD`Ut)UNK12l?bCY8z(x6#ivc{+sJp5g?Fl` z`2JXIdYl|b#3t7*T#}3Yrzitb`TAMg%w|S)A?#x?KN~SM4A8XzlEgW0%gk;yc`$-YljgqxyfNs(^VbA)upMi3XZtw zDCXJej+Qk96q*5kwMXG@-VXVVghi@wJKau>7>3|?pqjTBZB%G_`3jvp?!Sl_gzY(3gPvyv=re%FS3NtNF#+*o8V%@PnO7MJic z{a>C~UkmE`TSxD@FTtM{VZqxpi&oJrvKq`@PAXC3$Vz_UTNXy-d&|P0W<5+;}UD!MaH17`P}>hEv# zj(&|x7&L;rA($iY5}Y@Y;W$H=QAOkFNY&T4830xp-fOjZ%3xK(=jw8d@rh_I2s-bHxO3k~J??NvdVx|@*WCQKcHbIk z_Z4^NMLZ|zhT2?Zj1m4P==$&ifUZE#FuB;~mZ5%rMiUHN^5vZ3&$hCq31Va`sV$*> zu2HB#1`XATIymmqDk45zfea!T1;z6dP~|h%!&}nz?sJ5(G|!#a3VcKI z2bfW+DUu0Gsm}(3+|tcw1=%BSAT*atqiW|lJ`66XnCHm1k*Bs9#DNl~N;G`Ea3YBRmjCQioi(?(U)0dIm@XfAEMcHsJ*o<-| zPTo;0F_v?efzBw{|MBLf+M6C?)YO~`578xGr#Dr&9aXt@kxvhT$%vr+Jm+HNJBu&uT%Ikd$MyeOr>BP*`XTaUGbw@iT?VEl7ZN>qKs|^ zLh%fNuJfHwL&}@iQ~{0*)<)^{1~K__^dcMW209-F*0_tTvMcfw=hL>+(wmqVHk+Cm;0T8RIC(O*RtYR*#aQBTFE6~f7 zlumG(0%H;cj5Wm|*qyw&*0X*=O( zwSx#qTRQbu{(TM+sX^a^018?ODwQbPGAA{P%4HFRfM%a1!QoQ+RiS_JDz&!_jimkw z{DGpq2z@$0KWlsUhkiDc^Zjr7nKI5F`dOY}9{>N)&*q6=%sI|RDNt*5t@)k(0jypu zou6Nl=oUe`JPPc8FRjU-11#Oy%&4g(t-dR-5wNyS=rtsG2D;K_=mNHE zh)!-&v(P`)uSL&%uA9^UeBG(t1-Y%dWYypDD5ph6;YTRUsVHvnWud%51JD@AJ)6xj zL+deV@9ce)_U}bs-f9xx3T6shr-`o#5Yn(0@WQ(F(*_lM2e~NHDpI3NP42AgFz)3G zLokERy@`%jRBxYCzP;*Tr^LfGxjinc&I5l*i2OUc7ix0(aKE0*5|R+l8?inFucAAY z$IOcmU?DC_K-aZ82Yff_7E=WXiiXC|h=*nUP7tM3CBx|T^aCZlR34@EKazgns&^jQY29%-;$}4tuIw7B4;agPk9aZL3nRw!?T%7=^Tz>UMH)> z?q_XOSS<`vbzRL_6CgGJ+_=a9L!E9kF^b4s^Q4`w$96&INXsDSH6je%|MBZ5y6E{J z73qs`(eu4CYXPiz05W*IwO;Fc!FWf}Q21%HWo}T&o3kgFynonmAS-7G$W~JsZ0Neo z9l93vQ`>Prh|SC79Lak2H^*Az{aUXJzF(JtLu~>%`e*|OrVrn-KLqasSIV-nXNltG zv>SbQ?vqP?ry7#ZHf+;k3rr0sY% zk@vIK8~!%COx@^kL(&L^UPzlH^QT>jG4)^fz4sL;d@N)^#xGmG>){8VKPeZkZS&Mg zRgCw042_J5^)Xa2XApF2E7+~+6F3y5sRfN%|IibO{fJ~sm8xH2#g z<*RVxdbRl~#u1Dpd6v31E|ZlA|Cc29ZixNmy2!;&LZ25fQqUxd6F&`z&140K+8YybOS(w# zXZ2rx9QxR1Hf-xj04$!Bv10u8;`-FhDBM@#UV&_wowJ8xy;!&3WYeC@II_rrAI|(+xwY*Wq`v+&@%|0(R})lM>}N(QEEX|7L!*qG3@TsvdC{ZfuUiRMl_ z@Z&0uo#@uM$)RC|V2qiAZTidpm#(!=TBN6Tt3xB`u;+s#@&1c$^`0I#jRJv;H*+t| zR|Hk!ch!&7e8s{`B-klp_E}L)pxCZVbjl=`)bGhH5LcSCDDAWG7G7(pTIw;@%m9;G z5u~`$x&r+GPdtMyj+uV8a7Ywe`vGuFl8CF!P-((`vytmhmvP`D8H#eR-mto}uPzaZ z7-wBbQe>K{sU>56FG}ZBg-uXs!O-9<5pNEZn}nityJq(h8yLcRcofd+t$jw-ki-8P zl)Y$+gi{S*PfZe1_%Shk|1HCfcl?J8=bat{=@RTacp`Gx40hqQDj$O%kAvn zm{%bRM?MPV#hlBeL9ekxmcu}V;+rF2Vmt#%T0vs{z95)C(u7g3F|E~&M#f(t6evK4 z)>h`R5+_r5Z3q;qae$mO%0INB z9XfF2h&U@o-w~oWI{ccl&?*CKTcIa}`@p(;Mw|eS-mB8MM9)85{hDF3SNQ)P+fu+e zs(tj&_FIoG2Vh;KQVJ|JV;l00fq$-UeSCrGK$u%`wk~9$A!&Ohnh3lJPClh;FLL}PSV zU7eQk+nfFqyHaun0#CAS<-h8H?X8E=(1zwdRIw}(K#og1&(2uAe{@PXi?tTQ^v9MX z?3DX=*XW|g41e2i&LK~q>CiEK6oTLxRf9g`Gc7}R^ZN**SDPv_hBB5Bk$BGkOt|2* z0O49$j9gZ_KxQ&zsDM2ayT^dY( zHm^l=IjULk<{hoN#Ik4uE~VD0Wy2nhp=cau+$vbBqh_0^SIOn3$7sz!S+C`YqYskI zj9P^qaUK07z2&fC{JZTIefM@$OMQm=rz4otQ*Lwx8d~&LX9R|=q0aCt+IRX5ikKOe-COIdVmyQo~oLVq{0Bs zg?c9-$NP_YCjiUB3$u7H3MRXltf*oN*nhl>3_Fils(%4wvSQ;q7oi&^C!pe3f+>1= zkXSN2oqEP#2Dk7O{nMD1H@e+%5P_k#Fesw-YPWn^`{>CYX{O2kw^gko!ZxnRM`Iy( zyLEdIoajEXC7NSHa@IT4x|#f^?Nt~Aud9zWaB$>>-v46+u+5B_NrdAollxk3anx-D z5RV?JtaI#9Ac^;g6qOo1k@l99 zEBtpl)ZAWxUB=T4_z5Pd*FQPdmywc&k*1HG!zzx3`giY+#zl zjEw4ztRO_m)pI#2swQNnXu$CB6QR{OL>@^k zTd^T!^hI&$z2p6ni2-r_9vk}^brCvi9i9z^4MVjrgGe+kw~lGwXw0!38A!H@)sM*7Ct0{DZw=`kxoa|E;j?y- z_z~iQd&{O>hF_-`bZwts-`8|s4`?_KRF}1LKib0PaDkYDV%h>lr%IoW$*tAUMDr{h-MtKBJHW)d67%*trM<(;o z7%|rhV4nc*B19vVCM2OclcZzZYYc+OE)c)-RIGy-e&U;z4n|p~A|drRko`Yi%x?>= zF5QFQ=h;=)i+m;VJ)MvF6gZ4LbOQ17`sUZAFaTe1?;REA69XPP(-ow|MrJGZWHn}$ z^!|45kCZxoCIdnfN0?bc^Nz45yPRp*weRw@Qs>&z>-XU!@+Hc^3HOtAD%Fv`3o|xk z7)mQn`}Ixt&4j+ww-tj;T)c}di3NIRedo%cDIZRg5ByafYLsoCHnYOsB2GD-9bfXV z)9WmDVZgyYS~qH?q_SW)T{4pyDHu$j@lE<@#kksR#&K~k!l zc1-5t7lAp;8-F3NM{rNOEk|jP z&8BmkWGrM_G!>dr4FkRgd3#^?C^hIioU(R14IV|u$IJvDT+WBI2A-(Wd6sLk9iMTTiD3gt@jMRt&1Y*;YZ$?F3IFmPM%rSDOK3Ood#yED7 zFt!1{7JJVZ8p4Qf%H{j@$qeb(Q`UDa8464$ji{Ah>cw;t3G zQmdtxv>g{e_(?cLdr-|F_i|?np*#R1^Pdt}Mm-5s-O8&2^16Bgrq=?fyz_oypsy)8 z`R??N#q>#Fh@R^f_#Fv-pI}77-2UjZE{C=M>Tj=Gm$3|M%nz)gPkMW&pU>PE;gDgI z)uiy{E+cJcF1<^K?rY@8>4-ja6TE<9rdKOp{N4i(x14@5e15`Ku+aPU#Y6x zXLCrZ=PV(cgw2LW0*)Aws2%Q|*Q^ z_UL`raq=r{RYo^ISIr!Bc+VE`DiNDDC)xVvtyySlheYh!^Qa8?o#*LgO!bKdbL+*G z*VV;`Y3jzY*t+r>+AmGsXc6MLz1p?g&&QrcZ4YGq%6^J1owB;Fq&1e;u6JWkZ~1W_k3V1r!kHz-|sQjEL@e%bCAn=*Z6kd1EbCvT|k{EEAr z`K7+aWn%{wss1MBZQiD-&27hBNh^XjbPx-fO!mEMZZsVj+gDn>>0M$ahSTZ3jAidR zPeui=Gx=Ev@(qy%3PljX5b3~%*|i_?oV3G062jH1Sy=_uU^0;~_by%iN~^y2t16@C zlktxYvgA__9*Zz$igigu=GUGX%Vr1SC^xf*y7Tl|3M|Ea&s#+J(%nEHHl zQYD}W6t`m)b8xOGcMIw!4jQJva-z6O-8qfe+QQuy3Q#%f#ulOcv>Im=Hy>-7jKN-E zEl?n2nYOaXsuQdKvYd(}Ua1^@g-4n#PCEz&b|Kh=@9*?aP@A`g$8x~42i}Eq-Wz|X z_~8|jj&Y#v0qMFY@z2kEXb)-_)!qiHYk4cuR@LH>Bj__?RL?y6a^27>x#PpWIrCHRYxt>?MK? z3gVns$xuL~dE*aB3>pK|R~w|7Lo?0lq8;02=n`OQ>$GegENvM+!9khp@aUyWb{2U~ z-iKPbW^ybJ?*ty28EIuIVpn8G39jP$O0jOU%(fTfbX53bgAI)nONhJ_$ZA6E?5$;% zLkDKCMr)tN%hYrFwdo$VHFm3)KkZGMU!`LOAIS-SW#kf_xX?WnoD2BGqB!_&QEoTC zfPG>_IA)TMe{|LV6_SOAp;~?XMaO`aLWnU!_%DBT4Q!g~O~pv5UxRG~ zWk<7luMy4H2^j1;Q`Jm2m%8afq>eHP%(4|4-J#Q*T?du29eH=wH{L@1Ti6k<*?jir zjt~~yEaOl+-4%<)6`3^yU!-3vJxi34l%wsblSWBa*+H(h_;$@Fj(1lfODz~%a86U6 zqBY>@L{q~PC%fY{0q60M5ETQIETQ23T)pAzlKy$Hku%O5Q0qa7Jv6x;!8UekCV5Y|jJR_-st9Q-B_}0n1m5;$w zt6Ifqs^3aHvZ=(tn|cQwwO)e(jVFjZk^RK29A)Xavj83bt)9HU5Cp}XHSndo^4o*h zpzO|>ISq7gNi@}m#tL!kcl|VuEa#Fj2xMhxR+B>v$c~BZ8tpAFzhQGdSgOb6ZYeh2 z*`Vl(7kS?FOk}^l8J9drJax-+4t*u|U^W;1`ASsG0lCPBp8M7Q#ws{qWXoZez&CgBRO`!2x7zQKb8Y|mjdorFs zIH+R$=1@N(eHYXlanAA2(q;nhq$$)4dFA?qY9UPIsSIRe@T}G6)bjUuTYjrL9HU=n zm;A)gR$;)EtU59-SnKEvW`+-FuT%2BfqG-pSUP+6hcsL^W;WzYcu}X17GvW)QTmHp z?FMs^_kO)021pK%YCD#H;1aYO$vowu0!owh9*?r^zknwiW5oS04vUV|&|oJnamd=j zzoFc=$Lp7=6K+thISWGZ6$Dczw`WF=C~jU4YhHAu1;>=bcbP|8Kn)L?LaTLfrCFhd zjJgIcTg1?W%z%c|H?3+v2`?k>vhDZvdaTk}O%M%}EUQj>u`7$XPg5hS;nkH{=Dv&g1l7Rf>O-h^a@ zBv~1qlPxQIW*^xzBPDdKL*y7ovO@NjaE?8H*Q@vVyx-s3`}c3RTh4J@=Xzb&^YM7x zACh7Aah`JUdoQA##1;p{5p^02!E%CF;iy#EPdQLzTv&$CJ)?yWx%B3uHRlD^zyP}l zrI>`(u;3di;pB4Y{L8kkp`1@P3PZwl){FBL9&BTm z4~F0W!sY9d{G?;DH~^br6-5ezXg8NjVDFJmcGcamAo>F-FYEiHZ4#yX6i<~=2sq5UMoNB8sF!=y*R(% zq)H_Jr{uPfuq*YGli>MCU25Je7vk{Z_*A-iBNvrp7fR~wASUsT(J$C$kdl>S_^+;a zi1xnKtd9<9o~JYj?kkqE9x{7E6KFUurzkjGIhruz2Q!e}lNI`0Wz%t0 z)`1JHe3~SI&tQ*0j3SVgXYL6q{5(l~vhFf=Nv$_(JvNm^Z!1}t&k14uC)^iG49W#C z5ChVp;~!O97V8BI>s!OmJa-}+c4q`Ftvhk?pX;%iUzr3bA6aIQ#KG_~k&Y{iEwXx* zh4Z`a`hRRttY)YUBpF-e(sB0E*XcJp!9ye>K%e_|BSrzqs$Zx|=FY$|_0Yk>ioXUq z&efgzdK!r}*m6u7)WloMo zFL1xa<@p8n!Om#d7!drH6SYDLI~%U~PGVh1JGZtqkI#tP#u}Xk|ATH=`JGCvZQrLvdu1f=_6jGt^h{R2 zsClC7#K-KVFHL8N(w(=To4$`QbmS_sBwfXu-#dyC1rO=G0WN#>R!>!wB4SO>ceaqATt!_J=_w04_^xO@{S(k%tDT5Hno z?6m8Dgu^!Oy1QAN)-4SS&%K>-enjh(>%#vMFeN1_b5{7wcwHpE%I9m3vy46nJNU)t zwBy?7*z-j;P4b(&xsO#0gYR(r#xSK#>;9XA@lUQ#zDe;pthyWd`IjykT=h3!(2-#8 z$*1feifQtm!vy&rBp*S8%n4FWbpoVRBo1o2a<$}YsX@=(N*X>bgX=}OJ`)mu#>KEA zNd6UQdkhbNws(?~Md5gs^oo(_&ju-F+Ur8r^YO-8+F`FIquf>vpQ z_-?m|?`3EAp-*1Z8%|u&ibXwSngIqZVxkBVxZd)@K*#@9--_8xg#U-E2OScFT5<#K z>jf}|SO_}(|EPPj1cI>tDGP$iu?vJ>&!e7hyF1wZui%-@~OWz4c zWTt)s^iO(6wo5+}6uPWP*!N|Eb|=)5R3W0C)#;<&4#b%~iTWd z)_T!({JTj10rcQuBg6xp4WyBdVeC2(YsvBim(r}s?k@`hD$3-4ptgk7fb*ABd1O}g zVoNW{>`&)wrfNc1#|Myqy%f;z8XrP!C(>Zbp7f85KsZ-Su^+IibMvg^Cu*~3vAvvd zQ`y;y^+z277XJ7+ZG^d*FAN1l4I@Bt`}y?4F-WOB@C9{F%e@k`&p&kW4zW&dh8kiU zcgJp+%WSpN{pegh!DIbTYj6iiFPrdLTdHgEx(GBGUytY6Y^0F(-8$7O01@O;i9iEL znUNFd?Qt*oM}HOu>dz|EnFp;Wjn@VE4%YxyQhO@45p=7RGB$073_&7{jvLepyfZ`P zUkBXeoUT10orXZZFhrk#7j=ionKqd00mPzMwrS%*uM6AOB$QBE2Vs8+*qu=1dy+RY7MGCvgxNOUL!$OV3oi`!kd!K=jijXjRMRZ1ee z-<2nQy4O?}09^@#OM}3?0&Cdzc!TBnVese{t@y;#C!y&0f3rZK2{e065?bm4gKyFF*A{@_R=9SmTMAL& zjR8azz5MVSCXZqvA8be_Ry3)dy{cO|RoBR|P<_-dAAIl1neXmTKCRnH@)*|cYhTYF z6dw*~2$`$lYd<&MLcD_H7mrxuKgW1%B_u&V+i2XYV6gPrx?NJ|KwG~%=9q|{m03s) z7`7$WPP6lGSglztE#uA6tOvZzZi7=u{n_={E4i2c`=+=g$o)AD0bTO#nD)YVD&AS%%B;7|NN4+flQ@i;Tci;My6RpebAM23d(`z!32O`*B7m??so9! zWphT=mL`)J2yePX;S8{l03@J6D7K28h9IZ3k3YT^eq25KHSM~R<@80u0rea2Z_i3& zjs%J_6AtH#^I}S$^rjm*ff6?N%Uw))I_>~#>u2OUeV$$QIB;Vt_iIJJD-qK@rk7*k z*N%oiH+_0s#gwwmGn3!HeXXiTv^&4)+I-pw10c5Qbu7~0ze4SHw$~mLiH0(P6xzOf zc6Eu2al@nvv(l+PzEfXF-=NIt@o1VT!oK*P>J zAt(DF9&@(w@C@hSwB(Do7bapxgB-#X9{tk{ctEk?tMak`YVrw>W*?427&ysh1 zj1iDxhb%O^nqQ$i|grKp6yQux3YzJt{L0}wOn1( zmD-eusO7(-gOKxq2w026gsTG7q$Myksp$R7b0qp9=g07Cjp(riHn5uZUcM zfl?$L6dy}ak*R4gSp+GzSY-AX{9=mWovG(97U(<-NjdHmlI~Qj^Sfg;L&_o%~k!8Q| zw;2&&M*F|TZ358oZRXT@N7UA!vWLd_m0%a9SDOv0*_p_riKW6_P$0*y{+$NEEF&+( z*E$|A97MVI!Jl1eNf9A`v&=02w?6Vw%e!z`So34Sr)|H0+&%5-zJ~{IVil|l?}*U& zST|yOJd+UJ`DalAdqAL2EfN=HZ#6!{yRzuy7W9zRCbXC$I^wrFnVzkC_c~f6F5X}= z?T)&$GFev-D)e&b0PiV6ONAjLi#!Q80K?0e7n zCq%#h4)$a62p@au%dK-rzRyfI6pCF=9J}*eCTo;eROV>KVOk6(<*!1u1X6)$_*g0Ux5cUcCF%k1{b1h+YB7$(vo5aevlS9LOPG zAH^j?JUrk_>66wkKz~bI=q1vw@(0^|8XL#Eg&7y#^5Zku)MevGB4k?!pYAT{*=;eh z{ZcXC$uWFr75ss^FBU|_5jRhHed){_k-UCv%4j7LJHC`^qp~l z_Sxnv_+4pY#o2}etHXu0`}aUo*vHE{)J>!$t7@Zz7d&}%nJVcY$&xF?9kvebb1sv3 zT3?F}r_OQWytr+zP5+M&TPRH)#U3oS5MS%1UiN-WzW$x9$XzB`)o&T~x~l6)?>}j1 zlyx{MivH%fBtJ4(!+n67_D*YCE4)=oxh|KRjnWY02Q_mCt_@p77Q z*^qpdqGMX@VD?{`0c1)K*NJ}kPm1W?DUov-`^^+DgCt~zctcH=S~py6da5d>&D=qN zZK=I>Yh=jQMD&3kk-#ctP;Nq;Xm8^ojZRq#m;T#NilkNU5lJF6p+*?qNxIS)l1$b; zg%YQewO5m3i(EF?he`y2G_>+m-ZY9)B5;zXVi%;^n+wM!-6ZrH%KXYi7Tbs-0Tfw)zj> zM=|$N{rtyTe#4!6?A)+Y*d_e52TfI(BWNnjQ@5vMF0&8PhRDU2Ytgri;6G94Z0JL$ zz1g$Z0yZ}9B4VN(qz60y^+;(YuWF4Uon*NBFA+|N;QvH8+F_HEwf6C9aD3$5P|toh zYRpb3ku#S|roj=LNUZuzT^nUeAHMm#LFJkV%`3rXr0h~=70*Pf+%!0yT>Zssi}}%> zPDe!$7y7#X*BSjr+-yj1F8Bg)Yvzc)n5#`aSwcT5te-$|XmVYX62NbXK zry&K7?7RgpMWWJF6U*d^n>=>SZr}1iLL06jKsUxmbQKUNzlv3~VrP{C70cUEW7Wp| z1B)s^Ub&K!i#1D^Rr!YshpwWoDJ^^LUBes9$l(8A!Q_e(>$Fh2nfh|SJ*5JX1>r5W zoc(JSb(z~4aeAS|>pUir_k?*lKcM4rP{gg^Dz-Z|h|CymM(sBrvM8O%j?KzjOY)eX z5JE{;IM}lyotWS;acTk>{EMqIZtH0KRqOJQ@3~ZaXT3QmE(THsg_+aV7icG?=;H|& zBdUayV!}+3nk534J#lX0pNZP&P z~9g7jPS7k-j}HY_mj{lW}Bp3wJy3Br9#Z%FP^dZ zADq;>>s_>9=rFM>=b|~^Q7_qYhupc`=}+1D85ZqDruaM7q_+31>Z*q8pP<^`a$|S( zfKIX(b+q{MSapT_e0Z%~lEJ$5HXT`R#iG$|TcjG9)amUml0_qR>Zl$a1&8HBW2}zs zban*-d-tn>Ywi^UFX!UCJ)3Z|!QGv!c2$v&wRrhjOoGpy4uzJ))px(L`PDA9O7CsC zAeD&Uw1s0IseFe>8FWu(;Ne>XycrjiwQvMBVnTxF6McVCmJTP$kWVmmG7XhyEK)iC z_1;>|+@F>Weeh#G2*n-pPI3CfIek}Zg0wq5kr|r%Xf=0i@@=GVD}_az{BuslVNctf zC|5cPMfRPcPTxdk4Vm01)BEoFXZN=1Oq1Z$Lqoxi1hbbfStH}xxfJ{7FJsi|wU9Ox znxrK-k(ts&C4M5`$hxnIv#XC%ham!jy(|_{k(yp70DzN<1ix45f|5gCciwOONJsPZ z#cW6}C^mDeym(%j0G;Y%>Vy#5Ub~BY!_OtF4`ey`#LXU}X1Jbue%`#kmuXjC(_!LB z)?L+BT?)tc-YP286(Qd z_lI4U8832mN7PuiXxiS`^HfVupuJn|0&C{`>7npYiQh1XuC*Gd*6 zD^6uK*Tt#(R5CZX@Jycusdba;qcZKf9_L&?$ z^yBh9t-p$jLPptoYT8;pRe*9YK@D^Uz)4bcE{g-i8#ux=8^Rbr4UGb3^_5 zur0aFWMM!e>qqPBwx`o2_PTU(EpY2g-!2{k%B>w6TgqO~dQ=l<)HjbLBX_~JEPn1m z-V9~d^m#B15B0>0*7wxXy|{4j&apSVI;vCQLQSu_#lhPG{x4S`!woT2AlN5MGg@xd z(TG0Lqp%J*m*wp#K*u|HO+Cz&6*+fEBsXexhu7n4hVNW`g^jT+9AOWS06$ixNdb|!c6e|5WR zm;cl4>VR(7Jqt(;iz{TDCMA|9PKoABq)WU+t?Z`1nQ3p1IuUv&jda~c2l6_j_VBp3MiPhIIHsj=zK=%SigY2fX3d>kA#g89 zz`IAK;V#Q=d`iW61s+cw!giX|(s5Fl=RpMcxN}_^($vH^!f}lG+MqVg>CLuNXO|40 z>`ce%6`>~)qKB@r9!{NwUb9Q4bQV5nhK08rl(94RM10B&`0za6f+%Hc#Tn3=_Kg^y zeVZow-Nl@i451xFTy>`R%v=Lkn<{)5x8^3}aVcWkjsD5iqfy08_?$PS;zhzbBt?U8 zl(bKT>gN;sRC+;gnh_{iJEZsnfRpwBILYrq8D#ytXQLA_z8`rscO~Saia$*$;vY%6 zCFW2v40Y@@q~Wn9=me*0Xk3{7q&zHiM#8L9{q|y-eR-N{bH~HwGx39Kl))m-@4I$n zc6{hAnx2&l4EtFeMeu>xpoJDc76^InKxl){o)C=PF8s5!`cHImtp(K;0u4(bn>M@VOIZ%zVs z%hMi_>G57hC^Mtsa04xQA}VXEKSRXnhN|m2#=a(-gCWyhbXo{QKW+MlM+fMjb!5Nb zk>;$*C`WOKyr!;Q=KKrZXz5J`?t<{faj1BRjt7&GGviL`%&gBHxcRJbyRZcPY~ZD7JnV1s_FmK@@PHZ`B<+U z_IVM|V+^P8yOvxt_12d+ym@gQDsU=xlhKgC3#~{_CLiXxG2a{v{L)(yopyIIH$xKl z$bJ`;LsXdPJ=2Liq}Dp6QgA<2b}Gp%GaC_n&g}v{y?YAd!Hh-_4Z+xzlp9X;R;X3R z@oSux?itd4i~8$gmc{J2Aec#{jEKMJXzEhX*y6Rlf;*iUgN)2J>Ru%27q8y^g8Z_V z&li?iz>!kzm`sRb(m-hEzBVTLUn~ILfF<>$=ZGsIyJ*=>F+Oi)bEhAqb8okO>ogPy zKqa@)z+H+{8vT-3#k5o?Y{xA9Bx2C)AnL6J+gG?~a8D8a?hqK8%@aJ(`9~#NC`^N( zZDi3ar;#QC!)otW+q89j81fnZ4K|0sXV(uLtEDX`QuPecGr0ebJNfv%iDQkupnG&s z$TJ;-vNyleZ1$N+`Y`If1+~hht+pG(#CN}rFt2*D9yV9o{!R^pzh!>dR`WBZ({Kr_ zTk+QX_bbIa0<(7)HKcx_8`X9(N<7YMYN^03&9l7BX=q>6^Gq+ z$R3yobF7CjZ9TF?i{gPgN!@g;XhnXiEhn*~asCp&Z$Xz`Ziw$A)PnfsbHd3eL`-R} z&(qje7Y+uWW0<7g6G%mUH6Ju$_cr}2?=iTuMQ3Vy@W&i9-mRRfSyDBVmyFjovpLg~ z_MNxM%_kzM_JVSFwJlgbT6w0xz~p=Uxc4^_aNMp$X?1NkriS>4yk-XEpCIN1q!DK; zbZo`ck2w811u0Q-2_#~<1pGskR}6U24@eKHHgUzmPpJc_UG#t7v3c|kHc!vgap#y+ zR$e0LO~bHA%(h$4Mz@%s+Z2U1oQL#9&OYq}_tUbGuV?i49AI=VvmZC{xOqc=@joKD zqn5?~^2{;bPAYF*I?fTk&qhVNVE(4>6tB%A)zPd%9DyA=cT-7;tp^Ss1cbA^k(9I8 z@dB0A=j`~xtrf=kmuASmA(E-v{~vu1PWGCFREB7ySZbs0b2OKH{kPZQk6J9NdD>3k zg?@khCofxf2202~*l2cT@#O=NK5d!}nlTp?tvk7_D z#6$I>H@7W?@kcv&;hWU7q*I;~M~Cg4-x3qTJ`H!L_MD-tZkEmvq-wJ5*z<1(zdUjd z70ld$t)8`M9U+=*DvhLjZO>99c+4#6>OyI;bTU>4SOtPGn2Sz|mo5|aX@92^{c&#d zCNI{d&pSy6i3ctk7+EUfsfQVIZ)fM;9|hrMN4}=HcnlYJZk9-g6=RUW#A|7`s-br> zmdfcBc_P8Gy=1{H|F6I|TcZfB2V0#cawf{b18%j|il?w!up7gdB6>h=91|L6Ul;*l zCi@;`rpW%OztdA^2QZWThKI_<&$CoIddq|wr8kmfX$W%}(yLznN@oong<)$EygFik z>JMnyr?XPOJfcwWla_UOoY^;cXe{*OtlA|xsIs|>dvT8gpSV;|BiM3hb>q-b6$$^9 z+I{hlL6;cvJ+{rww^XIW4mQet-lHqXd;wQ(b~Z01C}JX_tog)`{d0Lj6Bv@G$`J7u zoYS$~;wge2ocE|Vch%Tj&_yS)7EQq>`=+1A`DLsJykzOyNDKP#%L9>v~H^YX5NH?hM-5fdX=%D=C0#ImG+dcscNjV=-^ zwJ)T`=iYNEaLx^X<4PNfd##HfOyCQkW~vb9aXq|4YsT`ehI)Ju#f-Hz26}`~UpMkEr73)TN(RsvgnE_+Kf$ z7S3=;YHZaO()yo8caZ?Mb=ivDLKdJ22UO7Zvz>v5EG;r-jVR&0~ zRgr*ASLB!hWVGc;TWz1DUmH;I)tkC-=o0)so`R)LMChmC~W~9VAs|;7&kq$YjQu zZ`w3K*pF;t96=pz) zzg5tawie}!udO&PJ=08){Kcg8(vQ+uL$H4S8~^QJ2PwyejDe7!o{g(7j&^me17r*h z^n#`~LnRC;`Pa&8v_FA)sxee>6l!Zc@E?m9eB)^eZ3J3&5}?whe8Vk53Z32+vFrm) z+#)UjsGy!hpfPCPV*-SV@V2k}uy^O8^co_Cn(7|<9W0b}%IsEiRnk$7M@vlJO?h$p zu}J!T?z=GKv{?be#Lj(C)^ue-qFDzjpj!niQRxs=2x7~S_{=44V))m~#^sl!epk(h zha(__^VgH`s1E$_6xjCGA!g!9|8|R7}_&ddRH|_pzS=>Rl9{`09;r;sHH2hf|P?Xw@~&yujGsG4& zMl)F)%k};WhtLea;>~g$zz05b^Y0Kt#;>BsPuuxqM500K6zszxh}24!VnBQ}i&E0F z=?c%&yP&eopp@sW3FogFf(};M!|$=BpBvYmR#?8OPR(%mSlt8y^3X-eUf(w&Z(ia= zViB6Jh9~ki+FVCM3Ai2_r_q0uXg=sI5t{GX{CswLRK$Pjj&O&S6YLhi6DE@qTbxx? z3nxqJBRbZgilL4J<0f1()Gld7m{+Dr*3g00-106RZS?Q42I)4Vh-s5&B?SXU97AxQ z>#6g0N%;TTR-H&GC(lnLwL&sYe1cK45GrvN2Z_;SeMxpRWr@GObeXBkaZl(y!N+N+ zKMr05t_LY2r30cFXwrUiRuv|FldM=_uVQFi?AMvWEc;HeO7vJx6PkFPLMiwu<{|aM z=8BI0t7lXb62Clt!Bf!B*UnbVsRR8Y@So|Q~}+Wmq(nQ6TuF_5&?uUYctecyAsjssGkL~ zMCohQsxvRGUI&7(Ic5VQO0Mn!QfJO*lMWuNDCaTIe>Hp~C4#K5u8Q#h(kuIL=5j~0 z;KUXW550?S6@Q*^&x)GPD}64@MTga;sLM&7`GS;GGc%ozwITq5Zz!vdqaIKWy@uhp zVQd?ZwAjx5p6A50o!s?}Zy+S7clvSpB8u>(@_ps&7lOiTmsuzb(zv9%r`^Va-Cob! zXGIw48K?Q$SQqa54ieDu?XMiJRq7xuQQrcXeePY>#ED`M|FwsT6BW)AICtN!z?TM( ztV1MDa)ixRcF&|1@&@_Gs;&*6B`G~cT(JHCX%(pcv%?MWON4jENFT1%+!}23< z)tYd#UImOiA!Agvme6js{T@_sM?0ZRHQ|i|8}54mA)c~qhGEZ$WG2nzOB0?@f)h30WSe;*qyQomqx^4=@lc;2ya#})Dg#Bw*GWS$v z4Xz;Kr1wC(r7bca=DxEAB|Z}q%ycxj60)0uHXs4~Rf5{t)PpBT4&pzc@U&0J6Dd1G zrc(^5_}n&ex6W7VG@VjUR&La);xU^^>!qKxEmm_Umlm&Fc>AuziWZS% z^Z)!EpBNJG-qkM;j=4a?&8L9g1-XX^LAh+HKMnX|U(48dC+bNxNAtUXQm2m2b#&^$2 z9q1lWhwu{VVVoy^IV#sFP#s`R|nq^wYZK zm&+>GZ0gBgSBTnZ>fY5elY1zKi6vBG+if7v5rLm$Zc#D%Kt2E%>-dQ{4LuvEr<)c||TBcs& zz}#5a1CA9Pi@O#)7D!>3$HGkFJtW^11O4A}i^VdMy&-aV=c$&rY1k_L4&37_{F0u4%~}h?giSkX zrpFT}JA*>dZ7(+=-AFgl{0arvCCSf16DGlOO(g!R%vuio>xb=rPTrHRb$%PG?3%CH zT%f@;R;_T@%Jfe`o)x=pr9}H$q>gb~l9-ct^Qam7(a$x+;8`}tq%= z^O~uCP+6qkn4;?*-F0(5r*8sP&!3s)f5P_pO}^W7Hu%k{HPIc5^4(=IkUxP>h5D?j zBX{o_Jo~Mce|OVa#ni2*JHp+%hkhL1{ox+m!Qg~>Z~f;-s)zLFz7pv@=AW3%?Pa~v z{_R@hCR}lIci;y1^TEl!cbn=nT*~K!hRH)nHoit?aGu!4nA^?1dH3qe+$*q6r~4>m z;BNS24XC#M&f}jtg7IG*3G3{9&PR@+@8Y@wU&?g-*qy!4CDVLi9@q zVdatLrgy2~Zf8d4jy-ByQx);t_*+7+=e$rsv3_?%up8#FVJtmUKp zMfYpl=bx_Tf4(0pJ*9W>0LCGt(E9%#8ZZ$WyrS6V3^j6a{;!AgzfaOPK|2O9oZ}Bl zf9K!-_8(gUfRx05u|$}Sk^X-@h5l`rZjq0I#ul@EH;jb-b1(TnPxK7r6yI$es@2Ja z9nVndOhvvoZr$T)%o(cDnrE$M*2mWEBAv8aqT@ZR3c-yLR0!jaEVy?ud zL8z1!vviG{)ry`2YJAa2>EKP~;Z97aXh4eX5bjl!z6H%d-a1CEB=cHO?vRU%+cl%Rcxc3V#MB z1U;C~e$49qz`}gz@Y0P*s1d|KwruU6=H)B;2EIOXUZ5SEeJc!$Z&?CIYPOZh}-EAousc1<`Fb zAbE4<1t8VDyp9!cI}l?aj4B+~&hCd$Ff7?wzeh8Fn>;OV_i2ELH$^+04=w+ZUx2lTvuk3YZmXcouLsF5anBLob@zXAK=`X_GVMmQMom z$I3A{hMM0ybO)?|zRG(_N5H68&Tp>}9&Zha$Y6oSS45nEYCcKQRLK?JM! zVC$Bp`ftcNo|FU1P-=3Sgr6)M&mRNtvc@L>W+`e|NrZgNLsO@_pYxdv29xN>SBe9? zbp|Oye<*!tT-};jl93RQO|n3#AY`FgL#5wN*=6gv`^|s>^#CIMt9+Fh>XdMn-3+8M zOnLM|P4xcqc?^2ZVz1%5JN}3}iu=db#i_!EHSBb3;Sojm9bczOrWCj`G>3^j8YN6_ za_kXv_Q7XNm4ll&yAc-NAyx`a*t%Z(fffY5g143EO*NLY#v zb1Er#tL_kt(UXTW@Ea2eDZVjtC`cZV@%K-_Zk1XwD=U0!@^*=d-G-&^ z>3I;$UE*p3Uc~q)=_JyHHO5p5u>w~SQh`W5ReFM^*DKJ#&DLsp&huDKaxtfAdqBg$ zJLH@gY(~uU+I`kAR3YZcQ`&++MPd|yr>QOh?$DC{RG~lb>9TPs)yZ+C|H%#JCT;1R z!Mb0sY(2=3fo*_&m9n}CjOAR?ZE;l{ERKdz$~P)eFB*0s?`{LA?-{7mJUDRf5J$DuvBiQHSV9`I9z}#^N8uP}thJ9F;RAxPh zVCZV>#-KLQ6b8-eZ8aF15f!JMt;~B7A56eDV&JXVE4)eMx7zcrjoLw*45Z7W>}^){#fQTtuT=OUvlcsK%z9`COj zJH~oaW}DcHLV{3FQB#|Bv4}7hq5oLAIXS6 z#`1?HajgglrC9nQl)C!a!}`A_GmW+mSQqC$F- z#))cR-k>jt6KO_Joxjhok83+mPgmJ0Ko&dn-ia(aDTnR9HDpWmaI zB3-T2!$r{4IhONRC$)VMp#Q1xOkhX;Fxl}D{w=3Xkf?l4A!+3Y%yq=~=q2-=tpGR7 zbo;fOo61*xuAW>lb63m49(BNcLZUP5hXM(sK~f{RL-c$UjG1?O!1lHV73F>#>EIJm zEAD3*ZJTFcUMMk=8Z)~oa0UI8cI2v4NvoKc9xvN6=iF;XMxn?6>+qH@a57}n!$p%b zg9}Oqe+TGK7bI=4HBs-aZ68>Sk0uqJc6KjP*QKs8`{rWDItXR%R9?dh^D|QwyBrrN z-tv!PcWm}z>gIG}!fv~dpu(`xN={W3!n#x(>lsr%!Um{4uyMJmCTgVr^T?S>MS9%X zFDlQ^dz;*;);Kl5&RPZPLKyHfkf*XMISoqY@2o-PBTSqp<{D?WrtK&>g#Qf1`tHm) z*t}-!! zBoZaOOpu7tmFI{~(bZEAQKkDs_JC+tTpcU~btYXd!?Yg6B64}(?I^BRF4Rd%0sm#C*O<-!3f*S*;>{UfXvm0k_K+sa% z@I%J}FBR!LN|TmYv}U}V!fKnCs>74}*WO*^VAF>^@t2ygRtxNV(^4i#Bx^%V#t~Lj z<$0`4Mz*1Q`uvCFM(ytMOLXnh3#=9FwaeOwK)>HussYn;E0vXFu$v@J{gLjpq|^u} ziJHAi?h1PjTL>-M)cmP@PV6Y2Y-s;0e4r9F0ws#>*@t7)*k=yzF&hLM!>CV~j(*{H z@vlVtiB?!sBbfUA=2AFXQxE#XWazsF{C;natwhRzBYAxnJcTNRekku;6VS8xlmxOZ z4?9pk{IEs6xgqfjp%)b8TFYv!Mq@FXt^0HsL-pH>>}IyR6`0a6c|q(3%tK&`|H+RQb|lx+r7YXBD1% zj^s|ScSjHc###Jak9J{5r2|BvUZ>4AHr2j`LaUIFD|`llKf*rSf+?{oro>eq>(HHRROdLx^#01QBtZ)3sURA z6I&7m+*_8Nd9z80X8kVvCoD&mh43TpW1#%&D_p+wdb0W${uaNPR;t)fe!2HU_|g*; zM!6roq){Ta+`OLUcetV^$M`tyhPgR5%t|O)RDevjy=Nnpc%}MkRjgS!4R-D^ati)CK*U4UDlo?W?v;`xtI=J4;f<)OG zCPj|^Qinr%!6DZ^rp5`6T-Qb1b8M$39u>o9P(6v*YpA-l_pdwjDW|XxPgZft*O6Ji ze0s8ikInRY>MtB+?Kl~o`Q>1jV4X=dnK!?o6-;-@92H8+WRfmSF$kYN|W*4gbKw z=&POUPJSHpir*U-qQ+lyihUEn@Yb801O~Sz{khe#!pa9L&pFo}c8-{9eLz+rTq5?R z*%9?jr16Vl{UO5(JlDa2TLTWS&5!k+gN5y!Oen0LR|zF?KrrU%q^}ZEU@DghCv`dq ze9V_B3+=gjjQm$);f^jpYQ9s&$BHQHS+`R^xH%!sH_7MA8$4o?cPvJ}9WPORNb^kT zN%(W7G2{BZF`pKxBs#xxgJ6Fa9gZZG^wCT2*x`Mv7CWjV9){vq5F=}_xS|n%$*(dd zlec`HO{mfBM16F+kXc*((e*9b#;hrF zS~OSOgk)C}4|++&zIvPE?oTfMMV~3_0f5RLHN} zwfOO4Y<|%xoqK1id%f+4qE&Z@xYIkz5S&u=Qg34D>>rgnQXMAZu}N{Xe$(_8Z(H1B z4nxDI)w?vyOf;l<7W#9yDyn$CEoBGI-g?cNbHg|QF<{-1ULm`!5WM^|yI>u+r1fB9 znQmqjr5NlnlNt<1KjJ@6@3B)bL1s3>#_=`tz9pSXT_soEe z(`kx^Nqg9&y}#5Tmjz`mO0YjL)h-XP@g9~nbxofq?Kka5#2DNjtsM}Jt|h&+*q!l zHXbcKi6CrnD{*S&!{tn;w?)yRDxQC5JNyL~<`Wrx#}R*S;8S!jNJlE{lx@P@V-bP! zq|LhNRzX5rYBGGD(FhvaTGP}YJt9f3CLgz}&V6-=5}Z`B^3#1QuJaa^D!3T(if!@s zz68yA4w3V{SrJALO+zL&T%Z@wZ>@yN` z#MoYXJs+jo7AAjF)~Fkvz-+quQ@MkjE8-=a@LSU|MdddhRk8JvOccGJSGnbF!uO2* z=%M_e|8>S`Chf^Phi?zneO$fEWvJ(j`WTYHl7m7ga}g!pXFi&{9XQLA zPfONSaIFg2%@vOBl%in^2XmO4&IJ+|=|&rh%VZelf6^V*_E21QsPvqW(zJx_^h#so!D(ud`?tX zSSJ5!Mr!eUFM+BcA|a4;I!VAg8Zl3%f;j7P0scNN7iqgl!U)orNm184S9_L_fyEyzrz=O+pkJUd^KE%DUq}vF?yZD@KAX0sYdYG4P##gwW6v+ zy?l6&fL|-${s7xp4)@_TjIGZyYYnd8om!`^vYNtvEN#rD&{wtb*GmJ@Z)7{nWnwrL zQ$1Gz(N!=;+B-)!ebmefwz>}_gib~X?S>8@Uf!vKa|b)sd#h%cWfOZtorERM;%SXKv*0^^xHd58ehEo7{WGnsIO} zboCU2$aTC4r}((lm%17@nBrChPRZ@J{5PNbhzcbL*X5v`SLAm)ANAIKWm;vt(0EgA zPJpdWXy1w#^ZdD0y~#f{tvxbAs#H=XmT^k0(Z%m&W|0W@#;TweWY|KVNB7uj-XqW1 zg^11OvSv|utPf?Fy)pzz)aH#~sI%J(prMvA7mnC7+ z;pVk4W8Oc%7*&)R*i_iw3tBs7Hi$Gmm36T~@pvxdQACGg^jkStwJ`hS5yk~wEFIMr zEp4t&w*`zVt0{lb;MJ;+E|%}iQnoFiKZWAg0df)6=>GrJ_Lgx`ZSVWP0xG32G>S9= z(jX!@fV3bDf^&3)NhUFb3Et!`k$wN9x}0G z*4lfo>%Q*y6^VB-*DPAI^BBr|4LVP3O0kOZAWdd8H~R)H$RU30aTmEjH`)4HngQKv z9ZI-nbhLcXf(oi0)LI#Q*5fmi*2|LrGJ5@1NOg=eZOy_@7!vwzb^P;8BS-cP3y%~j zria?09F#OIl1?V_61%%o!ehSS3iGU$O9_azRdfYbzpZq!p}`m_KW}dg zquv<*?VmhOn!F-On)(dNHu7XQ-pYy2635%E$Xi<0pFDQo2c8VB$G|ftQx6tnD)##>!+t9Em^vzL z!GH97Z&}lv$LBz}@UEXSj~aX>Cvqx9*jjGy_sBo$6?Ofmx*b+Pyjdq!dB7zcl=j z^+eMD0FhLyRbVyzajYeYl_tk+M3JRv1@Oj8B*1a&VkNrOGMQ0HqrG_FzomR7pD$Qb zoE_aoOAt4$m>a0^n?JJr?2m|l4%~x!KF2e&Rrv3Gex0Aqo|mfvyI*sHkvIAi!4+zP zFCe{m=^>;Qo2(3CaJ{R3shpFlBmW>RUi_jCSi@F1)>_D$xEiY^uzK3a!P7;v(`Mjo zEA+$4n#Il?UFV>=xAeEM5{re{1I_05_jZ9>Czt=_7Enc$Cf{Pd(&H3u&4n_)0M@j} z$D6IrWh6)x7dgqHH)-BWV2D!kKFV5!7Ec3^zQPF_?78T?>)yDo!h7pq;gUWQ%Nh1n z>BG;d#s;ua=0#}N&E{3!lE*(Pb*W7>y<>su3>;r6O3=^_tnE!3lqZbf2S1(typ}y@ zHC~IBHFkz@e;@~?BD>E#jTOIYJv(B+h~Ot=-dzLr331(F3fI}@GvJ;yJ_VmDmpJNI z(^=VFXSM>)$Aod+4$OGcGQg2!uSlJWe|G~so5$r9@09jH5;JdQEmpBBt_;UAPD;*Ct`252s~m14Jo-j!4X zzo8=faC)*`r)?*>x&rI^e#BnY#OG0Hcas>KN$!4{?O0 zZg3{5Hv(7X>vtHeLAGVJuu@{v$Zc4TMFvkwAiS$o4UjQrSigN<1TTAH6=PPZU4U3u z44ehOs$SbNkF(;s1gP-!E+&e}o6N;vlb^BXUC9OUE=QAemyu#o5R}nT2C^gm5%;|n z({mZ>z*D9U_s{3zCvQrB=@1Cm zC1A8m7TrDHZ|ul^RQvwR4h)S#+x>=iP+mIxi`a1vTyjd2oX@)6k^X;V{JDz+0NH`J zBlu42CHQeXxHzeQ<2n*ff8#nTI8>mK6zdotByFUm#RJuO^a~Ya*S9|cz6TY)QMJb> zF2jtdr6a)VFy~!1rk%fd_7&lqf>*RnH?ZgOQ8MWRKpj+Iuvm>vUr^1+VoyFR^LoIn zq2;qcUtR6X3R`A1)RNp;JA^E83aw!UdA3i~U8`6ZwyqXrC|GZ?5#hZe6k!rfzEgt# zOk3V0U3x3zNmrjhxBw$D4hWq`aKR++upR-ri&76{_`>!fu*+saN22fASJcWca87=d zdEGk~;jw129Y|g1nzN5>49ON^M$^sTKrj)CeI#u!=wx`cmNyQnNvWG?R%|4NV=fRew;re+viDzQk5C;Pa&grfgZvDx<@9@eg@gf4u9zSJ*~;8*WNBs<0xUK?$rJ z)^?3Y*Gh$gt`)pN$6yZ0p7IlrbcpQ|B4_ch{SIW&d;gT}hle)$`FH<*#si?ZiwwF) zm;A<0_u@p;P|-g8l?tn165Ud{+*>TMS4t_9Dxs4bF45@~?QL{|6h1TE1}Zb(IHhES zrJW{>U2C<$dO)gn98j}773E4-Pbrs!qMO0>@u;-Y<0eV3_vBr$vZgc5x}Lkzz5WL4 zP)+s$@d4LiH<7(b8g<(B)30z_%xz`+PfM0q}3pf zW9f);`l$w%ca$#3%1;&;ggugv;5k`#LI2qAK@ zI0C`=#!1$mcB_E3bdQkz04hatdH*_DnrVs_7_1Uj$~nPjK<4XR)>i1Ma#3ZgV z#KMi>Tzq?q+lOS+&p$?-F%~X>49@&GJC+bqfwyxppxh&Psz!Qe6WZDVv7|+KB<18tO8z#`|KL{4|mdGTVj>woxrNEGQiEjt}wC z1Cchhl^?}J3rnhxrJu%Zz*Ky^`_qbzYcc@?Br@)3fx3gE^_WtiVor|fC+s^`fnM

g(jRv2W`BadYs4<%6k&JwH@m!Nodc|@iIozz>x#B zq68Dl-paDEFFrE-PWzb#CtHgj!g3uq?s-zBytez|kU z@2l6yUU``_IE?Kl66S$gF=cr+(i8OWjvvvq%T-xc`aokv9UG ze}?Gf)W*3$d>D)FsP5mJ?75yY*4h_(I;bL;1=3ugaik1!}n7(0FHKrJ-nL&OO<7O{yyrNNRbd`@&RtBg93+ff_epZZ7;}_}pe+7z=1M%(8aE() z1XFm|l`biJ$crF777OF-4zN{1r4l|q1_!MFdP#4+EOv)uAXrv9Tikn^ki46#^9G0H z^5t#RE#jM;k~`Hmcr)OvX_^m0%(YxH!bKADt5hi7PgkV#({zotYK`*w}cxNci}(SmS89X`W%=N_yg zN2GYp9)gR#z$HAl2gS=%Ah6%LEiKNh?GvtP%gf)+K`0%sL2oAL)zyhRqu#yiHR22E z*lIqBv3nBjcV6s?e=yO{IpBZV=PGDfG4tw+Z1lyXVa$EHLdw$%28c-lN%e2H9yK$w zB;0}`J$<7|RzzV5UX-rJeWSgiNK-=#iaXb~kF`;DrwQzfY&nCT2-wJ{Eu%%ml*pB7 zr=U334Mfv}S;mHaX~{U#BW0mi$%r}uf2Hd+!8*h|jnLs}RF~mEug%jWu11?sQI!dQ zbHTu5_{=xAGi!x|1$c;gSf@IuqQ_-Be2 zEo$&JUwSfGB-eH~;3_@EPmN~i+w~2PW3bvWwl^tMBY1llmJnIhv=HewrjYdgoB!P9 z0AISu27Xmx2)B6!$dk1c;hZ+|?zibKi0#-eOFd0r4E6Y#iJHqmQ!#Dt-C5x|$VeiE z6Bj7V#Yx)JXA+{<2p&`3jYT9CWs3^XESNYOA}HseDx;nbD)H5?2xD_2alUEG8xlEW z9JV>1%p2It$2}uk?H2}* zYm;wpirgG5h&DA&3TM7H^lCXT43)iHQG!r_mrVE?LuE7WI_hc=go~vt~_Y(LPG$H1+AsaAr#6 zN6aZqLL>GedE`O`fm+}W!+uRzQECBa6f-<3bu0U-RXQ~j-c+CcI|XUi&9;LSmo9B} z-E=&^oMLt2ACGPkuOOlK1fdTI}*7$Lm}xcbwTHv`Su50nZBvH`h9XEscjh- zc^DK{y{P_Uy|+Wf{jIjTFWSF_D6<( zri9?-!ic-F)80-I_OB@f=rtELOB~~xFL@d`wOcW5mRGksv(VUbeS#tvxE-lY2u;Gw zXqsfT`O5^H?(E4Jd_8i9<0ABKTtWzcch;QC9EtIYfduFE8=`FdY-OosjiaA-ZOWOe zo13df96KsRYLXiF^?DoTR#{MYgr&ARp3KjLhiWp&k7B03JXX_?oY0yd-Ojqrc_UWX z)7gSsc<@T?&E6-SfW+Qf>T3k+o%*~jqu9`H1e&<|UBoPNgkQcw_zO6n2wGmh@{dHq zuZKtf#P7?EkPuEL9miFsLVdR<3j}V^2s|=4OFk>%@$oaF?{|!Nh0cu;u&XWTu=>_~mvm@OcY6EoVCq({d10J6IDRMV# z^O>ch?&P=oR<$YYA)@Sod@TMiG++eK=*tvW1php6GS##)XV*Tv9-l zIVaO1NpNajkTrM`j*Iq+^jc=QE|*Y~T>cb#k(v2Y3q0D_E!xG3$s@^x{)Kl10^cS{ z^(V2FZ5C$PwmuUQSS!V0&P%hpo-q`|0~ey_zm?yC{~qtxZ>qpJB-!5@(?|13EuV5b zaDTyi^$?OnL3B^anvXr7qYSsYRpJgwC-=UXmqw<2Jm1FvG!>%j0gZ6`uh2s=4HtsC zLv{*c??MuoY`=RX`E^t>d*@6KD^haaE-PpQ#Oe>6gX%#z}sjZCJL~7O-M(@hAYUg%*-zB@nO81K6gKjR03r4$W zwzj)ixUq21j<((ilDHe2W)M^PtdGXRTaf;3(7Ex2d!;d=Rv&&Jz@>toLm*9l7egbPfs+fu4*iB5ieEOaA1 z+$EjqYvhG)@EwbA@{FkOeBAwUb+~r+5iLdeHph!`g(haUVUm7blUMWviIlG^sy3VH z;YEV+)US@1B7IN2y4QPX{5VMR3^_-Uwfr{3Jfp6n^9q2iNC_FCwLsCVBD$MlsH0Ek zH9O2~$5ckU@0z?j-kpwn;0sV43RrfxdmL+X8X~!GdMerTW%=&E%VcC4^^v_NlESe_ zabLD?FX1hXE2=!#OgWv(L_dL}%4dXD~4LHY21V4Jx86uDTi<5Mfv&Bc3(C;T1 z_MuBCi}v{lVO4?+v^0a#QBmGA61&#T_7)rGx#9sD5*Ny$0sJ}kTxfm~&n$1d zwqMSVzAzm6d-6V8Bnp?W@u?9-b7-J-FfgBK=k|PJ^qPR_Ud$zgS3Dh=PnV6LRyWO8 z>?1^MZ2}Wxe!;-jqB?A}Ya(Ht(H>{3`58qdA8tnNxG)3942Ef;?*vI@E8uj~Gc0q( zzulWgq7E04c7ty4G~aYV&ez8?>F=7nI%x^Zwm(7j;5U6Kak-D@75!B2@o&6ltw6#! zj0??bGfc*Guqbb^_G=uh786eX=taQ5WaAi^^+hQ1J1HACU(i87jpi2lB#U+%uRoZ| z4|)m|N-F&tIN{4o>c--hVw-9Q@$n(L(>bf84aNGG7o%QiPH>csDklE?+%C5LqjRAr zf;upQZf>5S;PI)Rcej14$6eYikCK_g>ND>X5*GUJw~%hxsYC4cG?3?lcVpU0WPGbP z3^V6emnB9AO=^Vr(uz@w(Ju`R>RoN{?HNpVLg6CB4~LzGS(&X!aUr(-<(D7goYfUQu^U_L`j06=e8U?N<1FjyA2DY%5za z3#9@4F%K`}p)anxnPRkCd$nJM8Rqojxy&r-^oE>?*yP5g1 zvk-2G%YyW!fRL*J>i&y7YeWX8VH3KA(c#zWpCZ447yf9={}zsE75o+8QT-VYS$hIW zPrqn4KCfi_65=nS=*9WXt?glTdBvZ{J*42rKn=Pr&f(jPRzfeD>#URTU}+NZdwNE?Kg-- zW(#kI&7A;3wXgn>h0W1#cy>}T)XHWj!Rey>n;5>wUupErBiL3-PqBfVC~Pj1q$~V1 zzRs)n=F1=1M1Q1h69A1?p+zR^=yUq$PBn()g7#-+(T=`1&W_P;L?gA1gW@2gm3#&3 zn19>WvVz*1G?7FnZrwq!I)bkFb+0)gy&lUQUK-`g^*hSH&u1ESz1!Iw^fZ_WbKFpo_?#aiFT z>Aijkzz<0L4rScCAqv{SlCPFv>VdVZQKJeejD4_1@bYUa=WgQ)7|@(tI_>a& z@b;b*!|_ya*)ysYmAhXHUx7^6I8^hbu~!jP6^ib*OSei*7v5+bNUjpPn74i$zFWQrt>u(DPt4rzW9+fJD-4XnLJXS36So~KW3CJ?FDav?l|nob#>7y{bFDSiC?;gPxJMB3gVU&7!O* zMgKOM=4_{O5=i*G^^?}V&~w0(cI*x!;vwaswDtxDcpfev2c&aBIt^f+3B@8o-@2n5 zNqakRE$aX&+)%JC9f3u1eLys@{m{QLlha?F`4J0Ns8d~@v_`?42aE3M<8JS{jnZ0Y z#S?EG_GKyFT4xDm6a4%){lKSR;eb|o6O2nKt7!TR681|$M;D%RVnel`u#y54b`!P{ znMq{>0cZhs*!)x{06fRTvI7#6Ktl|h9cXHPM5QN!g&gZV;%jqbyBhF18b_FQXAb5lmYfQZzGnzFBxg87A*i_)~<%Cz~UEKBhL7g@0B(Zu1h0*r3(3mWjx z4}WDLb_|c35n>yT=lfe(yEDB9Wv2#S)$Y~D?UM(-t?iTU-#1=vXvlT-@7C>r8CeA; zV{Mh++5v|7oo{~k6ZI{>vT(95Bro;eVwQo%H@@cw<}OHD?h)BCi>U42rqHW=z52uy z--2dF2MKWIPAbQs645wl=W{+b-K2U49rA41!%Jpnp?Jb@{p@ft)@jgo>uc;*PCS~d zxgqC?bxOEs_Pn_yOJ_=w7jy(KXfeQUhZsj{E;q7MfZkMRM1Rf?bQIJ#;yeJ=J7i>I zY}jDeBWy%4$bP#VKP~JD+{X}g-mw07|>DP2VvSa9e(BZB_qb9xc>eAqh zCm0Uyv)L9*tBOT#-@%koy~(z70Z^z z6NENTuz7*@i&({~HVi#!4)G2UmtUqh{k#>NHd#1w&5RMIH`4(ME?kc6OEKdnO?kfC z0IFa8xwue$8v!QsUUQpwI@fLlJ2*q-*C+~sc<%th=+xX-Ly$zZ!SNOZB+{OccF-;| zBvW&Tm3H{8cGTrLt3%N1ai6d0#q#6H;w(PCrrCUEe3DbpUo-0C2M7{kEP)Ks4`Gin zDx5#?#hmMX!#ag-YtNurK?H8w#y(1r?x-O5cB-ysX5Rrg*?Qlf_1-MUCQye|lrqR% zwg?d5l$Obz6BkfD0~;EO+XKI6^`qvamCXCkv1AnY)08mjq)%WRwYPvuP%8&<1g*&s zn9f6;of|_kvPZhrtk*oDtlD?73FZ2BK z$LgUZw3f=;Hn`Qi2xXrFSa)Y56Q|XC2;$u^`d1~H8#EezVBQYbGKw#L16!V~2bw#J z<80znzOH+4as*f9dD(l*WzoI zT{f4Gg`I?Y=+S2wj)DQDte+zbxbitPBB9{!n|-ZD(YToKawgPu#S$;FNs2i%?+f7o%|*}dx3agtYtdmx|gjmE-iPIlURyj zOs0Cl)Rq*4E#uNSCtf0*-%Y(5mWo|@{R!8>OlRpur9k=~|f z7rq6Kw5(^IIu9iB;tsbOg0q5lpDpP=&67?}5`GpCj-jI<7A#9-BELX;EAr&E*_@bF zosWc-wq#u6Y6~dc-p489WhQ8dXS32dn>P9-HRBiA#}6eYc>dN;eUk<+C480g1ux-+ zbyvQD@H18@S>6Z|Bz8}{D)--*#@)my*_%o|oR1_W>!I)|V5W8ucvB+*x`epkNn3K) zTW`pp*^uOr8~ZMcVN{k1#N}==IshYx+P0Qtw|s5@iNmi%RAYEI7`9(&>*+x-JTIU7 ztsT4L;H@D@B&T$>(VpmRJtWTo^%2GD{lqAKZ2ARb3`~}fUr@kzDs--06wQrtie{SX z+TAni60$dbU-?nk@(pT3Om4c#Dc82jL~x~)`p0I+`5CYDO0uqT{C7?^VlGU6+&&h2 zvo{~cKu`XmfX`MFMSd<8x6mtH7o;ukjAU*>alPX|kj7;ytE#1=mxb zGFa)EguBW4^w{*&TPyxRmIbG_JE7jPP{qJHJ|_xC?NEw0qEGiO1qJ^hPUWL>j8W734aPddqDWRQHb*c6t`AaVxX;Bv0f z$33z$Hn?f!v3*=VP7ztRX}CQn3JGwe81b|4@y;gBsFJj)o+z<%y=&{D+Ssvyf&1xq zU#;#zX3>3fwgXLb4ke#?pLr`Oj<`wA^m3A|8yhupVLRRB%UZx6KNvU=Bn#oKWn>EO zd+60(H5o7Mz`z~Ud4<7Sz4$%?67tP>MaQF<1DPa)RHT0}-vISMJ@B~A1L%@n4e|QM z1p;~98a12|4}nZ?;S78jLl!H|oFdXbo=2wkOsjfQ{J}CT2K(`zPJ6esXw)07F`zeF z5fF$**hIL09M_}!T5g`YjbOyVO~mKI($pIw)E)3Bm=s(>gGI<}SLS8?QzG$kbMU3p zhpVP1_b=c*@ss~n7a*1t9=NSx%rz9z{>7KV6={m7VjvARz;AC?NkUjeF)BxhBf61K z@q!f+G>J*v$7eaMFmb+*N?BdQy4K>c0k85wP$tO)2rv`UWQ3yor6rR zU-rmjFvsdQ5)qQ;-jja0-aGy75Yz{Im6SLo*8p?~e-e9f48N$`ntGTc1ihajNE2PB za(`kKQ&e02le2AP2bL-zlj8-kXk$Q{95FucvpK(ctt!gbzw-DvU(&&%&@R~{{?0Pm z>9f?`0auJtEA{)IM2;BD5H8~Q6Ne4m+Sbfi{f_$`2FcT3qZ?X18`^#*>`+ycw-Y_S z@fdQ4+t&-3DW}~g_z{*)p*O3&sl9sC@Hg+d9Dukl*|49Gb6o^gw0rk`2a7BgNg@s)5UXoNTxkB?rQK<@ zeqJ;FJ-TKpyub&f_W;N2R%bY0-Wp3WcPT~x^hbF`DQ_j);-xy{%ob`iInJ1F0>_H9 z6ORj}`4r{79qTHIs@=`)c2cv_qyQZyh!X;Rmx(6H*IjN!@4)jQl1?LmBNK+ici@8G z+@aIrhGV!Ha#-E-p(!IDf*#qLDQ}+wZ;6KM)&0&_VxAFSS-PEGv^!QFckCuz5 zGEgmb__b~n414pHmW&Tz9+buWq`d4rR+;R92Zd}WaEhU*3oO=2fiJc!bce5=4ZAoY+NiL6`QKCmwaRbd&peus@2%WzAsJ+7VEVV>~TV zd$G}erg1E!xkkM+Uq3O_ha1ROjp~KV)~s|l74i!yC6%pyC_A-R`76#We?$sS+^fo|{+z=t@M|(n6 zoDV)b7!9kSIFQKKj+cSgO?kqsp+Q4cLO9VuU~9R zv3_pi#srSVUvw^s)jbj}e!YGnc~kagV>)?xW$y|u!rzTd{`!c#pBww-Gv$~LB`0yg z*$F=en;5d>r|W1&^P0L%Me5%JA{{#*Zrjcma-^gN;}}MAFpsrgrA@ctsec%D8Vbm| zdR!FOygvSjl}ky1Yx~5(HMf@)Xy5qPo6p`Z7m+KG-r=aA2;Jdjl1~Ck{?O6jM(_2_ z!6KjKVRyp7owzB1fU9!J(wfKWQL-srBNHY$53{I4>y3$)-cXQReZaG2AcL!WlEgDD zQ)W2zFlq*L{BD5g5UcsIR2F~yBgPLnpKICfKexm)vk^=co1QcpzLd#_myuT00nY3Kf|sa)%o72LgPq6V!+#5DZy(o1_j5+Pi4Lj6y0ISl!D7GltCR3t=&askr4i4U1Lxmqsib5y#X?Y;K4lT2s} zl+NLdqLT2`p@m+Dl~Un>%9Z$JFvnoOwvWf**ryKke1trG@+_hBgLH2D8hc}hPq?}a zk$0WlA?Drm0=312&vYjz?uV@Pk()V%EIb-U8-|O>`43+hHkEx-Gn05rEl;EG)p7M3 zQXQ)gO_c6Iv^Xp=En}aRNiVLN{b+rqo9WR{CCw52IGbl+dIDJ{N?gZY6wyoG|Asha zGDkP9@tZ4(B|MrR?Pyh|tR&*Vc?e5%DZ5+w!mYx^YTmk!kEB)Xl`ouNgdj-s^}fEf zyJ@hg1L=_A3|`8TwF46Z<1e7`GlXB`3m=do>aWlYVFt2j3NVQ_mwZ0#q7d!&?2h42 zIyJ<+L<#asu1!C@0Y34AT$UAwZ@E|O{;c&IG>{g5#JMu^YC>}p zv(Ci_CnGcLk}>q+$=>DOem{x7{${(8sUg|f#B;)U-lG#CD$0$yMf zdwb`{?0fYeoDZZ6IEj2p-)%tU+QVPl@AD@=yNLec9Bz+XfZNnmDkratMx=%xk@kd~ zoj=%=3_B}P2`(d3f>_{>lJf15-WWM>-$^DFK zRHK|H3)J;&L1Y@#zpvR#Xjdp1YE)2qR~sD7@*=o*=23>-Kq=pV#}^+O+28?5V9?8+ zyv8f*V`mWHQ$(S+I1o1G-Kho7EFapW-8uZaLE=UKd~evD?|~solYr=;Xq} z%l;|P$?;FSRKD;E^ab7dfj<9O@9FTATJK^^5k~03V~HemS1roxHXZFf7_^e#T)DJy zq!F=tG^iWy?-@4i;KbcD8?_X^MlGLsno#Dv+)E=mKFej)_G{>U8ecqu?X+#vIabTQ zK-5#U;xlnHvA)F0_Fd@`Kfn8pU#GP!Dte?Oea;WYGG^57|7wMtsv_A4 zT)VziS@QFq)^0${-myZ|cVpDhedryMO}6U&9HXu`l`q?`V3$v-w{XtI7V3{OFFC0b z(LW{^4S7pB;bKfdBq+Bp+yBHTsah5Kj1u`GA9Y9jS}dn`uL%Z|d)+$p3@~uKnx@pzFFch_J)m{^tmpowp18H2*T*NcuBQpBitWVemHfmJ zNlekg#H6jag%5~GX6q_@bM$1xO0MSRM^k2o4hSAne3jDYQnIRm?Vo6a8~I+x!SV4( z?N`LA#~GDe_f%w)u8(~*rPR&avxdg5r&AgBes?W$PfjF{bt;I$8=6yw-6e}8*_{#& z#=L7lC(M0E{(E0};&y@_kWR7T?aTVVT)(I_$>>tX@`bGQ?7d((KKpCD03(?<_%3ai zd?r!V?N^>TG~hJ}>+mP3(h@))$$9)J;_)zRgU0u2YAdl~Zv&)wft~%*jChBZOu5g4 zIwhDocls8sn zq5Sjz-yZ~TatQC#a1N+ChqRi}E7Je9@8bp(Ks%Go zP9FthvMv5&1K=bnFw)N%Ie!@Yul=K90{f2tmX+|&9{;^(zdwlQF=@cRNAe#p%HaoJ z;0xr8_~-3^pAIS0NeKQ$3O}XPq`@d|vq;>uu wFFpx=fWOV0mca2JXW_-o0Mh?I4)KKg;@(7sSx>Oj1@NaRrz%?|Z5sIh0Q9lZ_y7O^ literal 0 HcmV?d00001 diff --git a/deploy/paddleserving/recognition/README.md b/deploy/paddleserving/recognition/README.md new file mode 100644 index 000000000..0ece4fbd4 --- /dev/null +++ b/deploy/paddleserving/recognition/README.md @@ -0,0 +1,176 @@ +# Product Recognition Service deployment based on PaddleServing + +(English|[简体中文](./README_CN.md)) + +This document will introduce how to use the [PaddleServing](https://github.com/PaddlePaddle/Serving/blob/develop/README.md) to deploy the product recognition model based on retrieval method as a pipeline online service. + +Some Key Features of Paddle Serving: +- Integrate with Paddle training pipeline seamlessly, most paddle models can be deployed with one line command. +- Industrial serving features supported, such as models management, online loading, online A/B testing etc. +- Highly concurrent and efficient communication between clients and servers supported. + +The introduction and tutorial of Paddle Serving service deployment framework reference [document](https://github.com/PaddlePaddle/Serving/blob/develop/README.md). + +## Contents +- [Environmental preparation](#environmental-preparation) +- [Model conversion](#model-conversion) +- [Paddle Serving pipeline deployment](#paddle-serving-pipeline-deployment) +- [FAQ](#faq) + + +## Environmental preparation + +PaddleClas operating environment and PaddleServing operating environment are needed. + +1. Please prepare PaddleClas operating environment reference [link](../../docs/zh_CN/tutorials/install.md). + Download the corresponding paddle whl package according to the environment, it is recommended to install version 2.1.0. + +2. The steps of PaddleServing operating environment prepare are as follows: + + Install serving which used to start the service + ``` + pip3 install paddle-serving-server==0.6.1 # for CPU + pip3 install paddle-serving-server-gpu==0.6.1 # for GPU + # Other GPU environments need to confirm the environment and then choose to execute the following commands + pip3 install paddle-serving-server-gpu==0.6.1.post101 # GPU with CUDA10.1 + TensorRT6 + pip3 install paddle-serving-server-gpu==0.6.1.post11 # GPU with CUDA11 + TensorRT7 + ``` + +3. Install the client to send requests to the service + In [download link](https://github.com/PaddlePaddle/Serving/blob/develop/doc/LATEST_PACKAGES.md) find the client installation package corresponding to the python version. + The python3.7 version is recommended here: + + ``` + wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_client-0.0.0-cp37-none-any.whl + pip3 install paddle_serving_client-0.0.0-cp37-none-any.whl + ``` + +4. Install serving-app + ``` + pip3 install paddle-serving-app==0.6.1 + ``` + + **note:** If you want to install the latest version of PaddleServing, refer to [link](https://github.com/PaddlePaddle/Serving/blob/develop/doc/LATEST_PACKAGES.md). + + + +## Model conversion +When using PaddleServing for service deployment, you need to convert the saved inference model into a serving model that is easy to deploy. +The following assumes that the current working directory is the PaddleClas root directory + +Firstly, download the inference model of ResNet50_vd +``` +cd deploy +# Download and unzip the ResNet50_vd model +wget -P models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/product_ResNet50_vd_aliproduct_v1.0_infer.tar +cd models +tar -xf product_ResNet50_vd_aliproduct_v1.0_infer.tar +``` + +Then, you can use installed paddle_serving_client tool to convert inference model to mobile model. +``` +# Product recognition model conversion +python3 -m paddle_serving_client.convert --dirname ./product_ResNet50_vd_aliproduct_v1.0_infer/ \ + --model_filename inference.pdmodel \ + --params_filename inference.pdiparams \ + --serving_server ./product_ResNet50_vd_aliproduct_v1.0_serving/ \ + --serving_client ./product_ResNet50_vd_aliproduct_v1.0_client/ +``` + +After the ResNet50_vd inference model is converted, there will be additional folders of `product_ResNet50_vd_aliproduct_v1.0_serving` and `product_ResNet50_vd_aliproduct_v1.0_client` in the current folder, with the following format: +``` +|- product_ResNet50_vd_aliproduct_v1.0_serving/ + |- __model__ + |- __params__ + |- serving_server_conf.prototxt + |- serving_server_conf.stream.prototxt + +|- product_ResNet50_vd_aliproduct_v1.0_client + |- serving_client_conf.prototxt + |- serving_client_conf.stream.prototxt +``` + +Once you have the model file for deployment, you need to change the alias name in `serving_server_conf.prototxt`: change `alias_name` in `fetch_var` to `features`, +The modified serving_server_conf.prototxt file is as follows: +``` +feed_var { + name: "x" + alias_name: "x" + is_lod_tensor: false + feed_type: 1 + shape: 3 + shape: 224 + shape: 224 +} +fetch_var { + name: "save_infer_model/scale_0.tmp_1" + alias_name: "features" + is_lod_tensor: true + fetch_type: 1 + shape: -1 +} +``` + +Next,download and unpack the built index of product gallery +``` +cd ../ +wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognition_demo_data_v1.1.tar && tar -xf recognition_demo_data_v1.1.tar +``` + + + +## Paddle Serving pipeline deployment + +1. Download the PaddleClas code, if you have already downloaded it, you can skip this step. + ``` + git clone https://github.com/PaddlePaddle/PaddleClas + + # Enter the working directory + cd PaddleClas/deploy/paddleserving/recognition + ``` + + The paddleserving directory contains the code to start the pipeline service and send prediction requests, including: + ``` + __init__.py + config.yml # configuration file of starting the service + pipeline_http_client.py # script to send pipeline prediction request by http + pipeline_rpc_client.py # script to send pipeline prediction request by rpc + recognition_web_service.py # start the script of the pipeline server + ``` + +2. Run the following command to start the service. + ``` + # Start the service and save the running log in log.txt + python3 recognition_web_service.py &>log.txt & + ``` + After the service is successfully started, a log similar to the following will be printed in log.txt + ![](../imgs/start_server_recog.png) + +3. Send service request + ``` + python3 pipeline_http_client.py + ``` + After successfully running, the predicted result of the model will be printed in the cmd window. An example of the result is: + ![](../imgs/results_recog.png) + + Adjust the number of concurrency in config.yml to get the largest QPS. + + ``` + op: + concurrency: 8 + ... + ``` + + Multiple service requests can be sent at the same time if necessary. + + The predicted performance data will be automatically written into the `PipelineServingLogs/pipeline.tracer` file. + + +## FAQ +**Q1**: No result return after sending the request. + +**A1**: Do not set the proxy when starting the service and sending the request. You can close the proxy before starting the service and before sending the request. The command to close the proxy is: +``` +unset https_proxy +unset http_proxy +``` diff --git a/deploy/paddleserving/recognition/README_CN.md b/deploy/paddleserving/recognition/README_CN.md new file mode 100644 index 000000000..58efd6ba7 --- /dev/null +++ b/deploy/paddleserving/recognition/README_CN.md @@ -0,0 +1,172 @@ +# 基于PaddleServing的商品识别服务部署 + +([English](./README.md)|简体中文) + +本文以商品识别为例,介绍如何使用[PaddleServing](https://github.com/PaddlePaddle/Serving/blob/develop/README_CN.md)工具部署PaddleClas动态图模型的pipeline在线服务。 + +相比较于hubserving部署,PaddleServing具备以下优点: +- 支持客户端和服务端之间高并发和高效通信 +- 支持 工业级的服务能力 例如模型管理,在线加载,在线A/B测试等 +- 支持 多种编程语言 开发客户端,例如C++, Python和Java + +更多有关PaddleServing服务化部署框架介绍和使用教程参考[文档](https://github.com/PaddlePaddle/Serving/blob/develop/README_CN.md)。 + +## 目录 +- [环境准备](#环境准备) +- [模型转换](#模型转换) +- [Paddle Serving pipeline部署](#部署) +- [FAQ](#FAQ) + + +## 环境准备 + +需要准备PaddleClas的运行环境和PaddleServing的运行环境。 + +- 准备PaddleClas的[运行环境](../../docs/zh_CN/tutorials/install.md), 根据环境下载对应的paddle whl包,推荐安装2.1.0版本 + +- 准备PaddleServing的运行环境,步骤如下 + +1. 安装serving,用于启动服务 + ``` + pip3 install paddle-serving-server==0.6.1 # for CPU + pip3 install paddle-serving-server-gpu==0.6.1 # for GPU + # 其他GPU环境需要确认环境再选择执行如下命令 + pip3 install paddle-serving-server-gpu==0.6.1.post101 # GPU with CUDA10.1 + TensorRT6 + pip3 install paddle-serving-server-gpu==0.6.1.post11 # GPU with CUDA11 + TensorRT7 + ``` + +2. 安装client,用于向服务发送请求 + 在[下载链接](https://github.com/PaddlePaddle/Serving/blob/develop/doc/LATEST_PACKAGES.md)中找到对应python版本的client安装包,这里推荐python3.7版本: + + ``` + wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_client-0.0.0-cp37-none-any.whl + pip3 install paddle_serving_client-0.0.0-cp37-none-any.whl + ``` + +3. 安装serving-app + ``` + pip3 install paddle-serving-app==0.6.1 + ``` + **Note:** 如果要安装最新版本的PaddleServing参考[链接](https://github.com/PaddlePaddle/Serving/blob/develop/doc/LATEST_PACKAGES.md)。 + + +## 模型转换 + +使用PaddleServing做服务化部署时,需要将保存的inference模型转换为serving易于部署的模型。 +以下内容假定当前工作目录为PaddleClas根目录。 + +首先,下载商品识别的inference模型 +``` +cd deploy + +# 下载并解压商品识别模型 +wget -P models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/product_ResNet50_vd_aliproduct_v1.0_infer.tar +cd models +tar -xf product_ResNet50_vd_aliproduct_v1.0_infer.tar +``` + +接下来,用安装的paddle_serving_client把下载的inference模型转换成易于server部署的模型格式。 + +``` +# 转换商品识别模型 +python3 -m paddle_serving_client.convert --dirname ./product_ResNet50_vd_aliproduct_v1.0_infer/ \ + --model_filename inference.pdmodel \ + --params_filename inference.pdiparams \ + --serving_server ./product_ResNet50_vd_aliproduct_v1.0_serving/ \ + --serving_client ./product_ResNet50_vd_aliproduct_v1.0_client/ +``` +商品识别推理模型转换完成后,会在当前文件夹多出`product_ResNet50_vd_aliproduct_v1.0_serving` 和`product_ResNet50_vd_aliproduct_v1.0_client`的文件夹,具备如下格式: +``` +|- product_ResNet50_vd_aliproduct_v1.0_serving/ + |- __model__ + |- __params__ + |- serving_server_conf.prototxt + |- serving_server_conf.stream.prototxt + +|- product_ResNet50_vd_aliproduct_v1.0_client + |- serving_client_conf.prototxt + |- serving_client_conf.stream.prototxt + +``` +得到模型文件之后,需要修改serving_server_conf.prototxt中的alias名字: 将`fetch_var`中的`alias_name`改为`features`, +修改后的serving_server_conf.prototxt内容如下: +``` +feed_var { + name: "x" + alias_name: "x" + is_lod_tensor: false + feed_type: 1 + shape: 3 + shape: 224 + shape: 224 +} +fetch_var { + name: "save_infer_model/scale_0.tmp_1" + alias_name: "features" + is_lod_tensor: true + fetch_type: 1 + shape: -1 +} +``` + +接下来,下载并解压已经构建后的商品库index +``` +cd ../ +wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognition_demo_data_v1.1.tar && tar -xf recognition_demo_data_v1.1.tar +``` + + + +## Paddle Serving pipeline部署 + +1. 下载PaddleClas代码,若已下载可跳过此步骤 + ``` + git clone https://github.com/PaddlePaddle/PaddleClas + + # 进入到工作目录 + cd PaddleClas/deploy/paddleserving/recognition + ``` + paddleserving目录包含启动pipeline服务和发送预测请求的代码,包括: + ``` + __init__.py + config.yml # 启动服务的配置文件 + pipeline_http_client.py # http方式发送pipeline预测请求的脚本 + pipeline_rpc_client.py # rpc方式发送pipeline预测请求的脚本 + recognition_web_service.py # 启动pipeline服务端的脚本 + ``` + +2. 启动服务可运行如下命令: + ``` + # 启动服务,运行日志保存在log.txt + python3 recognition_web_service.py &>log.txt & + ``` + 成功启动服务后,log.txt中会打印类似如下日志 + ![](../imgs/start_server_recog.png) + +3. 发送服务请求: + ``` + python3 pipeline_http_client.py + ``` + 成功运行后,模型预测的结果会打印在cmd窗口中,结果示例为: + ![](../imgs/results_recog.png) + + 调整 config.yml 中的并发个数可以获得最大的QPS + ``` + op: + #并发数,is_thread_op=True时,为线程并发;否则为进程并发 + concurrency: 8 + ... + ``` + 有需要的话可以同时发送多个服务请求 + + 预测性能数据会被自动写入 `PipelineServingLogs/pipeline.tracer` 文件中。 + + +## FAQ +**Q1**: 发送请求后没有结果返回或者提示输出解码报错 + +**A1**: 启动服务和发送请求时不要设置代理,可以在启动服务前和发送请求前关闭代理,关闭代理的命令是: +``` +unset https_proxy +unset http_proxy +``` diff --git a/deploy/paddleserving/recognition/__init__.py b/deploy/paddleserving/recognition/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/deploy/paddleserving/recognition/config.yml b/deploy/paddleserving/recognition/config.yml new file mode 100644 index 000000000..9ccd0cfc3 --- /dev/null +++ b/deploy/paddleserving/recognition/config.yml @@ -0,0 +1,33 @@ +#worker_num, 最大并发数。当build_dag_each_worker=True时, 框架会创建worker_num个进程,每个进程内构建grpcSever和DAG +##当build_dag_each_worker=False时,框架会设置主线程grpc线程池的max_workers=worker_num +worker_num: 1 + +#http端口, rpc_port和http_port不允许同时为空。当rpc_port可用且http_port为空时,不自动生成http_port +http_port: 18081 +rpc_port: 9994 + +dag: + #op资源类型, True, 为线程模型;False,为进程模型 + is_thread_op: False +op: + recog: + #并发数,is_thread_op=True时,为线程并发;否则为进程并发 + concurrency: 1 + + #当op配置没有server_endpoints时,从local_service_conf读取本地服务配置 + local_service_conf: + + #uci模型路径 + model_config: ../../models/product_ResNet50_vd_aliproduct_v1.0_serving + + #计算硬件类型: 空缺时由devices决定(CPU/GPU),0=cpu, 1=gpu, 2=tensorRT, 3=arm cpu, 4=kunlun xpu + device_type: 1 + + #计算硬件ID,当devices为""或不写时为CPU预测;当devices为"0", "0,1,2"时为GPU预测,表示使用的GPU卡 + devices: "0" # "0,1" + + #client类型,包括brpc, grpc和local_predictor.local_predictor不启动Serving服务,进程内预测 + client_type: local_predictor + + #Fetch结果列表,以client_config中fetch_var的alias_name为准 + fetch_list: ["features"] \ No newline at end of file diff --git a/deploy/paddleserving/recognition/daoxiangcunjinzhubing_6.jpg b/deploy/paddleserving/recognition/daoxiangcunjinzhubing_6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc64a9531db0829d42b51e888361fa697afd080f GIT binary patch literal 45098 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%0AFwsN&t+ni`;vRcQT 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 literal 0 HcmV?d00001 diff --git a/deploy/paddleserving/recognition/pipeline_http_client.py b/deploy/paddleserving/recognition/pipeline_http_client.py new file mode 100644 index 000000000..8a9ffd536 --- /dev/null +++ b/deploy/paddleserving/recognition/pipeline_http_client.py @@ -0,0 +1,21 @@ +import requests +import json +import base64 +import os + +imgpath = "daoxiangcunjinzhubing_6.jpg" + +def cv2_to_base64(image): + return base64.b64encode(image).decode('utf8') + +if __name__ == "__main__": + url = "http://127.0.0.1:18081/recog_service/prediction" + + with open(os.path.join(".", imgpath), 'rb') as file: + image_data1 = file.read() + image = cv2_to_base64(image_data1) + data = {"key": ["image"], "value": [image]} + + for i in range(5): + r = requests.post(url=url, data=json.dumps(data)) + print(r.json()) diff --git a/deploy/paddleserving/recognition/pipeline_rpc_client.py b/deploy/paddleserving/recognition/pipeline_rpc_client.py new file mode 100644 index 000000000..fa43cf432 --- /dev/null +++ b/deploy/paddleserving/recognition/pipeline_rpc_client.py @@ -0,0 +1,34 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +try: + from paddle_serving_server_gpu.pipeline import PipelineClient +except ImportError: + from paddle_serving_server.pipeline import PipelineClient +import base64 + +client = PipelineClient() +client.connect(['127.0.0.1:9994']) +imgpath = "daoxiangcunjinzhubing_6.jpg" + +def cv2_to_base64(image): + return base64.b64encode(image).decode('utf8') + +if __name__ == "__main__": + with open(imgpath, 'rb') as file: + image_data = file.read() + image = cv2_to_base64(image_data) + + for i in range(1): + ret = client.predict(feed_dict={"image": image}, fetch=["label", "dist"]) + print(ret) diff --git a/deploy/paddleserving/recognition/recognition_web_service.py b/deploy/paddleserving/recognition/recognition_web_service.py new file mode 100644 index 000000000..f8beddfbc --- /dev/null +++ b/deploy/paddleserving/recognition/recognition_web_service.py @@ -0,0 +1,79 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +from paddle_serving_app.reader import Sequential, URL2Image, Resize, CenterCrop, RGB2BGR, Transpose, Div, Normalize, Base64ToImage +try: + from paddle_serving_server_gpu.web_service import WebService, Op +except ImportError: + from paddle_serving_server.web_service import WebService, Op +import logging +import numpy as np +import base64, cv2 +import os +import faiss +import pickle + +class RecogOp(Op): + def init_op(self): + self.seq = Sequential([ + Resize(256), CenterCrop(224), RGB2BGR(), Transpose((2, 0, 1)), + Div(255), Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], + True) + ]) + + #load index; and return top1 + index_dir = "../../recognition_demo_data_v1.1/gallery_product/index" + assert os.path.exists(os.path.join( + index_dir, "vector.index")), "vector.index not found ..." + assert os.path.exists(os.path.join( + index_dir, "id_map.pkl")), "id_map.pkl not found ... " + + self.Searcher = faiss.read_index( + os.path.join(index_dir, "vector.index")) + + with open(os.path.join(index_dir, "id_map.pkl"), "rb") as fd: + self.id_map = pickle.load(fd) + + def preprocess(self, input_dicts, data_id, log_id): + (_, input_dict), = input_dicts.items() + batch_size = len(input_dict.keys()) + imgs = [] + for key in input_dict.keys(): + data = base64.b64decode(input_dict[key].encode('utf8')) + data = np.fromstring(data, np.uint8) + im = cv2.imdecode(data, cv2.IMREAD_COLOR) + img = self.seq(im) + imgs.append(img[np.newaxis, :].copy()) + input_imgs = np.concatenate(imgs, axis=0) + return {"x": input_imgs}, False, None, "" + + def postprocess(self, input_dicts, fetch_dict, log_id): + score_list = fetch_dict["features"] + + return_top_k = 1 + scores, docs = self.Searcher.search(score_list, return_top_k) + + result = {} + result["label"] = self.id_map[docs[0][0]].split()[1] + result["dist"] = str(scores[0][0]) + return result, None, "" + +class ProductRecognitionService(WebService): + def get_pipeline_response(self, read_op): + image_op = RecogOp(name="recog", input_ops=[read_op]) + return image_op + +uci_service = ProductRecognitionService(name="recog_service") +uci_service.prepare_pipeline_config("config.yml") +uci_service.run_service() From ccda54d7cba564d2bc86f3e9e9e22a5ce74274f9 Mon Sep 17 00:00:00 2001 From: Bin Lu Date: Wed, 22 Sep 2021 16:56:08 +0800 Subject: [PATCH 34/81] modify Searcher to searcher --- deploy/paddleserving/recognition/recognition_web_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/paddleserving/recognition/recognition_web_service.py b/deploy/paddleserving/recognition/recognition_web_service.py index f8beddfbc..0e72a913c 100644 --- a/deploy/paddleserving/recognition/recognition_web_service.py +++ b/deploy/paddleserving/recognition/recognition_web_service.py @@ -39,7 +39,7 @@ class RecogOp(Op): assert os.path.exists(os.path.join( index_dir, "id_map.pkl")), "id_map.pkl not found ... " - self.Searcher = faiss.read_index( + self.searcher = faiss.read_index( os.path.join(index_dir, "vector.index")) with open(os.path.join(index_dir, "id_map.pkl"), "rb") as fd: @@ -62,7 +62,7 @@ class RecogOp(Op): score_list = fetch_dict["features"] return_top_k = 1 - scores, docs = self.Searcher.search(score_list, return_top_k) + scores, docs = self.searcher.search(score_list, return_top_k) result = {} result["label"] = self.id_map[docs[0][0]].split()[1] From 45b7cf24eb1d4c2a9959ec9d5b85b9a9796e56b4 Mon Sep 17 00:00:00 2001 From: littletomatodonkey Date: Wed, 22 Sep 2021 19:32:35 +0800 Subject: [PATCH 35/81] Update getting_started_retrieval.md --- docs/zh_CN/tutorials/getting_started_retrieval.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh_CN/tutorials/getting_started_retrieval.md b/docs/zh_CN/tutorials/getting_started_retrieval.md index 06dcc11c7..a0695d88c 100644 --- a/docs/zh_CN/tutorials/getting_started_retrieval.md +++ b/docs/zh_CN/tutorials/getting_started_retrieval.md @@ -117,7 +117,7 @@ python3 tools/train.py \ 其中,`-c`用于指定配置文件的路径,`-o`用于指定需要修改或者添加的参数,其中`-o Arch.Backbone.pretrained=True`表示Backbone部分使用预训练模型,此外,`Arch.Backbone.pretrained`也可以指定具体的模型权重文件的地址,使用时需要换成自己的预训练模型权重文件的路径。`-o Global.device=gpu`表示使用GPU进行训练。如果希望使用CPU进行训练,则需要将`Global.device`设置为`cpu`。 -更详细的训练配置,也可以直接修改模型对应的配置文件。具体配置参数参考[配置文档](config.md)。 +更详细的训练配置,也可以直接修改模型对应的配置文件。具体配置参数参考[配置文档](config_description.md)。 运行上述命令,可以看到输出日志,示例如下: @@ -245,4 +245,4 @@ python3 tools/export_model.py \ - 平均检索精度(mAP) - AP: AP指的是不同召回率上的正确率的平均值 - - mAP: 测试集中所有图片对应的AP的的平均值 \ No newline at end of file + - mAP: 测试集中所有图片对应的AP的的平均值 From e417884d585318a03baddea9f8e50371f2f5438a Mon Sep 17 00:00:00 2001 From: littletomatodonkey Date: Wed, 22 Sep 2021 19:33:06 +0800 Subject: [PATCH 36/81] Update getting_started_retrieval_en.md --- docs/en/tutorials/getting_started_retrieval_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/tutorials/getting_started_retrieval_en.md b/docs/en/tutorials/getting_started_retrieval_en.md index eea6c1667..f548572d9 100644 --- a/docs/en/tutorials/getting_started_retrieval_en.md +++ b/docs/en/tutorials/getting_started_retrieval_en.md @@ -120,7 +120,7 @@ python3 tools/train.py \ `-c` is used to specify the path to the configuration file, and `-o` is used to specify the parameters that need to be modified or added, where `-o Arch.Backbone.pretrained=True` indicates that the Backbone part uses the pre-trained model, in addition, `Arch.Backbone.pretrained` can also specify backbone.`pretrained` can also specify the address of a specific model weight file, which needs to be replaced with the path to your own pre-trained model weight file when using it. `-o Global.device=gpu` indicates that the GPU is used for training. If you want to use a CPU for training, you need to set `Global.device` to `cpu`. -For more detailed training configuration, you can also modify the corresponding configuration file of the model directly. Refer to the [configuration document](config_en.md) for specific configuration parameters. +For more detailed training configuration, you can also modify the corresponding configuration file of the model directly. Refer to the [configuration document](config_description_en.md) for specific configuration parameters. Run the above commands to check the output log, an example is as follows: From d3c9a3d58e470704cc1d59a6533eebd44050a2e2 Mon Sep 17 00:00:00 2001 From: Youqing Xiaozhua <843213558@qq.com> Date: Tue, 14 Sep 2021 16:57:53 +0800 Subject: [PATCH 37/81] Upgrade the English Document --- docs/en/tutorials/getting_started_en.md | 189 ++++++++++-------------- 1 file changed, 79 insertions(+), 110 deletions(-) diff --git a/docs/en/tutorials/getting_started_en.md b/docs/en/tutorials/getting_started_en.md index 1903a04ef..ad0e03eb4 100644 --- a/docs/en/tutorials/getting_started_en.md +++ b/docs/en/tutorials/getting_started_en.md @@ -14,13 +14,13 @@ After preparing the configuration file, The training process can be started in t ``` python tools/train.py \ - -c configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o pretrained_model="" \ - -o use_gpu=False + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Arch.pretrained=False \ + -o Global.device=gpu ``` -Among them, `-c` is used to specify the path of the configuration file, `-o` is used to specify the parameters needed to be modified or added, `-o pretrained_model=""` means to not using pre-trained models. -`-o use_gpu=True` means to use GPU for training. If you want to use the CPU for training, you need to set `use_gpu` to `False`. +Among them, `-c` is used to specify the path of the configuration file, `-o` is used to specify the parameters needed to be modified or added, `-o Arch.pretrained=False` means to not using pre-trained models. +`-o Global.device=gpu` means to use GPU for training. If you want to use the CPU for training, you need to set `Global.device` to `False`. Of course, you can also directly modify the configuration file to update the configuration. For specific configuration parameters, please refer to [Configuration Document](config_description_en.md). @@ -54,12 +54,12 @@ After configuring the configuration file, you can finetune it by loading the pre ``` python tools/train.py \ - -c configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o pretrained_model="./pretrained/MobileNetV3_large_x1_0_pretrained" \ - -o use_gpu=True + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Arch.pretrained=True \ + -o Global.device=gpu ``` -Among them, `-o pretrained_model` is used to set the address to load the pretrained weights. When using it, you need to replace it with your own pretrained weights' path, or you can modify the path directly in the configuration file. +Among them, `-o Arch.pretrained` is used to set the address to load the pretrained weights. When using it, you need to replace it with your own pretrained weights' path, or you can modify the path directly in the configuration file. We also provide a lot of pre-trained models trained on the ImageNet-1k dataset. For the model list and download address, please refer to the [model library overview](../models/models_intro_en.md). @@ -69,28 +69,26 @@ If the training process is terminated for some reasons, you can also load the ch ``` python tools/train.py \ - -c configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o checkpoints="./output/MobileNetV3_large_x1_0/5/ppcls" \ - -o last_epoch=5 \ - -o use_gpu=True + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Global.checkpoints="./output/MobileNetV3_large_x1_0/epoch_5" \ + -o Global.device=gpu ``` -The configuration file does not need to be modified. You only need to add the `checkpoints` parameter during training, which represents the path of the checkpoints. The parameter weights, learning rate, optimizer and other information will be loaded using this parameter. +The configuration file does not need to be modified. You only need to add the `Global.checkpoints` parameter during training, which represents the path of the checkpoints. The parameter weights, learning rate, optimizer and other information will be loaded using this parameter. **Note**: -* The parameter `-o last_epoch=5` means to record the number of the last training epoch as `5`, that is, the number of this training epoch starts from `6`, , and the parameter defaults to `-1`, which means the number of this training epoch starts from `0`. -* The `-o checkpoints` parameter does not need to include the suffix of the checkpoints. The above training command will generate the checkpoints as shown below during the training process. If you want to continue training from the epoch `5`, Just set the `checkpoints` to `./output/MobileNetV3_large_x1_0_gpupaddle/5/ppcls`, PaddleClas will automatically fill in the `pdopt` and `pdparams` suffixes. +* The `-o Global.checkpoints` parameter does not need to include the suffix of the checkpoints. The above training command will generate the checkpoints as shown below during the training process. If you want to continue training from the epoch `5`, Just set the `Global.checkpoints` to `../output/MobileNetV3_large_x1_0/epoch_5`, PaddleClas will automatically fill in the `pdopt` and `pdparams` suffixes. ```shell - output/ - └── MobileNetV3_large_x1_0 - ├── 0 - │ ├── ppcls.pdopt - │ └── ppcls.pdparams - ├── 1 - │ ├── ppcls.pdopt - │ └── ppcls.pdparams + output + ├── MobileNetV3_large_x1_0 + │ ├── best_model.pdopt + │ ├── best_model.pdparams + │ ├── best_model.pdstates + │ ├── epoch_1.pdopt + │ ├── epoch_1.pdparams + │ ├── epoch_1.pdstates . . . @@ -103,18 +101,15 @@ The model evaluation process can be started as follows. ```bash python tools/eval.py \ - -c ./configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o pretrained_model="./output/MobileNetV3_large_x1_0/best_model/ppcls"\ - -o load_static_weights=False + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Global.pretrained_model=./output/MobileNetV3_large_x1_0/best_model ``` -The above command will use `./configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml` as the configuration file to evaluate the model `./output/MobileNetV3_large_x1_0/best_model/ppcls`. You can also set the evaluation by changing the parameters in the configuration file, or you can update the configuration with the `-o` parameter, as shown above. +The above command will use `./configs/quick_start/MobileNetV3_large_x1_0.yaml` as the configuration file to evaluate the model `./output/MobileNetV3_large_x1_0/best_model`. You can also set the evaluation by changing the parameters in the configuration file, or you can update the configuration with the `-o` parameter, as shown above. Some of the configurable evaluation parameters are described as follows: -* `ARCHITECTURE.name`: Model name -* `pretrained_model`: The path of the model file to be evaluated -* `load_static_weights`: Whether the model to be evaluated is a static graph model - +* `Arch.name`: Model name +* `Global.pretrained_model`: The path of the model file to be evaluated **Note:** If the model is a dygraph type, you only need to specify the prefix of the model file when loading the model, instead of specifying the suffix, such as [1.3 Resume Training](#13-resume-training). @@ -125,26 +120,15 @@ If you want to run PaddleClas on Linux with GPU, it is highly recommended to use ### 2.1 Model training -After preparing the configuration file, The training process can be started in the following way. `paddle.distributed.launch` specifies the GPU running card number by setting `selected_gpus`: +After preparing the configuration file, The training process can be started in the following way. `paddle.distributed.launch` specifies the GPU running card number by setting `gpus`: ```bash export CUDA_VISIBLE_DEVICES=0,1,2,3 -python -m paddle.distributed.launch \ - --selected_gpus="0,1,2,3" \ +python3 -m paddle.distributed.launch \ + --gpus="0,1,2,3" \ tools/train.py \ - -c ./configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml -``` - -The configuration can be updated by adding the `-o` parameter. - -```bash -python -m paddle.distributed.launch \ - --selected_gpus="0,1,2,3" \ - tools/train.py \ - -c ./configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o pretrained_model="" \ - -o use_gpu=True + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml ``` The format of output log information is the same as above, see [1.1 Model training](#11-model-training) for details. @@ -156,14 +140,14 @@ After configuring the configuration file, you can finetune it by loading the pre ``` export CUDA_VISIBLE_DEVICES=0,1,2,3 -python -m paddle.distributed.launch \ - --selected_gpus="0,1,2,3" \ +python3 -m paddle.distributed.launch \ + --gpus="0,1,2,3" \ tools/train.py \ - -c ./configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o pretrained_model="./pretrained/MobileNetV3_large_x1_0_pretrained" + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Arch.pretrained=True ``` -Among them, `pretrained_model` is used to set the address to load the pretrained weights. When using it, you need to replace it with your own pretrained weights' path, or you can modify the path directly in the configuration file. +Among them, `Arch.pretrained` is set to `True` or `False`. It also can be used to set the address to load the pretrained weights. When using it, you need to replace it with your own pretrained weights' path, or you can modify the path directly in the configuration file. There contains a lot of examples of model finetuning in [Quick Start](./quick_start_en.md). You can refer to this tutorial to finetune the model on a specific dataset. @@ -175,26 +159,26 @@ If the training process is terminated for some reasons, you can also load the ch ``` export CUDA_VISIBLE_DEVICES=0,1,2,3 -python -m paddle.distributed.launch \ - --selected_gpus="0,1,2,3" \ +python3 -m paddle.distributed.launch \ + --gpus="0,1,2,3" \ tools/train.py \ - -c ./configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o checkpoints="./output/MobileNetV3_large_x1_0/5/ppcls" \ - -o last_epoch=5 \ - -o use_gpu=True + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Global.checkpoints="./output/MobileNetV3_large_x1_0/epoch_5" \ + -o Global.device=gpu ``` -The configuration file does not need to be modified. You only need to add the `checkpoints` parameter during training, which represents the path of the checkpoints. The parameter weights, learning rate, optimizer and other information will be loaded using this parameter. About `last_epoch` parameter, please refer [1.3 Resume training](#13-resume-training) for details. +The configuration file does not need to be modified. You only need to add the `Global.checkpoints` parameter during training, which represents the path of the checkpoints. The parameter weights, learning rate, optimizer and other information will be loaded using this parameter as described in [1.3 Resume training](#13-resume-training). ### 2.4 Model evaluation The model evaluation process can be started as follows. ```bash -python tools/eval.py \ - -c ./configs/quick_start/MobileNetV3_large_x1_0_finetune.yaml \ - -o pretrained_model="./output/MobileNetV3_large_x1_0/best_model/ppcls"\ - -o load_static_weights=False +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python3 -m paddle.distributed.launch \ + tools/eval.py \ + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Global.pretrained_model=./output/MobileNetV3_large_x1_0/best_model ``` About parameter description, see [1.4 Model evaluation](#14-model-evaluation) for details. @@ -204,30 +188,16 @@ About parameter description, see [1.4 Model evaluation](#14-model-evaluation) fo After the training is completed, you can predict by using the pre-trained model obtained by the training, as follows: ```python -python tools/infer/infer.py \ - -i image path \ - --model MobileNetV3_large_x1_0 \ - --pretrained_model "./output/MobileNetV3_large_x1_0/best_model/ppcls" \ - --use_gpu True \ - --load_static_weights False +python3 tools/infer.py \ + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Infer.infer_imgs=dataset/flowers102/jpg/image_00001.jpg \ + -o Global.pretrained_model=./output/MobileNetV3_large_x1_0/best_model ``` Among them: -+ `image_file`(i): The path of the image file to be predicted, such as `./test.jpeg`; -+ `model`: Model name, such as `MobileNetV3_large_x1_0`; -+ `pretrained_model`: Weight file path, such as `./pretrained/MobileNetV3_large_x1_0_pretrained/`; -+ `use_gpu`: Whether to use the GPU, default by `True`; -+ `load_static_weights`: Whether to load the pre-trained model obtained from static image training, default by `False`; -+ `resize_short`: The length of the shortest side of the image that be scaled proportionally, default by `256`; -+ `resize`: The side length of the image that be center cropped from resize_shorted image, default by `224`; -+ `pre_label_image`: Whether to pre-label the image data, default value: `False`; -+ `pre_label_out_idr`: The output path of pre-labeled image data. When `pre_label_image=True`, a lot of subfolders will be generated under the path, each subfolder represent a category, which stores all the images predicted by the model to belong to the category. ++ `Infer.infer_imgs`: The path of the image file or folder to be predicted; ++ `Global.pretrained_model`: Weight file path, such as `./output/MobileNetV3_large_x1_0/best_model`; -**Note**: If you want to use `Transformer series models`, such as `DeiT_***_384`, `ViT_***_384`, etc., please pay attention to the input size of model, and need to set `resize_short=384`, `resize=384`. - -About more detailed infomation, you can refer to [infer.py](../../../tools/infer/infer.py). - - ## 4. Use the inference model to predict PaddlePaddle supports inference using prediction engines, which will be introduced next. @@ -235,41 +205,40 @@ PaddlePaddle supports inference using prediction engines, which will be introduc Firstly, you should export inference model using `tools/export_model.py`. ```bash -python tools/export_model.py \ - --model MobileNetV3_large_x1_0 \ - --pretrained_model ./output/MobileNetV3_large_x1_0/best_model/ppcls \ - --output_path ./inference \ - --class_dim 1000 +python3 tools/export_model.py \ + -c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \ + -o Global.pretrained_model=output/MobileNetV3_large_x1_0/best_model ``` -Among them, the `--model` parameter is used to specify the model name, `--pretrained_model` parameter is used to specify the model file path, the path does not need to include the model file suffix name, and `--output_path` is used to specify the storage path of the converted model, class_dim means number of class for the model, default as 1000. - -**Note**: -1. If `--output_path=./inference`, then three files will be generated in the folder `inference`, they are `inference.pdiparams`, `inference.pdmodel` and `inference.pdiparams.info`. -2. You can specify the `shape` of the model input image by setting the parameter `--img_size`, the default is `224`, which means the shape of input image is `224*224`. If you want to use `Transformer series models`, such as `DeiT_***_384`, `ViT_***_384`, you need to set `--img_size=384`. +Among them, `Global.pretrained_model` parameter is used to specify the model file path. The above command will generate the model structure file (`inference.pdmodel`) and the model weight file (`inference.pdiparams`), and then the inference engine can be used for inference: +Go to the deploy directory: + +``` +cd deploy +``` + +Execute the command to inference. Cause the default value of `class_id_map_file` is the mapping file of the ImageNet dataset, we set it to `None` here. + ```bash -python tools/infer/predict.py \ - --image_file image path \ - --model_file "./inference/inference.pdmodel" \ - --params_file "./inference/inference.pdiparams" \ - --use_gpu=True \ - --use_tensorrt=False +python3 python/predict_cls.py \ + -c configs/inference_cls.yaml \ + -o Global.infer_imgs=../dataset/flowers102/jpg/image_00001.jpg \ + -o Global.inference_model_dir=../inference/ \ + -o PostProcess.Topk.class_id_map_file=None ``` Among them: -+ `image_file`: The path of the image file to be predicted, such as `./test.jpeg`; -+ `model_file`: Model file path, such as `./MobileNetV3_large_x1_0/inference.pdmodel`; -+ `params_file`: Weight file path, such as `./MobileNetV3_large_x1_0/inference.pdiparams`; -+ `use_tensorrt`: Whether to use the TesorRT, default by `True`; -+ `use_gpu`: Whether to use the GPU, default by `True` -+ `enable_mkldnn`: Wheter to use `MKL-DNN`, default by `False`. When both `use_gpu` and `enable_mkldnn` are set to `True`, GPU is used to run and `enable_mkldnn` will be ignored. -+ `resize_short`: The length of the shortest side of the image that be scaled proportionally, default by `256`; -+ `resize`: The side length of the image that be center cropped from resize_shorted image, default by `224`; -+ `enable_calc_topk`: Whether to calculate top-k accuracy of the predction, default by `False`. Top-k accuracy will be printed out when set as `True`. -+ `gt_label_path`: Image name and label file, used when `enable_calc_topk` is `True` to get image list and labels. ++ `Global.infer_imgs`: The path of the image file to be predicted; ++ `Global.inference_model_dir`: Model structure file path, such as `../inference/inference.pdmodel`; ++ `Global.use_tensorrt`: Whether to use the TesorRT, default by `False`; ++ `Global.use_gpu`: Whether to use the GPU, default by `True` ++ `Global.enable_mkldnn`: Wheter to use `MKL-DNN`, default by `False`. When both `Global.use_gpu` and `enable_mkldnn` are set to `True`, GPU is used to run and `enable_mkldnn` will be ignored. ++ `Global.use_fp16`: Whether to enable FP16, default by `False`; **Note**: If you want to use `Transformer series models`, such as `DeiT_***_384`, `ViT_***_384`, etc., please pay attention to the input size of model, and need to set `resize_short=384`, `resize=384`. -If you want to evaluate the speed of the model, it is recommended to use [predict.py](../../../tools/infer/predict.py), and enable TensorRT to accelerate. +If you want to evaluate the speed of the model, it is recommended to enable TensorRT to accelerate for GPU, and MKL-DNN for CPU. + + From 97a36e433788846e930e0fc8dfa121b930308d16 Mon Sep 17 00:00:00 2001 From: Youqing Xiaozhua <843213558@qq.com> Date: Wed, 22 Sep 2021 18:45:26 +0800 Subject: [PATCH 38/81] Polish the English Document --- docs/en/tutorials/getting_started_en.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/en/tutorials/getting_started_en.md b/docs/en/tutorials/getting_started_en.md index ad0e03eb4..fe8dca132 100644 --- a/docs/en/tutorials/getting_started_en.md +++ b/docs/en/tutorials/getting_started_en.md @@ -20,7 +20,7 @@ python tools/train.py \ ``` Among them, `-c` is used to specify the path of the configuration file, `-o` is used to specify the parameters needed to be modified or added, `-o Arch.pretrained=False` means to not using pre-trained models. -`-o Global.device=gpu` means to use GPU for training. If you want to use the CPU for training, you need to set `Global.device` to `False`. +`-o Global.device=gpu` means to use GPU for training. If you want to use the CPU for training, you need to set `Global.device` to `cpu`. Of course, you can also directly modify the configuration file to update the configuration. For specific configuration parameters, please refer to [Configuration Document](config_description_en.md). @@ -59,7 +59,7 @@ python tools/train.py \ -o Global.device=gpu ``` -Among them, `-o Arch.pretrained` is used to set the address to load the pretrained weights. When using it, you need to replace it with your own pretrained weights' path, or you can modify the path directly in the configuration file. +Among them, `-o Arch.pretrained` is used to set the address to load the pretrained weights. When using it, you need to replace it with your own pretrained weights' path, or you can modify the path directly in the configuration file. You can also set it into `True` to use pretrained weights that trained in ImageNet1k. We also provide a lot of pre-trained models trained on the ImageNet-1k dataset. For the model list and download address, please refer to the [model library overview](../models/models_intro_en.md). @@ -210,7 +210,7 @@ python3 tools/export_model.py \ -o Global.pretrained_model=output/MobileNetV3_large_x1_0/best_model ``` -Among them, `Global.pretrained_model` parameter is used to specify the model file path. +Among them, `Global.pretrained_model` parameter is used to specify the model file path that does not need to include the file suffix name. The above command will generate the model structure file (`inference.pdmodel`) and the model weight file (`inference.pdiparams`), and then the inference engine can be used for inference: @@ -220,7 +220,7 @@ Go to the deploy directory: cd deploy ``` -Execute the command to inference. Cause the default value of `class_id_map_file` is the mapping file of the ImageNet dataset, we set it to `None` here. +Using inference engine to inference. Because the mapping file of ImageNet1k dataset is used by default, we should set `PostProcess.Topk.class_id_map_file` into `None`. ```bash python3 python/predict_cls.py \ @@ -234,11 +234,9 @@ Among them: + `Global.inference_model_dir`: Model structure file path, such as `../inference/inference.pdmodel`; + `Global.use_tensorrt`: Whether to use the TesorRT, default by `False`; + `Global.use_gpu`: Whether to use the GPU, default by `True` -+ `Global.enable_mkldnn`: Wheter to use `MKL-DNN`, default by `False`. When both `Global.use_gpu` and `enable_mkldnn` are set to `True`, GPU is used to run and `enable_mkldnn` will be ignored. ++ `Global.enable_mkldnn`: Wheter to use `MKL-DNN`, default by `False`. It is valid when `Global.use_gpu` is `False`. + `Global.use_fp16`: Whether to enable FP16, default by `False`; **Note**: If you want to use `Transformer series models`, such as `DeiT_***_384`, `ViT_***_384`, etc., please pay attention to the input size of model, and need to set `resize_short=384`, `resize=384`. If you want to evaluate the speed of the model, it is recommended to enable TensorRT to accelerate for GPU, and MKL-DNN for CPU. - - From 9a4b05cfc60fd09b7926e145e9933de6fa90233b Mon Sep 17 00:00:00 2001 From: lilithzhou <80816848+lilith-zy@users.noreply.github.com> Date: Wed, 22 Sep 2021 19:43:48 +0800 Subject: [PATCH 39/81] Update wx_group.png (#1253) --- docs/images/wx_group.png | Bin 58963 -> 206022 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/wx_group.png b/docs/images/wx_group.png index 4a410ffc8850a03ffd2e7a62a9a7a41948781b9d..94f87f6f0b6178c5d491cbc0ccd2d0efb6702e61 100644 GIT binary patch literal 206022 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�y}7wn`u4(=kKg{fpd~ zF;IV@w#!RzT1{%%{kWv&+vW|cRFd*8>96dr3Oad91fkD`%kua|MCe1s)O*FI(j+qX z`gV>Q(~?;Yu){ohQ-LXCU2XMQjFM=FJk+(=@P4MFA=##h5_|akcGHSCN?&82DGWnI zJc^?~x+^`(Ejz3w4wOa!qEBmb*(MsFE3v`UgcilOca&KM=EU)pdzHL5eXZ}x3rwnL z?nyn|s(j+&DnH5l&2;98!e;qFv;RfN^x+@no&PChl4^slbj%M6LJJMA^bGnFujTqg{-;opc5hren$)o>vI_@YLTLQ#J+32;lIg=SK}tg z*JP-<96X;uCpY zMI1&PK2`t-i~WD#n#uy!WJqSdvX$WZ-;sg!FkCfuHU$G_maI?D0Revqe@vKMqZkaK z_C}a0$ZoVxVLhQqFZcp9wBaN)zkpKdANtU>mnEwPKF<+T3Z@}nABls=w`5q&dU(Ml z_4s-#@fkoq`h8(ZlsXk83;vM??MTN}VcY$wR+&%-?d+1F)Lc9hzBtIEw8Iql++MC{ zA+ZCnxPSpBBf~-jc*@4xM`h7IMAUue^hCVehz29uu~q&ZjQIhx1nKi&XK?vs5AVJ z%_m2s(yHOT{C~9V!HZa9I~LfU+*nXAd+&#_sGfdHfMo=vj^3-3<6iV;N1l z%w?nEppy)<8X>K`smAnFWU_^Mvgr{f%ih^k9^VGUaPV%%e*q1>-{kgjB=a`|1eONu zt&e>_5he1!XaxKwXHG(m6fvz9nL4upT9Y5AatC#mqLJUR5vOQDa0&khDppb)7ofn_<<6Dr5tz-%Jtr1!u$Fe(yF&5DcE zDo51j)R@Lh%6XD=ahJk9uZyOZf$Ug9rR3VjsMZaT3yFpJ_&=X-#r9lGGhBQgeJC5H zIoX>X!x`sa%S(?y2n?cT3NO&BFm86aC|h~wq%b902a_4+pV4;1-h2x2%toqkCt-N0 z7h8DyiA<6F--ZmLTUyYR7?0K_VFrihj|qDSn_?+apO0FU7^c~;hs#xAuxcMX$GPDtUu83uyrt3KY1Pj6o(i*d6krt&{15-+4^8hH<` zrI(+STTW|s;jNB06ZIsAcQZ`JKgxUAde%vQ!&v8*AB!2um1D6_>YNWjwyQwhj*4(& zI0G~jgy-;}MzNFA4TB$BF9rCKds~|leT>rNUP$1&lKIAo$z25tA_jFLf0N5~^S;X2 zpA~D;PmEKn4E@&lQZBInKiRWELicIOUd&$_W^3}5Lqt3&8tEbU+^;9~vUx2w-l?-+ zs(oe7JU8EesdWT%RY%2GpJ#o~UVU3mOr!qSnO1=K_ak&DR+0E4MVp{Ly7x5s38^Ps zQcf)#@MUiJu9P*0*<(Y7#u^3k_RfrY$EeM@ho)Lg-{yFcsC}R+qRg z>MR(8`tc-`N$8GsOR~OHtj#d??Ri^$HFodC0*}ufIlugbvM~dgfl7{W7qO)TAQg!h zs?4(hF{Mq`>fBTxp7Bk-@2d5_3!Bl(l<0Wn3Bl-d;ruSI^OqmqLt~K@{$!v?KyJB-sVj zN>2#|+eFbp?Ta}|dqw6^$!S-7_xTP6eE6Jgp|_uG;xSb@>sCOA?BWL5LIjSPGcXBk zmmp0m(Y-+6bCg}MbD*~e_)~vT?b}C*X`A-}n<_LA>^PrKJ|cEHRZLZa?99#8dCkP` z=7JEe)O~CHVe=V^eKe!JvEEC4pDl&-dLJ_%7K+8@rq{Z*u4ZK|S~2^1yctm#{qfg_ zhL2)iZBzjNfZLId8n%BDI#)?nuCDo1rJJ7u?n6&_)r@!gN6-{C5JwF!=AagaxY90B zIxnsy{XA>3?byl`dOyGMG-IyFq`dh-WA7bpRwN@J!5d#7(o2}Ve$LANogZk%*4a8+ z-Zn3mOHl2_X9dCwl9Af{Du5B^B`zoCB4e0Jn62~{b243lODj&D(@9mur*}@KgT%nH z>)%yb_goxJYg(?~QjGPfUht{JAYd7k3Br4=f!7+fjix+)9bAF~$=J1k9woTVzrY=~ zUL8)$mVewL)l^JZeT36|7wI(;fz@I>JrFrgU+r2pk=IL8z!YBA+1gSuU&& zr)@9Sk@vjkh92ePd`@L}ILS}DKoC-H^HE_Kq^5Z4zY}J*Nz}k$TLIo=A~;R-t@48% z-FqcR@LJ*%gEB|fd3GixvayJ1^P+~7Z({qX;Rtnnotau>3bK%P`NTEiV$gKzV*d|b z(t3zrjm?0oecVR0QP^0{Rw|gu@Q>Lp+)+v(aOj?;&0aN!?pWllNhDtJr+rD=WnC&b zdOo@-fl%Bf_r7KjCR6pBGx@CPb4Ct*I6SLxarh~B6Q?yoq#IUuqYx&d&WOBCJBQ()@^4uo{ z`D$}!uNaHWp+}odqf$=F2|)_LT?#FlvRT57#E&Mti3}A`t6!8HVykO#){LfZ9Nlb} z`c=CicVX3o-6!g>j&~O|di0fRJc4YW{QL72|JxN%9E1ZfJ{Jt@PC${V=zMlLyE(j= znoTafCu)cge&Yq4!R&ZG+v<(FZA%_?xf$YBFK%U7zy9LP?Dd|eOHUy5Xa?DCkY=#= zv6{gBA3#@f6cBHcQto+N7uJELZXTT*yGuQ#^HNrGV8v}hwdA^PblZx~z^w{>et)tP zdS7MY0b>B_r)C6{$BbnJqJ~0estUsqJ@w%Bj^g2ZpSawW<)XXv_3^8pIgkVNClPn| z*9DQi{zy>GSNQFpaypO-BBM*IWl1$}Fu|}noQ?yhKm7O1XcSer-yXXe-UF|HAQJ+e zU_itL4}NB6fux~oI^wxEQ^k}7NXVt$hQiai|KeWD1M8Fs^H&2#o_v5L8RrAJW&cY- zGuB&y1{P~cO4YtStzm@6*IZC!C<%dP}X|i|?J`L$ohdSDfpJAiokJGTWUs!z? z*`X})iereB1k4=Ok!MTh!_bV#pE0%q#>Yq z>_r~-h5m)o2M-jA$OqGZm;qixma!Upr90YYL1kq$G=q@c1=Kk5u;n^A)7~EwV52p* z=i<@PkhZQ6Zv%5nmEM97WTY8$AT(nh`rUv0|Eb~sBUL?;43%3)mSx1(w}^osn$gdr zNmqe1xMbU3+tL4;a{l-K`s;-4zs17;QWuUN1R67HP7u$NJ|lZmBS6gyJoRcF#-*Cjl6p{Eu%FW-3C>7v5)27qmoy^$hIcIREp7rdZzr$JmkX$d`zaBjfOxBxJvfy+_gVXwBRow@!jpku1SQc%na6|g=e6|iEyc;9SW+^TcK!JWDuGF3Y!X}nIpkNW@q@){9ZlAo-C0?88M7G zLlpho_x`unDrdB#Q++#j+k306YA;S5(y}Hv%MFL%GwWb={gGNb6kM!O4Fs~nWIZ-$ zr5f4y=))G6fxhOm(RqaKY+kwoJ((NV{moQ6;EWmjtfk+mxf!Cod zLGstRj0kQ0*p9`t+Zlc!kNfP{2zC$$ZsArcI+t3=Mi*9>P{LWtxRp@C zuo&_g+ST&!zV;jI-RtK?cdb!8HfBP|!qY=P;F?E6M>`e77(}?c8a!jMEzE^4vk*6s zCKsrnC47%efv*nVpaf2e%Pu-=r(@NE$JSd;1=0fNTo^n3hH1_!bwtTv+G}XVvKqKX z2&Mxd%z`LsFuObFD|`6hhxrQ98(s=QXsWP(Eec&1Dmco6Khk^$C}(Ze8Zq4ziU;oc z$vNQcv6iSM-yJ=1rAx|jUM-nH&?~DCHs+sCu-36&)FcE~;@m%|sePHOW@~mmOVZ-f zxeAS#c-_|^nqER6T``(A7R1t|2T)S6*mi;tu6eEd))`+5CJP%vd4aYLEywzHc51t( zUGeI-&`F{jsogtt^s@{|HX1<|g)CT{D{z5aMGp0%2E9G~(3x=$O5s+AHs=O2)DG}q zw35=3CankS3R3qEnM8D`DvaAR=18I7h8zZlXVkSXCW34yrcoiV$EFem+fR!s1Ywap zUqUIaCydGsNj1th*nj9TaYN3{cMfyTW*)xwr2VDMk-i}2ufSc>f}6NY1}u1zP(#hz z5<6GygoqezI^9;i4fWux(3|{zd*SYRPZh8KN?W4yb|J?vS}|=y4dH9as2ki7!li47 za0OrrxDr)FD<@C09Zyf@YrfzUwdr=&x({8@+-)x3=4XA*_bHmA_9(3hYnb`8y0To> z3#kK`0RN--?=y2I-D(3G!6oZ3$R9q(>l#$pP7x792A8GY^nL4$L}Z<+(iA%$7aV%+ zdrE&broUb;ze?EFr_P~Z};F-l}^RW)kh zd-w7QnLg0_)H5~u}5z+Ix z_(VWwR(Eh?aP_(o_6~mu2OLbPIxCorQXGEvUmMMOclh8F6(vV83Y@ib%({Yk(qx?U z_2l>%0RmkOG$RAuc`5GR_mXl>(~Mpw+R3-?KXyI8LCvh!d39D>_7|R76!jqe3CMfYF*<3 zX9ITQT|c2c1b1I$xz<2si7|Y3FX9C%E?NSEa5DEAIXfmie=3h0Ox4*}@SfqAiY=>e zStoMMYSTD#P*d6c%a_l*B0AOvNxrurzI;(_&^jMDEmwS3b&Eg%0HufzB6=|9~#K1f#F{O8qC zxFLdqBxQIRB@sIWcmq%X(p%(}#nD22T!jU9=4dNC7Jd9S?AmbUxG<+bXKV2HHBnDX zf=c&}@A!o-y;(Jf{H>n#Bz`=;GWJtm`E#D!jD5hM z9qr%}&=5yl=>w3*wFER=16&jtV~MXqLJf9EHK92aP-=I76Luh|6U}Zv7hF>+X5oV2M{1r#vC8N1-KW@U(GT`9{ zkbTu3EyUx|Ccw9hMCoNVTgo}AkFzSH^eRfV>s-HXv1#veS!=v!Vp8FnxhJ^8$Ye#K z?{XpqFGI%XFO#go8;EkYifG=K;p!5i26GGX8do>j{e~&g=Zz-=z%c z%+Lcgd|>zApZfm?tNC|-QUOhYBPD`UYt4kyU9 zn@x>BIh3eoZN_t&NSbiaXD`hr>?x1kb1H%~HRMFBQDO7>U(N{Pv*e{SM)e%K(y=L( z5R6G+Ttx=?z9Pp8syyZ-Xg#$BP3QR<6rb#NBfW3HWv=sfyfODu)mBoR$!}qK{VJma=g`4-*|-Tv!iN7tDFF(u1aYl_Kr#o~ z?8r1PlA)LEZ*oVS<#@OO@)}gFA+1A>BoCAz5bjPFrQ?pIGPYzv0XX{aQy{Wh0zwyH zB!aggZe7DHHyP(0*T&bmatLE!#8(A;6w~#VL6kBP%md22 zaQrsufdY21^Djef8MN?syBz-!Vg3iGlvr4S>A@zSNr4Wg#jL|~%mX<9 z*=4peGN@YTrOC0#-1u&?EinvlL3{M` zk-sm+U+U2f9E6=SoBa6`*_SObidTTmrUB|Ex<8TRKhl!$hy1^^-G(0}wCmu?aMvr& zgMeA+NNnz+HJz&z`_(ZGO0+-^M?gUxXbcA=>8(p?L&k11R#WOH`^s2i)^X5(K55oY``(ZTm3pf7t#Y_On?Hd=Kx#YbApnKGgb9a0 zwF}ymYT+swbqMz=Ir}m>tB2L2mBOIhAdR7?6d}SWH~P^E$1~{>zV5jeZ12jpmv)*-oSPmb95VY438LFlb(r&VQ*% zTeA1=uD34A58rg?PlNml8!0{PHXHDBV_IpnJ5PQDx`3b)08RpM=gY!#3ucfq8U^r zOz|##fmu^N^sOC-GspPSSj-`^El`6mzn13UXEA6&&XuDo8%Xrw7I@s&NM zktnWz2I~~U{D@3N4pyd*Z$ZZ+5dp_7NIV~xCjSYhoTtu_dmj3m?~kSH6u4JFvAh0X zYm)qU55FZqiu|)ls6Nyy!=-$%gd28KEcu6k-;rWa-g(dSXNNPxJ9Q_p3L%X{CbbJ4O$C{G4sYIcl z`BQKxY^5>&aCA5Hix1b?PA^JD8;c46_skR-;mOuUJgbzHD_NSkliUph#Frj-TyDB} z>p~&%jgwqpxI~`$ArmhFrhrx<(wxy!T!S_s!3AbHA_i0QdETWrDhE<~4yT6Ri`&HP z8%N9f-gaI8u+6$4WIt7;6L*%~S($GG_(WRj&(PxECtUGQ?^jz3QJ$DQx$8zj+0^gkN5AxuViT1kI+ zvwu3DaH~LO;}jmVs+5S@ft&z9Gzh|tU<+4E$v6OG?xKu$6$0}+A{wqKZ3ywx=XmAh z*^4RSMYbDv;%~Ly_2=CdwD*AQy7Vg$cZjGFf=xXs!IkC;)p?xqNMh(!>ePFp>X;;< zi&tZF>bqFoOBKmUy)BwPhpf*#c7H6LG^rmr-*3Uw{kKhoeSsH;j_}o%5Syd}88W6j zIY46C3Avh?BI;RdlQfHSzSfin2e&x0R9h)WXS(caN<4C;!*|a4w59qGwyp0}=t4e5 ziZ9<3TT34^$1>}4@EPG{^$bl>drXwO@cqz|T_umRuVlAA%e?eu_MA{^k5%NIS1r+g z%ZW`xe;Uu<+tdU!DzVgp(@7PU>k(I+z$5eNjoati)=E+fwPs%7+*14TCCKstY@g~t zWpQjYxp^VAnX_B?nXOmLG4=Ih`8RCdQfWM>~uTyu_B^Ro1$yHD6_10;T1(AtlS z?BvM!%qZcIR4@E2Sr}P20w_1HahA14SSIIE|wr}*rrUc+BVX{4)ggj-1kPlX}n|JI$kC4w{ zcKj%%d?Rn(O_g;zn|LkHjLv-Xx6Fz@JhtUrY@a}ZTr5BAG|C=I0-BN*HYKbQS27S9 z@CikyNauF2rzVQ3qxqslAB((g1+jBu?e~bO?mK;-WGo=CPsttq1z?17D7^qm3wWrk zWN=XTEMh7(aTp%&ZKoi`33-ia?x%D$8+YIhcyst0Lxbj3Cu}L3o-kVbv6;jms%R`N zoZUHKy#hElZgD@OHGKvgZ~HrZ0ycYq{X0_q?i#g^A+zj$G3YR{H+0B00^4EFtRjF0^1 zn{||vyKEPk`(4*hYEk9uBG2cG!Jg~L(h@JLDzF@!-1@G6ys(m zyJJPig!X-F>Mpx!1_5~bCzm3Nh8m>zxR85FB-61m(iCHckz#N%`hgos(|XPG@p(>5 zsJ+4e-u1EB-ru5k-ZqJOPtWUCfK)wxH(@t8Bji7WE0TGTj}qDA_sz59v2EZYfvq3E z3K(nKrI0~_dO>yIkl8_uXPZEH1-d_Q;`#l!l9R;fD=W)2uEd3WZgZEOZ4IW$usQYc zllP`IdC&KXi;f&x9eTHXrKe&6h()=Mc2`b7iq;|AuTnsUR3v(gZPLr%*&Zr7g^L-E z*-vs1jb=1?(|dz@X4$b^t4j?5zr4~e7s5y@g@IHuar@&i{g-z#2nilE8rB8 z&yckb)VbH(Ls?v+D!q2V>%Py~friFoD9z~xIK$Q{gqu&Sj(!A2hwli95Qn{xP8IrI z1!l0tOdYCTTmb0InS<3OS1s?&Vwm~NefCnazN_x=bI$V%w~QCd zMBkuM`JWDx!teq(CiCD}>OLlOA?#6QnxW$>_&rdro{h2vuF7S7RF1c1^D1wnyPe3# z;<}|h!4X&YO8DJNl26M{3e8JF_ySjsf-#+A!*7wHB8VvXMmV#bsM*2?hj9=*M~A(E zk6pc$@1;d1K!3T&XP>iYzO5&Iy6hLUYKZ(7j?v>rTgLI&Wzn10yO=+z zsER)V7eeUI3@-Bb%60!+>fT=~=Z!=i6aNu|C`cXHGR~!4Zv-3K#kTyYSWavJ94wwN z5`xWj5zm2e=#+phbpST*8Rvg|@%LW^UoRhBQ5^> zACNKWZGkAzK%6KWmI(Z#Eh^iCY4pcg=I5Z`*ZWn!9Hr-A|%NCym24RGG%I zezDHX^>hQlh`C@$>yW*)VGAItqNXz09a&dBeFteMyR#KvhH&dkSRqBW353q$L1b$-_Yo_@{ybo3F(0i2L*2PTiEC}X8Nc^j+q9U~ zwS%cltB91MMuhQ|)s5Vf}<20X?sIQf24HG{lR1y04}4 z#)hPnPj;HO`LpI+@XqmbbDXjNbx`k&Ne|;K)Zz!^u~0OnWXM)sWFKK|cw&)XE9ygS zuqOLk&phk#ycUXgEKlrHxvKWsDARgx^vWmioaFY5Y)XLB{sL#hcSFDW7<9H`1qU?@ zh$4Hk)d1^`;gZHQV{?V<2va8y(sbvpfGM0$KH5*&!Hy<--aNWke*D=5&w%xQkmLpn zd)5eMkUhK<3Yk$O5kS>5WKeak6zI{Nyn^uvH@Ecn9?ZcUF}!tR7z%DcFh>pIg*S4j z0Y1?I?&%i%JtA}>G==YyKD1p3U0JCD!i3HOLm~T2axKH?z~faZ4k!32kvC5{2rh}< zj+y4%biVN6zG=ROa2<6l39^lc7MTawTFhp4)=*Jz6Rq%MLoAP@#W*0^tv97+F!lJd z_PS$|%E(iZq4C33`!cg;`F>Q>G29;wxk_1n?r*LeQ8bR-(PhADzCmZ)4or5qq@OMAs;{; zlK3_pgiKl0r=K_oeJZh`=W?m#y0lXE1Z?jE)WZ2g)@}P0F;47)`ZH@>B2D8pukS%j zeW&Ptnjr{E&;u_>?H*pt88{3p#TI(=sz)S`*ThPck=hs7QFxQ&@R4qoB5g`HtnHd! zQ6;QUJYc;74WRaUlb_^Db=VaoOq&k&dPENS&%a2_WD8+o(M27Fu(kY?%TBRF$OLG& zx*BynlGx&7C@@U5{LC@WB;J(Il#H3VY1&d`@r?Lu351W$au_T~FaT#oiwb0^DS*$Q zxh+hRErH%x?55agtuC5Jn@;W0TO2%mO6TTu(^ro}^Og5i&Sbsa+;k|7vtE5U@l^#j z+QA!j0pYFwe3%vA0anYxv?C;Y@mK6jq@2*a1NxrX627%fMJ>1z_H>^cwo!Z2DnPZ+ zJU}z2m$q+6b?>gGw2coh_!h@MhqXExWJL^*S1CZF1i#5CFl_3LXJ)<64-gPk=oXY0 zd6Rro)AK2*yS#GZwB#rFjWubzcPZ2=Kil{DydZM=QZl!)y5&B7?{&z`oxbC7ZrN5{ zi_#OyvlZoVVV`^l>s^om1vOC=cRoG#GFBXlYS%Vpv|6U!!$LeC^_UXBDCv8m-&fzz`7q{ z7U2rgtGEHCize}VwdgKodZdP4Gxj8oSBA32?19l+@%|otD)wgPaz5{iz@4>8n{Y3X>8P1 zH02k$sEHR!jtkxoY$UwbmU%jax3H5JT3{OeJTf*Vmy3imEg5b?&vO`!2a|%9w-n{% zd7kmi$bCPn;Cc8vmk75XdirD1$I3s87sxGE#Q>k1z z)?@#^pao-smT}P0OuclotU^ywj`@x(v?GTW$S!dgECAgl(&0lQ z-A8<18g1rl!u33ZcvNW%xSVsxDCZ5$@1z_Y?l^lm{G3s?im{Vo<>^KHvU0r+?23D9 zvUuhdNdnPeR4+hr;569FelT2xnBf zz`d-PuTakRLQvBXah3EIu83_yzw(i2&iHH^fng|QrV5PQo$5`cfo94ry0eLvKBv~) zpe*pbNMHG5_n=~u9Aa?nci$Dc8_=B$Ykq=R#u>x*-~pglC$l~x;%7on=$pP6TLcaR z(aW+r45r4#E|6wlYxZO2&FxQ58_&)8rOn~cLj{L|*?!Z?3E6Uips`%7g=tjds!OET zU{bFUv(5ltye#GnOWoR={~({p<4o6eFOJKzE4X@y;c$3JGlxgocOoC%HsNxI_fmcE z^G}R&11f$eU{er$Vy}>jPi*DV&f!+5370e-Ro{R*@?R2*PgdogPdg^L8TqZ%b1nWc z_|&}m+Npb^39YZ@jLRJ;OdvD>mUx`ac@ zCVTtuKFn0oBTN0z&onhd`jN|7VU3iHN~8Nl=t3p~2Tril}`cRaNzmFc`X!5M{Ng%UL-c z(olvXwC=qOtzt*QE}8e;ZjWiHCQ!D%JbIiOP(T#>Jc= z4?nTCkwwnQ$Sd0lJimuMu>P3bL2@6V0J_{-v83vdojpVA*)k$CWsiW$)JvJlRH>;v zH|ytn{>Rc2c`OJvz!!F3G%b`WcFT&9&IFO(aX&-Ak*QRWz9;Wx|1hC8(ePKKVvclG z7M#PopnTzitB!{#wH$9_i(ULlQZw&k@+j|j%KBwQCR=(vx5pc%u%nnW9Nr4`E-C+< zLFhm~;wiumg$~C}K>21svYAS11V)7#PvKE_{m zg-F3CK~M!WfE-ztoK66!VGEQC`H!#x`!d1)gdlI`8UwDufD5Sp3SekgeC7VzXt?1Y z#nG6x6s#DR*Ne{E(GASM3RY%CIVZwxX)SXew)B0YgJ{F``wa4XGant7hEM|~+JjXm zW&{|vze+xNx4(S6DwiM+$1;`7jYbA`;1DwK(ms%XSO$@Ud(oxo;xAGbnVSH8v>jt& zkW9*1Gh_;5SuD>!h_NUyI`=Lm!42y3I=3$=dQg1m@Z1{|<7Lyv#vPw=RIcK!JZdsB z{lf~9F)(q!xA1}X;-2Rir?|aMTZ#*aq7Bc5jmzuv9r+LZf0LWyLO*KgYPj`1IitHh z_r@2ct6%J2{LnU=xQ*#v|3ywphGA|YLlduowM-otW-Va;us=*SGKNtd4sc*`(dzU+MW745|x*aDebRkPwwk2_Mh3m zoY?kz-M6A>72iIzfPh(vlFLXst&{^if*cyKMMOFKy~nrt`xnmII-~e5v6s=t6w2&X zIvNqX9OLDugG0Z;01k4!?w|&lU zC2kSEiyX_Il)ZG{&Fzhklj{aL?_EznS2iOqZe!xB6%!us3<2&lsJr%}<_h@4CXqoI zc*YStYABgtod1h}Gx?jFsjwZgmQw~Qy}utLYIN~NpfwWMg$R(E(LiK(@r;AoqkQMP+yIQ<)3)G))v# z_@<~gGT%(_jT5P7AX=}fO$<(Ftng~Ky|<-1Q>W$BwT){(THS5A@_hqyBcW3VpPdC> z)-pgJs4|2i?8R09hq{1sW=KKxSyC`}GC0B~n=+y?{eR)Zmx#c#m5lTzz-3*~C0Y29Y*A}tDzgsKcsp3(?cUbIbo1Yh3s@31>Qf8C-a`459 z!+SlREh<{qV=9kTBBcvh!ky{EbI9N`3OgZ%7_yw0$Pun(qZSl=;wi~|5aU7iLxqQq zz@UUOrV}(R3N#rTGDM`vK_d&kChHN$*?Z$hYSEmWX2$j-ul;9UqE}o^yBBinJfo2K z)ol3;q?9-Vv@haL=evs3%i`3XQTHM}-4ECnzVc1F_LozE`l~Z;ZhSv4taX?33H_Yl zHEXY2elEX4)?oM)d$ie9-cA+zQ!uq}8}qZCp#7@P?{7a zmRn*OOxf`Afx@tku;Of1HLw>;+7{K$z2vJ?rmYvtH=6D3zPQr(dg@1)uI*D^tIa8! zb2axDt8$0=r4J3p){JkL`ybv*iY3%s1k<31_rY@gidhL8%Sg>JrOh2-*0cB=HF9W{ z&r9W!M!nh3ZtieAkmq$EC-;VC7xN^XLJ_p{E6}4S?|v4hZ~ep@8UY`_m~wE7 z-NXy^4!XDRy}LL@IU{terzsTtHy}d)a7?XP$8jeMas;Knidob^_ZtD#&uWOa$8%5sA5o+z1+-IX4JrjW9c3^9wiD=R_^a-7Z@BbnU!7)_0#*EVJ&B z!!LS)6q{HAOZE`2U<;iL5xGzJhj?QVG4w8a#XM0lRA;&HW#ieZ_cxqR#07I*D1@fq z`ERbazR0CsR_OQq3$rBifIo9kqwsD32ghkiqk!KXBHkn7L|Quue3kj2rG0x%rK|8j zZ+NPEEjO$0-gV>e#vb+;mKH`HjQ!XjCWq^IDSV|h>5CoFYRO^75n)BQNT_UIZ`K=8 zU>t@AYB_xv+?nMvJ+XeTdh2w5rMp*`o?BD+U_rm$5~z4f!Dm69E4U1)orQv%7jEwe z)>0d+rA9ThM3hb;hQX2?JI2OW0K)nI;-iE?vh*sZ!Y71LGXn}i^>|TkSYs(Z0o}x< zDAR+R+x(vgb9UBX%aXqipIQ8A;iKvq0UOsnT=*z_(=*jIHG~sDG`tF4@M$0#vUAfx z2_D|S$=OwoxpV{wMh+svL*U3##M9wD58+i%l0_(CHz!u;EdaC^J0+s76j8#BvWOGh z=+Vkivnxt+wF67*cf0H^ezG=q$->9$pC6c;0c8!ydX{KvGNx9I!v+I<*Q^;lBoM6a z4Cq#ZwL2n>M7T+L6){<)CQ3PTo@m1zVdMJxY}!=&baZoXNrcaVN3MaU$?)$la&;5E{zx_wAn$4QE zKI=MP@9TY$=r?O6D$zWJp#eU4Er7SmIM6%=sYM$fVjeFH5*Iz@WGhUl1RpBiHzp7`s2kQ{}0*AjxQ>N&|9o!l_!Je+mkn1a;`s>Ydu59@}{MBu`_-*;N6Kz^?ixOUk z=v1wN5X|Q12ABM~keG3sFGk)>znnn&3gnx(8}pQtu#Pcco;MW67i%HqqDeNJSH zPK5o|GTx1LaJyvk?NwhJ5BPT~4IC4U$6TwFsL9s6G9jcdI1pkw&8q|A0&hSxvW%z3qhF#B;c=t=`|?N`4~D{G8Me&zqg;| z{yias^BjOQO&oq0*dieO0=fu>kS;=rL|mK9)R7Ajm~j_mzE3ES`JSE3e2Mc7lMh%6 z<@4c5(%uAgP`=Acpa(1U2CDxiDTI{o&-jXeuN3g1krcQPEb#-VF7s@J4dr{V%4~uH zLuOF)l?aDh)1v7>+Br~yyb~gXSKRuC_VjNz`oW5rIS$$9W(Q#kkHHzFX&xaF_tJm= zrg3}trV?h4OdRCK#d3g#7Sgx9&IM2b($Q7}DC$A@Lf08ofgU8ce3eO?3TGwpH$$)R z?Nn@K94_NV%+TZVi5W3`JDNSVmahhXUf}dr5C(9=WzH*Wb+0;xsqe{BI{ola=R3FJ zphWFOtGg@Lig^bByAMLl^8h<~82V-M!p>)arWRo@oa>R&gMVbB??Wa#HS*xom#4!c z2ZMtVOT$dGJ1dseV&c3KS@TIYL$~ew(@RpYywaD)y&2+D-iJ_@7tK4 zS$(ag-~IlUOIN+8CWPJBwsrry@W zFYQ;65a`Cr@nKxAIcoopFb7V=6Er{ z>f+&;K<$;&UJRI0KsbIPEQCL^qZx`G5<_GB6==y0;Fk5^1qk9y)NxJh#C}eEXKI*F z=dfJgnZ{dNloB&j``)|f?y#QUJ-DPOYN?F;^lx&is+d1g2b-q{I-4pogDF#r=lec@ zzt$+Yn}}iF9bwwvBjm9{r<+N2r;Jw{ShVvd4q7U$*=?!%ONnyaym@#M*G74B~WYXh^6ZaVv)n>G|Z9Lh-qc`Mwhe1ix(X}Hz(39!`Zk! z?ZFYcLPN&#<(`obP2Im9a}C#MxF-2n(HVrzZnqq=vAsH z>Z%pOkSU($F|xNJ87nM1XAQ8_bW67dAw8a-qbBCK%)F@iSvzn5)~R^?*Hv9akO}Ia zLIGI)(0=|rcS=q+_~{p{c{IhiN{6^WS5B#KWu@|xMwchKl4p2y>$jC$(c}Ewj?s4- zqWpExEpQ~rR^VZz1c^{XD{2GN7cc{KKxYykBQ?y9DAQ8G4{^tW=yPx_PGwq`&1Dt+ z4+q{GEoFNuEoSU$(zB1UT=eAWm3FPF`p5B|T&+ZpzvEVC9ugrlg1Va;4PZ`=ybu zR)h^&j4b!An)f72!)NY^SFe(!Wc-mvAhd=F|r$auFk780lh+axN0Tn*Ms z@)irO8QG@zs`Fw6$@KX+h3owMu6|z1VY*hsu@zdGqcPV_n`+f|*NgjaDK)OhDhwre zlE!NY3bgRWV!*cd3CZ9Ct67jykbBAqsZE7>AVx3UnOWx7t@!9!_ja|GRY@vKbc3o?Y?10bhJGT@L_k;z1+^RU=-lAqZOHPE0BS0sr2}p~2 z&N}e&aZF*+Cp}Sts~^~9BXyX*l9QX}nc?eIUSJ!Zcl!u$t8@L%x*Sd4uMfR45@M{@ zdwVTESzfX0`tcKj+F8Q-u_hT?0nF#?h#4EX^~8*5zAJ8zSpre%UM{PQF^}fJY^14o z4oRBlFf5yNU+E5+s6BrYGknJ_C?#Zr*qvW*znl+GQU6nIl_+6QAVo*QSWj!pt(X^C z*$QP5(qJXSTvFW&-m)&zc6!c#lp%ieL6us8=V0W<<^2nb!k4PZGS!xLLEaw%*uU*qB&IE9dDA@Lt{0TciE1!Z6j5@krfm3f`|QFw zDvLSc%qfojEynDP*Bsl;=0)AHa5+trIHvsZE9sjdNU*IIrqc^uBoAS%+nEO(J5V`H z{k?fZ?lmB!E(mTCM)VKBExe8q#LCJm1Eyem%%7!5S01>^_L|{P<#T_&`W9xQ6`w7a zWm>%P{wjAzy6GU$?m?9K6A_~4qa~7PM>OzuW%?tWU75}1oi&w6vubBe8N6DP+1#Qy zVlnp&GsU5`&(Qd0{i9?Bh3LhjZz6Bbw_Bw1N;DMIwtsR5(qu~TH=~?e$m_?qi3E*O zVn}nTKmDs{5+xR?8ULR2HytWj$uGz<(yHRXk;zYyas^_>UC1#M1rTfjbhQiiP=68a z_zk!5eKDBVHE`cnESYZdIP8)cO62|^K?Y+By}>=gZ3V zwCM+oKopP&>BB&{0#{HsMRH*wA7f%cvBKC7(#_HV96D3!q5=($@IFLEYw5d4*&-MV z43(%bQ$oF{{6*&?h=lC>@ZJ`a^in=kgI7Q#f)MvfM9|~`iT-g`qNG!>Oasy%lWe$C+ygD?Lr3*V3oInpXb#r0K6(Nq9Qxw`>3OuSk_FAqDq& zy^0QmV%zROlhg^OlNZ7n!)z1-8M$IbQY zGHN9M%{VHDDM!?^vf={tK#XVlR(EBVdP(|5Y>s=`FuDS*YFDf~?J+&%lC<+4=l(Ne zyGJ}1=32?*N}UzkCIL~`tZ4pX^nEoA$`56X4ydH;trhwIKb<@Wv1`Of##7ozg%~9} zqGWp7tqmb_4`A_Zi75Mn`y#89Rz807^=rv?+dWycNotTRhUDc3d~KI}V4s|y`|Q%X zt7qA#qZB)C&ap@h*|^wwt*Yy((~Ir*%(hriD+WrxfXT7d8n>t!0`+wrsG>|zO(~>W ziG0ikqLVwcv$yRrMhPNH*TnM5Ze2d~xQVu|1Ci;Uuo3N}h)|4V|q%uOm zW45nZ=etao4lNn0lutK^>U-wNzOh|aOh3&~%93u!@zpNfXOhT$X@s&mZ!^Tyv6wPA zCZq($*P*)DVMdS*u;-iyomCa&kMFwJ!R204Y3lSNcxNP4U z!X153n3Ef|Vf?+T>++%t;d?}}kR^XR=F%%c6!S|@F<>az zH5~K@X{FF?{pC+ZN74w%v)qA>h)OSDkQ%znDPvPAn88$86II_T_IzG6+hz);P^qL4 zVr1WGz17-gOQf!VZ2Eq9SkHJXw=3M-(PN#v>%mP6kAq=1L*6M^#3M_=&f z*BhW_^p`#pVX~2ry}Tz^D@>GPZJM*XE}p2${8DEz%&DsAPrs7hs-hPr6CH=rb6{Dq5iWzJ@yYnq_k32cw1g4A3k?Mkcz!)WQ?@*&CA=YNwj|SXhic0%bonqxRjX8 zG(;AsTr(G=V84d1=YeCO--RyAg0=+l6w+`JtZD^VnsJXz@cL@+|Uh+r$(2f?WlCK#Y5-wlOL%B#e%_a#m zgK;@(Xycd@cgX|SWOx0Hm2ooVE479{=jC30$yz26W2GW~93}|heA!RYgP9X+y)G~@ zZ=3?f-CLx_+8?ANHO+D56UB@qU*U9s@uPuTIj`;e&Y47WWlNhTVsHB_p%r?pTj#kO z);rVYKiQ~U=;D9;jKAM^(piNeQ4jC)9OW+0m|ta`oZ4xNuxJktPcLOFu^=!&YLmC=TneX>m6It@wt@1lz6u0@J$fV*M9?mc)rUM)AFR3CAM0aC zzng|jVQy~c_xsI^Z93y~hLEZ21ecX}bN3#%OSXr5LN?l&?|8TxQp~j9s{7R*gw%aN zt0FWD5WaluTRE)bDwYH=L0<3@phF0#o9|QFK8-8&ET!7p<8Hr)x0lN8H1{U`Ox>BU zvo<~T3cWV(AW&mpfB?ml4}$wXTUcT*+uubdc5agxgFc3Mm~ZfUK@8$*)8bJIt+Lfc(Yred`c@3QE^I76lyQWOX@rny}*4>{}#frePrQkMf!=8Ntw&{ z!IWuY2mDokYDq=EIBD9}>%G;l-t#_3(%8&R_{r43notlNpt(HwR1O2>JI%P&O zw`a|JQmA@QOx9H4izt2^%Di2Oki-TOJ=YlkJZ#w}(Fy3zG!$2I;hWJUFgHkAGIKbr za%w1JCUnq6xK*7&?DsUgE-LwpVN!N}gJ(;7_s4#Ru#B3>)d2_Msy}wfqyQ^P8bl(D zW=$g8u>%UR5W2(pefBrD0>!Mz3}Ve$aawZt5@j~7$Q7@#%nz^MS-|nSJen!(*f*wQ z-n-H3?8+FQGqLrS3d1sM?VX}Oi_Uv2aeJ~cbfSF?7TkecI;VxJF{Ej(Y=)>WZExA3 z(@gmR2YTS$tA&BN?H^f3?B+DK)80%A(K7J6_R9m89ul|aXSZZXmZS?q=&~kw17`0^dAjNO~>NGw$}8gm2(m*Wh@~50VgF)peb8KDC#i z!&x(*3Tg99>b)UXgT#T=Jh`;X{TXBfH-C_Z2O0QDe{y!)4^j&;*p}n6` zK3^TaI!ScM0QvR}5eT9u=jM3vjPY>>LA;ve$M`?@rSq%Gl;atgJwit7;CmyxO3{%< zn+ftBG#8EQh30P4Z_Qns-5Zv-1vJK-gxO+Tg_-O^Ye&N8}Vu z@_R>s`MBoqPDIR0Ez0FhWOtSI;M<7Q&qwK$NAzvt+eGUuRn@9gD{aJ*S4SC3)jh*) zdLnL2vH9yqEiveZE062e34wRaD4`2 z8Q+Gyevs%;gy=d&Iv-U}oQLAT@Eak82l=T3T9d;Rj7NaUsulT%XMQZcW}e6k(HtNp zB9L27 zQ_d5Y*j*IP?QHLhKHC|2-(R>Y!W_EGXA-fV{=Xx8GtZ`8J79V6n&`Fp&Hg6&hmO%1 zaGcY=2%C9`Ewdw-F%rxCs6$jY212&}$=M%i3Gp{l;N4fFzoBHG6$>yC#hn*~CjpTn zlS@`jQyqZ15w`h=fdSH@!0H1HsOqV~bD{}9OTgP`z*^m7X!dfn4m2gA+H2fH9p^<5 zw*cLoL%dF5GzapIt3}zoKrgCAH!v_eLZ$nAw*WOHjR}iI@+@p61;3lg`zCBVW}(icM}-yfvtKMOr?!xTX?H!TbPrIj`UJ?WDw`kiz1AJ z*`SObDE}M82Fj2Jp#=HFl-3P`EA%&0i-+v0;)^1Cj#b>Q9E#V8K$~FXq<84=v`{2M zw{E88@^C};ryFix?3p!_CnoZGF!-d7s#IvoJ<8=00lIvf81wJd9sw?d@H_>`)u*l( zTsE`O5#;$ok#QHmdlo)>sgi9A&iDt^vBdyWLGC0Z=Vh@pm z9VmNMPWMHP>0_!Bfc|y)yGIt~gVqKv>WQ@pVdZ{|vQ(>rEY!s>%Y|6Bu`fRt5gqUC zf%jl`e{v?Q#0y)C3Tc>k15!iN6vEhtr=X<>%L8F7I*-_)!&ibj7zZKZ04s2Qo5T(L zC~U{p;d&9T0O$Q({=;EWe6fl8Ua*p-udpB@62T)>pw<)n6s?7-kKhRokV7`{_i~Qr zkKwYR(R^{@l~|M|Hj6VDwbc^&IOo@BxD1R4^Admvo`TN<$y*vB$Yu`S-Ut$12>Fs( z()1nf$;vz2w|ghtQ%$dVB&t!v9!F=37XQOHBpcw03Tja>rj`3X%KTbHSfVJYP{)PP zaBjW%1fxF#lvRGgXx-`uNP@>`j~Ur6)5~uc4iGJ`HpBp;|Q*4GF&TA1>>Uwa(x-e$JV% zw7Wq6$$15a>)j)E*S>1@I(+=JcjGhq$av(2zI-3Wm-v?{HMpt?#3V_=g#rQbP&&~z zmG~d}(ah+w0^Fj`>h^&2rV#t?=q=ATY@8SCcG%X|wBM4nP28B|Lz#(or{KmvNKe3_ zP0$|;E65o5H#IjlMG#F9LHji!#ZLnp6#%Ebi`k=!TVGl<$dg_xeRz%C;64OJ7sdKNF|0aC*1Is!F!yJbw$^+f)Gs zN<@kP=<{$n7Wlmo(!H}O{kP67kDEAl`(SwM_TlAUJtIHPjM;rlcjgOke@ju=PjBMC z+@ZyZwRyqQoczLH14@TGqu3h_X|4mp}8M5YpsYSI#e{l+g zGXU#iVU{=`Xw0O7LDN0R1Oj59MkxIt-3~T`qsoDgDbW0pL1&@0Ww+rQTgt$O3(Jku zkxR@k`ys>Pc zY`_+p2=bN$SbPq#iBMT~xaUfv#V3pGGSyVu>%0YB18+(6qfl%e0d^pka3fwTQQEE# z1|1w5rUTV7;efy;W`SK&vX{J?x_ZlUmoz+Xm>9NXJifW6mpkr8Ap$1C$`cJ2fsxcxD<<2<&btHNuG&S zXbH?dqwnMMblKO#Ln~6Bopz4DxzKLkv#*;pM9m0k2GlibMIGUctcCAjF098jT$1G~ zo%qe7VQ9e)QETu0K4PYc{;!sA_D_6~=;xxCH1qYp+9t&DYPoGMq%vyJleYf^i$9%g zH%`5IvzUTiTy{S+s{B`yxMhdiB3LlT*v4tPOt8+wB*rW+0$GWEmWPutW)L^FiKJm3 z<*3(_AEYZn&8Eb%zRh5QA%r+`i-xG65pg|zP;divQ#*a|6ei8NVn0{j)fI659j{*> zH=D9*y`n^5s1j*R=w1K!{jOrut;*=Mf(rD$d*kZWU-t}ad1fTU@n1(91)o?SUt3%AljSqukUY+jHo0y* zbY{kVNgZtwThmvEz(I>!s*_Gak*~sJ3Fo64W%d_;NcA7=zh>^jC+_&m{vT4Ne>_VH zCmQo0F3l%HUfxK->98g1A61+3xfv3=2mN!Df`=mb>aX^ncR#O0li>bR6zj99?T!Jy zWKN2v?#lj*@Z%P1dSROx7up-206x0(o_mW=1+{g5jwD}GKAJstLCo{)>8X!T8f}_B z?NFrX?9~?Y!TMel6-YuM=I`I}U%9V9Cg)^v;ceh!nU5YR^s{!`T#zuDA$m}MA}FP< zSZ~+6k6kwto(z(i!t(e62NEC1?m+dR#~B8zO?)YnElxpky6Br?y#PQvt+*fXhBdRu zSKjoM8fIrpBCWPm_b2rNE~GTR)9Vtxfv!eduWH(O*~EZrFXnDnKKt~*$h1W>XPf_m zWROl!{!!4-d^cS7*4jmai^#$ByXGMqFfwPzd97S>@~kOM%U*tT-FGfv^*U%LyodS% z7|5URQM~Js?4j`fEYD&0-Lq1i#iujEK9oDg4Qt($+ZrEy;@60G{t)ey>U1I#Bo7o@ z3VPcG>B3bQBuq_6cL(@#+|c7Vok@qq6N3WMe>gGt#{le~hANc) zsUWY>xHP8M1vQ+>Ui8{TFsh2>vj4YedDczbu3 zPM5CuJZO6S{k|DfnpGUhpGABq zNR8oWvc==??V-)Y0#Cc3`#D{u`f<5FtG*am%U5K)QQF|WUiYY|`SPug9>a05wRRl#RCWQL8QYk-w4&MGqlwR5A_Igu+-^35urz=+2uUufh-xgIzNi#MeAR zfgi&pUW|>g<`vm^`1!H5rhPG)Ur>~5Gu`23?3jU)blfDwwCPL zCrg&jbnH^Q?)s`tgh)d;)c{}>DEF37p(YD94QjG<8?@wP@hn^wvjY`L66BgJpsmKJ z96q}@9M|SRV7Z;c3dLnRpv;OAOD#V@00F!PN%vAy z756T3AZ41#!Xw&82xH8VGgevfaJJ;0(^~S_x-#gThBR;8-M;R*olQZ)LWxM3m$Owu z)-D%$g_fbkhu_PZjdJH(mYqt6x8ysG6tMO~&XSKxk&y(%I zv+PKRwfk?WAMOd?c<|Jr-8)h)kvQM@D~PvZQK%a$pgcOtri?N*%Cg^*sO*k5B?)37 zj`Uwo$r>)%xBjInJ0hS&?9>7{$>RX4Jf4%2`y?m#>C?PRb?@Fy^YmQddB!NVLTe?w$Cn7Mr!@rPU@pOZ0PX+{L&m*LDXkH>B3qTH zQ>qyXQW%;x#C?rOxhG0Sm%ZX1XI#E_`}y0tjOsqgPS%iX{Vn#^CHeuX8tiWX++*Ua zn+D%QZ+)Bt>N_hK)OFn2l=8|4SDDUnVUx>X-%4JofckiGCH9^3ldtSA;ksC&KRoeRzlMEF~a|3MeFMV8lE)_!J_4 z*x(#Bv}Ir;cTSAU!#=webK+z-xjk-Jn4fdKvhB9-jeFfL!YF%B%}?Bv?x~38Y95_K zq`77srb71hrKK|7(8+z`Q4Hr>=^3T4us3I$Pq_Jc+%-Glx%%Kxtm5`cU)TN@DmA7m zX8iSSHtrnRuJa!r*0nl0f{@0X@H`q{tc+z;l6_8QJuRq<+$Zp-x( z73tP&ip+q@{HO_yc-r<)#nEREH!W4hUq;2*9@`|PAzF*D@1gGK#2O@w3Qb1{(V*%0 zS3fJ|s#meX>DqNGi;zxMC_@hVovf*jpA^bfjJPJ5cbzV-yT11$wclq>R?x9sRgNiUmR=azBtzAlJl&yYLOqaUVd#^ zd&uX2{mm=O%cCsUCW-&dKo6ZelaSUoNB@>{jEj3}U+!#k4;NUSDt|X7A$#*2O+Syr z@(TmzHcSoIh?L&7-`2E(4gG`$ z$1YfJXiMw*^w!Is#HV|GO$H`g7DUc-K2|O<<3V)3=mZ_Y2nWF}7uno3Y;c3tIW@@)h4)Dj1znz6D=|4Fbl2pZ8thK^w}Cg2|K{mH&1eFXGO(CL z9|b(lQM9CsIdWZV7GOZdp)2JNaY5WRi3t@51+GDaznSS-!`Cno1wi6Sf20u5smquh zJY-zQt67Vvt_eZEld~Zr+zSz57BLIF-}NdQh}TnfE|fd14V_5nLst^r0IpU7EI5?b zCgNpcG9YF=mjSbj^WV)a{yDHli2Vl$DN8uMhHzNXD>2tMIGuD_K%$5caKGyQewa#*YzP(kL+Upp$1gf08Ojw!=oS+yS0v{W20^igtXXj++~l zW&&KroVW&6w*UzA)P><6h>Z)N`>Fh?!T`{EJG_;VS`p|(__Br+U=`{(x0QIw5_V*M zIsF>0H;o0KzHIJ{;Ln8)*;=P<66=I*i@zDF{NrDL(k&2PeexG5UBOVpQ2gx{Jhk{5 z+tI#9&_SF1MO3hh*-^uQVcJY6?S`->PUa?&`)^I6hyII1uzz9NmeT)c8YP0Op(US? zJh!3Kd{x?6L6k0~Ij^fGf!N~T0t$3g@t!6PxBh}G?BT2@W!uP=k^2N%slwl z_t{Oa64Hu-lm~x6OLjutmq+MB8I~YV4&761A<40U9HJG2HxY;-z)nNP&fDSg2od20 zOSQ-yzm4p(zf(E8Kq8EpuvH5QgdI0RtldwpNp7NI3k`6210e6_!W;ktc2F+Ip7CvQ zTkdcfYmRTHBSji^eKs!4ugmdUpXOw-pfJdDk(+6MhJRO#%5}(|8vf^5jSoyHo>cJd z=`R~iRO``0tZ;BK>s>kr9(WAPIQp7x3xB=hz}I(_MU8$hI9r@8_6u61^CYVtb&yk$ zpB$a�EI+qx3}>)FMCdH`2}tlftRP*WA3Lm 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 Wyt#?Rr!{9*uGPJT9;^K8#s2{zYI)QE From a37332d326e933eff5d5585e3746aa3abde43294 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Wed, 22 Sep 2021 12:19:16 +0000 Subject: [PATCH 40/81] Add GeneralRecognition config --- .../GeneralRecognition_LCNet_x2_5.yaml | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml diff --git a/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml b/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml new file mode 100644 index 000000000..3c30488b3 --- /dev/null +++ b/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml @@ -0,0 +1,174 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 100 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference + eval_mode: retrieval + use_dali: False + to_static: False + +# model architecture +Arch: + name: RecModel + infer_output_key: features + infer_add_softmax: False + + Backbone: + name: LCNet_x2_5 + pretrained: True + use_ssld: True + BackboneStopLayer: + name: flatten_0 + Neck: + name: FC + embedding_size: 1280 + class_num: 512 + Head: + name: ArcMargin + embedding_size: 512 + class_num: 185341 + margin: 0.2 + scale: 30 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + Eval: + - CELoss: + weight: 1.0 + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.04 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00001 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ + cls_label_path: ./dataset/train_reg_all_data.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 128 + num_instances: 2 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + Query: + dataset: + name: VeriWild + image_root: ./dataset/Aliproduct/ + cls_label_path: ./dataset/Aliproduct/test_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + + Gallery: + dataset: + name: VeriWild + image_root: ./dataset/Aliproduct/ + cls_label_path: ./dataset/Aliproduct/test_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] From 9245804e412d5fe556f7b7247fb671eb54e21139 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 23 Sep 2021 02:23:44 +0000 Subject: [PATCH 41/81] Update GeneralRecognition_LCNet_x2_5.yaml --- .../GeneralRecognition_LCNet_x2_5.yaml | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml b/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml index 3c30488b3..a7576b74d 100644 --- a/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml +++ b/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml @@ -97,7 +97,7 @@ DataLoader: dataset: name: VeriWild image_root: ./dataset/Aliproduct/ - cls_label_path: ./dataset/Aliproduct/test_list.txt + cls_label_path: ./dataset/Aliproduct/val_list.txt transform_ops: - DecodeImage: to_rgb: True @@ -122,7 +122,7 @@ DataLoader: dataset: name: VeriWild image_root: ./dataset/Aliproduct/ - cls_label_path: ./dataset/Aliproduct/test_list.txt + cls_label_path: ./dataset/Aliproduct/val_list.txt transform_ops: - DecodeImage: to_rgb: True @@ -143,32 +143,7 @@ DataLoader: num_workers: 4 use_shared_memory: True -Infer: - infer_imgs: docs/images/whl/demo.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: Topk - topk: 5 - class_id_map_file: ppcls/utils/imagenet1k_label_list.txt - Metric: - Train: - - TopkAcc: - topk: [1, 5] Eval: - - TopkAcc: + - Recallk: topk: [1, 5] From 929b845b2f82803b437455bb259239845856f0c8 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 23 Sep 2021 02:50:08 +0000 Subject: [PATCH 42/81] Update GeneralRecognition_PPLCNet_x2_5.yaml --- .../GeneralRecognition_PPLCNet_x2_5.yaml | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml diff --git a/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml b/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml new file mode 100644 index 000000000..a7576b74d --- /dev/null +++ b/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml @@ -0,0 +1,149 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 100 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference + eval_mode: retrieval + use_dali: False + to_static: False + +# model architecture +Arch: + name: RecModel + infer_output_key: features + infer_add_softmax: False + + Backbone: + name: LCNet_x2_5 + pretrained: True + use_ssld: True + BackboneStopLayer: + name: flatten_0 + Neck: + name: FC + embedding_size: 1280 + class_num: 512 + Head: + name: ArcMargin + embedding_size: 512 + class_num: 185341 + margin: 0.2 + scale: 30 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + Eval: + - CELoss: + weight: 1.0 + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.04 + warmup_epoch: 5 + regularizer: + name: 'L2' + coeff: 0.00001 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ + cls_label_path: ./dataset/train_reg_all_data.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 128 + num_instances: 2 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + Query: + dataset: + name: VeriWild + image_root: ./dataset/Aliproduct/ + cls_label_path: ./dataset/Aliproduct/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + + Gallery: + dataset: + name: VeriWild + image_root: ./dataset/Aliproduct/ + cls_label_path: ./dataset/Aliproduct/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Metric: + Eval: + - Recallk: + topk: [1, 5] From 126246832b4dcc7fa5ae0332d85bc190f9178df6 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 23 Sep 2021 02:51:50 +0000 Subject: [PATCH 43/81] Update GeneralRecognition_PPLCNet_x2_5.yaml --- .../GeneralRecognition_LCNet_x2_5.yaml | 149 ------------------ 1 file changed, 149 deletions(-) delete mode 100644 ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml diff --git a/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml b/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml deleted file mode 100644 index a7576b74d..000000000 --- a/ppcls/configs/GeneralRecognition/GeneralRecognition_LCNet_x2_5.yaml +++ /dev/null @@ -1,149 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 100 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference - eval_mode: retrieval - use_dali: False - to_static: False - -# model architecture -Arch: - name: RecModel - infer_output_key: features - infer_add_softmax: False - - Backbone: - name: LCNet_x2_5 - pretrained: True - use_ssld: True - BackboneStopLayer: - name: flatten_0 - Neck: - name: FC - embedding_size: 1280 - class_num: 512 - Head: - name: ArcMargin - embedding_size: 512 - class_num: 185341 - margin: 0.2 - scale: 30 - -# loss function config for traing/eval process -Loss: - Train: - - CELoss: - weight: 1.0 - Eval: - - CELoss: - weight: 1.0 - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.04 - warmup_epoch: 5 - regularizer: - name: 'L2' - coeff: 0.00001 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: ImageNetDataset - image_root: ./dataset/ - cls_label_path: ./dataset/train_reg_all_data.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 128 - num_instances: 2 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - Query: - dataset: - name: VeriWild - image_root: ./dataset/Aliproduct/ - cls_label_path: ./dataset/Aliproduct/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - size: 224 - - NormalizeImage: - scale: 0.00392157 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - - Gallery: - dataset: - name: VeriWild - image_root: ./dataset/Aliproduct/ - cls_label_path: ./dataset/Aliproduct/val_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - size: 224 - - NormalizeImage: - scale: 0.00392157 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Metric: - Eval: - - Recallk: - topk: [1, 5] From 16985020a57f0fff19d1f040a1dca7fce191009f Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 23 Sep 2021 03:20:34 +0000 Subject: [PATCH 44/81] Update GeneralRecognition_PPLCNet_x2_5.yaml --- .../GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml b/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml index a7576b74d..4def1be2d 100644 --- a/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml +++ b/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml @@ -24,7 +24,7 @@ Arch: infer_add_softmax: False Backbone: - name: LCNet_x2_5 + name: PPLCNet_x2_5 pretrained: True use_ssld: True BackboneStopLayer: From af25e25640124327ddb9eaf9d840a64c8125ba40 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Thu, 23 Sep 2021 11:22:25 +0800 Subject: [PATCH 45/81] modify format --- ppcls/engine/evaluation/classification.py | 32 +++++++------- ppcls/engine/evaluation/retrieval.py | 45 ++++++++++--------- ppcls/engine/train/train.py | 53 +++++++++++------------ 3 files changed, 62 insertions(+), 68 deletions(-) diff --git a/ppcls/engine/evaluation/classification.py b/ppcls/engine/evaluation/classification.py index b1ddc4186..95f508bc7 100644 --- a/ppcls/engine/evaluation/classification.py +++ b/ppcls/engine/evaluation/classification.py @@ -22,7 +22,7 @@ from ppcls.utils.misc import AverageMeter from ppcls.utils import logger -def classification_eval(evaler, epoch_id=0): +def classification_eval(engine, epoch_id=0): output_info = dict() time_info = { "batch_cost": AverageMeter( @@ -30,21 +30,19 @@ def classification_eval(evaler, epoch_id=0): "reader_cost": AverageMeter( "reader_cost", ".5f", postfix=" s,"), } - print_batch_step = evaler.config["Global"]["print_batch_step"] + print_batch_step = engine.config["Global"]["print_batch_step"] metric_key = None tic = time.time() - eval_dataloader = evaler.eval_dataloader if evaler.use_dali else evaler.eval_dataloader( - ) - max_iter = len(evaler.eval_dataloader) - 1 if platform.system( - ) == "Windows" else len(evaler.eval_dataloader) - for iter_id, batch in enumerate(eval_dataloader): + max_iter = len(engine.eval_dataloader) - 1 if platform.system( + ) == "Windows" else len(engine.eval_dataloader) + for iter_id, batch in enumerate(engine.eval_dataloader): if iter_id >= max_iter: break if iter_id == 5: for key in time_info: time_info[key].reset() - if evaler.use_dali: + if engine.use_dali: batch = [ paddle.to_tensor(batch[0]['data']), paddle.to_tensor(batch[0]['label']) @@ -54,17 +52,17 @@ def classification_eval(evaler, epoch_id=0): batch[0] = paddle.to_tensor(batch[0]).astype("float32") batch[1] = batch[1].reshape([-1, 1]).astype("int64") # image input - out = evaler.model(batch[0]) + out = engine.model(batch[0]) # calc loss - if evaler.eval_loss_func is not None: - loss_dict = evaler.eval_loss_func(out, batch[1]) + if engine.eval_loss_func is not None: + loss_dict = engine.eval_loss_func(out, batch[1]) for key in loss_dict: if key not in output_info: output_info[key] = AverageMeter(key, '7.5f') output_info[key].update(loss_dict[key].numpy()[0], batch_size) # calc metric - if evaler.eval_metric_func is not None: - metric_dict = evaler.eval_metric_func(out, batch[1]) + if engine.eval_metric_func is not None: + metric_dict = engine.eval_metric_func(out, batch[1]) if paddle.distributed.get_world_size() > 1: for key in metric_dict: paddle.distributed.all_reduce( @@ -97,18 +95,18 @@ def classification_eval(evaler, epoch_id=0): ]) logger.info("[Eval][Epoch {}][Iter: {}/{}]{}, {}, {}".format( epoch_id, iter_id, - len(evaler.eval_dataloader), metric_msg, time_msg, ips_msg)) + len(engine.eval_dataloader), metric_msg, time_msg, ips_msg)) tic = time.time() - if evaler.use_dali: - evaler.eval_dataloader.reset() + if engine.use_dali: + engine.eval_dataloader.reset() metric_msg = ", ".join([ "{}: {:.5f}".format(key, output_info[key].avg) for key in output_info ]) logger.info("[Eval][Epoch {}][Avg]{}".format(epoch_id, metric_msg)) # do not try to save best eval.model - if evaler.eval_metric_func is None: + if engine.eval_metric_func is None: return -1 # return 1st metric in the dict return output_info[metric_key].avg diff --git a/ppcls/engine/evaluation/retrieval.py b/ppcls/engine/evaluation/retrieval.py index bb6d08d31..bae77743d 100644 --- a/ppcls/engine/evaluation/retrieval.py +++ b/ppcls/engine/evaluation/retrieval.py @@ -20,21 +20,21 @@ import paddle from ppcls.utils import logger -def retrieval_eval(evaler, epoch_id=0): - evaler.model.eval() +def retrieval_eval(engine, epoch_id=0): + engine.model.eval() # step1. build gallery - if evaler.gallery_query_dataloader is not None: + if engine.gallery_query_dataloader is not None: gallery_feas, gallery_img_id, gallery_unique_id = cal_feature( - evaler, name='gallery_query') + engine, name='gallery_query') query_feas, query_img_id, query_query_id = gallery_feas, gallery_img_id, gallery_unique_id else: gallery_feas, gallery_img_id, gallery_unique_id = cal_feature( - evaler, name='gallery') + engine, name='gallery') query_feas, query_img_id, query_query_id = cal_feature( - evaler, name='query') + engine, name='query') # step2. do evaluation - sim_block_size = evaler.config["Global"].get("sim_block_size", 64) + sim_block_size = engine.config["Global"].get("sim_block_size", 64) sections = [sim_block_size] * (len(query_feas) // sim_block_size) if len(query_feas) % sim_block_size: sections.append(len(query_feas) % sim_block_size) @@ -45,7 +45,7 @@ def retrieval_eval(evaler, epoch_id=0): image_id_blocks = paddle.split(query_img_id, num_or_sections=sections) metric_key = None - if evaler.eval_loss_func is None: + if engine.eval_loss_func is None: metric_dict = {metric_key: 0.} else: metric_dict = dict() @@ -65,7 +65,7 @@ def retrieval_eval(evaler, epoch_id=0): else: keep_mask = None - metric_tmp = evaler.eval_metric_func(similarity_matrix, + metric_tmp = engine.eval_metric_func(similarity_matrix, image_id_blocks[block_idx], gallery_img_id, keep_mask) @@ -88,32 +88,31 @@ def retrieval_eval(evaler, epoch_id=0): return metric_dict[metric_key] -def cal_feature(evaler, name='gallery'): +def cal_feature(engine, name='gallery'): all_feas = None all_image_id = None all_unique_id = None has_unique_id = False if name == 'gallery': - dataloader = evaler.gallery_dataloader + dataloader = engine.gallery_dataloader elif name == 'query': - dataloader = evaler.query_dataloader + dataloader = engine.query_dataloader elif name == 'gallery_query': - dataloader = evaler.gallery_query_dataloader + dataloader = engine.gallery_query_dataloader else: raise RuntimeError("Only support gallery or query dataset") max_iter = len(dataloader) - 1 if platform.system() == "Windows" else len( dataloader) - dataloader_tmp = dataloader if evaler.use_dali else dataloader() - for idx, batch in enumerate(dataloader_tmp): # load is very time-consuming + for idx, batch in enumerate(dataloader): # load is very time-consuming if idx >= max_iter: break - if idx % evaler.config["Global"]["print_batch_step"] == 0: + if idx % engine.config["Global"]["print_batch_step"] == 0: logger.info( f"{name} feature calculation process: [{idx}/{len(dataloader)}]" ) - if evaler.use_dali: + if engine.use_dali: batch = [ paddle.to_tensor(batch[0]['data']), paddle.to_tensor(batch[0]['label']) @@ -123,20 +122,20 @@ def cal_feature(evaler, name='gallery'): if len(batch) == 3: has_unique_id = True batch[2] = batch[2].reshape([-1, 1]).astype("int64") - out = evaler.model(batch[0], batch[1]) + out = engine.model(batch[0], batch[1]) batch_feas = out["features"] # do norm - if evaler.config["Global"].get("feature_normalize", True): + if engine.config["Global"].get("feature_normalize", True): feas_norm = paddle.sqrt( paddle.sum(paddle.square(batch_feas), axis=1, keepdim=True)) batch_feas = paddle.divide(batch_feas, feas_norm) # do binarize - if evaler.config["Global"].get("feature_binarize") == "round": + if engine.config["Global"].get("feature_binarize") == "round": batch_feas = paddle.round(batch_feas).astype("float32") * 2.0 - 1.0 - if evaler.config["Global"].get("feature_binarize") == "sign": + if engine.config["Global"].get("feature_binarize") == "sign": batch_feas = paddle.sign(batch_feas).astype("float32") if all_feas is None: @@ -150,8 +149,8 @@ def cal_feature(evaler, name='gallery'): if has_unique_id: all_unique_id = paddle.concat([all_unique_id, batch[2]]) - if evaler.use_dali: - dataloader_tmp.reset() + if engine.use_dali: + dataloader.reset() if paddle.distributed.get_world_size() > 1: feat_list = [] diff --git a/ppcls/engine/train/train.py b/ppcls/engine/train/train.py index 73f225087..16b197594 100644 --- a/ppcls/engine/train/train.py +++ b/ppcls/engine/train/train.py @@ -18,19 +18,16 @@ import paddle from ppcls.engine.train.utils import update_loss, update_metric, log_info -def train_epoch(trainer, epoch_id, print_batch_step): +def train_epoch(engine, epoch_id, print_batch_step): tic = time.time() - - train_dataloader = trainer.train_dataloader if trainer.use_dali else trainer.train_dataloader( - ) - for iter_id, batch in enumerate(train_dataloader): - if iter_id >= trainer.max_iter: + for iter_id, batch in enumerate(engine.train_dataloader): + if iter_id >= engine.max_iter: break if iter_id == 5: - for key in trainer.time_info: - trainer.time_info[key].reset() - trainer.time_info["reader_cost"].update(time.time() - tic) - if trainer.use_dali: + for key in engine.time_info: + engine.time_info[key].reset() + engine.time_info["reader_cost"].update(time.time() - tic) + if engine.use_dali: batch = [ paddle.to_tensor(batch[0]['data']), paddle.to_tensor(batch[0]['label']) @@ -38,43 +35,43 @@ def train_epoch(trainer, epoch_id, print_batch_step): batch_size = batch[0].shape[0] batch[1] = batch[1].reshape([-1, 1]).astype("int64") - trainer.global_step += 1 + engine.global_step += 1 # image input - if trainer.amp: + if engine.amp: with paddle.amp.auto_cast(custom_black_list={ "flatten_contiguous_range", "greater_than" }): - out = forward(trainer, batch) - loss_dict = trainer.train_loss_func(out, batch[1]) + out = forward(engine, batch) + loss_dict = engine.train_loss_func(out, batch[1]) else: - out = forward(trainer, batch) + out = forward(engine, batch) # calc loss - if trainer.config["DataLoader"]["Train"]["dataset"].get( + if engine.config["DataLoader"]["Train"]["dataset"].get( "batch_transform_ops", None): - loss_dict = trainer.train_loss_func(out, batch[1:]) + loss_dict = engine.train_loss_func(out, batch[1:]) else: - loss_dict = trainer.train_loss_func(out, batch[1]) + loss_dict = engine.train_loss_func(out, batch[1]) # step opt and lr - if trainer.amp: - scaled = trainer.scaler.scale(loss_dict["loss"]) + if engine.amp: + scaled = engine.scaler.scale(loss_dict["loss"]) scaled.backward() - trainer.scaler.minimize(trainer.optimizer, scaled) + engine.scaler.minimize(engine.optimizer, scaled) else: loss_dict["loss"].backward() - trainer.optimizer.step() - trainer.optimizer.clear_grad() - trainer.lr_sch.step() + engine.optimizer.step() + engine.optimizer.clear_grad() + engine.lr_sch.step() # below code just for logging # update metric_for_logger - update_metric(trainer, out, batch, batch_size) + update_metric(engine, out, batch, batch_size) # update_loss_for_logger - update_loss(trainer, loss_dict, batch_size) - trainer.time_info["batch_cost"].update(time.time() - tic) + update_loss(engine, loss_dict, batch_size) + engine.time_info["batch_cost"].update(time.time() - tic) if iter_id % print_batch_step == 0: - log_info(trainer, batch_size, epoch_id, iter_id) + log_info(engine, batch_size, epoch_id, iter_id) tic = time.time() From 756b6fb3ded645d2774acf9defde0d167f39b929 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 23 Sep 2021 06:36:12 +0000 Subject: [PATCH 46/81] Update GeneralRecognition_PPLCNet_x2_5.yaml --- .../GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml b/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml index 4def1be2d..967673f2a 100644 --- a/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml +++ b/ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml @@ -84,8 +84,7 @@ DataLoader: sampler: name: DistributedBatchSampler - batch_size: 128 - num_instances: 2 + batch_size: 256 drop_last: False shuffle: True loader: From b7bab1e6480cdea756682e2d5b831b0ba113b27f Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Thu, 23 Sep 2021 08:29:07 +0000 Subject: [PATCH 47/81] fix googlenet avg-pool --- ppcls/arch/backbone/model_zoo/googlenet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ppcls/arch/backbone/model_zoo/googlenet.py b/ppcls/arch/backbone/model_zoo/googlenet.py index 00b7feeb9..22528427e 100644 --- a/ppcls/arch/backbone/model_zoo/googlenet.py +++ b/ppcls/arch/backbone/model_zoo/googlenet.py @@ -131,7 +131,7 @@ class GoogLeNetDY(nn.Layer): self._ince5b = Inception( 832, 832, 384, 192, 384, 48, 128, 128, name="ince5b") - self._pool_5 = AvgPool2D(kernel_size=7, stride=7) + self._pool_5 = AdaptiveAvgPool2D(1) self._drop = Dropout(p=0.4, mode="downscale_in_infer") self._fc_out = Linear( From 6529765a0bb8065fd6b45c1c6b1a74bf35301613 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Thu, 23 Sep 2021 19:35:35 +0800 Subject: [PATCH 48/81] update pksampler --- ppcls/data/dataloader/pk_sampler.py | 59 +++++++++++++++++------------ 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py index f78bdbd41..4e872ac09 100644 --- a/ppcls/data/dataloader/pk_sampler.py +++ b/ppcls/data/dataloader/pk_sampler.py @@ -48,50 +48,59 @@ class PKSampler(DistributedBatchSampler): "PKSampler configs error, Sample_per_id must be a divisor of batch_size." assert hasattr(self.dataset, "labels"), "Dataset must have labels attribute." - self.sample_per_id = sample_per_id + self.sample_per_label = sample_per_id self.label_dict = defaultdict(list) self.sample_method = sample_method + for idx, label in enumerate(self.dataset.labels): + self.label_dict[label].append(idx) + self.label_list = list(self.label_dict) + assert len(self.label_list) * self.sample_per_label > self.batch_size, \ + "batch size should be smaller than " if self.sample_method == "id_avg_prob": - for idx, label in enumerate(self.dataset.labels): - self.label_dict[label].append(idx) - self.id_list = list(self.label_dict) + self.prob_list = np.array([1 / len(self.label_list)] * + len(self.label_list)) elif self.sample_method == "sample_avg_prob": - self.id_list = [] - for idx, label in enumerate(self.dataset.labels): - self.label_dict[label].append(idx) + counter = [] + for label_i in self.label_list: + counter.append(len(self.label_list[label_i])) + self.prob_list = np.array(counter) / sum(counter) else: logger.error( "PKSampler only support id_avg_prob and sample_avg_prob sample method, " "but receive {}.".format(self.sample_method)) + if sum(np.abs(self.prob_list - 1) > 0.00000001): + self.prob_list[-1] = 1 - sum(self.prob_list[:-1]) + if self.prob_list[-1] > 1 or self.prob_list[-1] < 0: + logger.error("PKSampler prob list error") + else: + logger.info( + "PKSampler: sum of prob list not equal to 1, change the last prob" + ) def __iter__(self): + label_per_batch = self.batch_size // self.sample_per_label if self.shuffle: - np.random.RandomState(self.epoch).shuffle(self.id_list) - id_list = self.id_list[self.local_rank * len(self.id_list) // - self.nranks:(self.local_rank + 1) * len( - self.id_list) // self.nranks] - if self.sample_method == "id_avg_prob": - id_batch_num = len(id_list) * self.sample_per_id // self.batch_size - if id_batch_num < len(self): - id_list = id_list * (len(self) // id_batch_num + 1) - id_list = id_list[0:len(self)] - - id_per_batch = self.batch_size // self.sample_per_id + np.random.RandomState(self.epoch).shuffle(self.label_list) for i in range(len(self)): batch_index = [] - for label_id in id_list[i * id_per_batch:(i + 1) * id_per_batch]: - idx_label_list = self.label_dict[label_id] - if self.sample_per_id <= len(idx_label_list): + batch_label_list = np.random.sample( + self.label_list, + size=label_per_batch, + replace=False, + p=self.prob_list) + for label_i in batch_label_list: + label_i_indexes = self.label_dict[label_i] + if self.sample_per_label <= len(label_i_indexes): batch_index.extend( np.random.choice( - idx_label_list, - size=self.sample_per_id, + label_i_indexes, + size=self.sample_per_label, replace=False)) else: batch_index.extend( np.random.choice( - idx_label_list, - size=self.sample_per_id, + label_i_indexes, + size=self.sample_per_label, replace=True)) if not self.drop_last or len(batch_index) == self.batch_size: yield batch_index From 55495b69063a8e07d4bcfb0c049675a2159325f2 Mon Sep 17 00:00:00 2001 From: stephon Date: Fri, 24 Sep 2021 08:19:09 +0000 Subject: [PATCH 49/81] sRecognition Serving: support det and rec pipeline --- deploy/paddleserving/recognition/config.yml | 14 +- .../paddleserving/recognition/label_list.txt | 2 + .../recognition/pipeline_http_client.py | 4 +- .../recognition/pipeline_rpc_client.py | 2 +- .../recognition/recognition_web_service.py | 163 +++++++++++++++--- 5 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 deploy/paddleserving/recognition/label_list.txt diff --git a/deploy/paddleserving/recognition/config.yml b/deploy/paddleserving/recognition/config.yml index 9ccd0cfc3..f67ee5521 100644 --- a/deploy/paddleserving/recognition/config.yml +++ b/deploy/paddleserving/recognition/config.yml @@ -10,7 +10,7 @@ dag: #op资源类型, True, 为线程模型;False,为进程模型 is_thread_op: False op: - recog: + rec: #并发数,is_thread_op=True时,为线程并发;否则为进程并发 concurrency: 1 @@ -30,4 +30,14 @@ op: client_type: local_predictor #Fetch结果列表,以client_config中fetch_var的alias_name为准 - fetch_list: ["features"] \ No newline at end of file + fetch_list: ["features"] + + det: + concurrency: 1 + local_service_conf: + client_type: local_predictor + device_type: 1 + devices: '0' + fetch_list: + - save_infer_model/scale_0.tmp_1 + model_config: ../../models/ppyolov2_r50vd_dcn_mainbody_v1.0_serving/ \ No newline at end of file diff --git a/deploy/paddleserving/recognition/label_list.txt b/deploy/paddleserving/recognition/label_list.txt new file mode 100644 index 000000000..35e26a622 --- /dev/null +++ b/deploy/paddleserving/recognition/label_list.txt @@ -0,0 +1,2 @@ +foreground +background \ No newline at end of file diff --git a/deploy/paddleserving/recognition/pipeline_http_client.py b/deploy/paddleserving/recognition/pipeline_http_client.py index 8a9ffd536..aa0cb5429 100644 --- a/deploy/paddleserving/recognition/pipeline_http_client.py +++ b/deploy/paddleserving/recognition/pipeline_http_client.py @@ -9,13 +9,13 @@ def cv2_to_base64(image): return base64.b64encode(image).decode('utf8') if __name__ == "__main__": - url = "http://127.0.0.1:18081/recog_service/prediction" + url = "http://127.0.0.1:18081/recognition/prediction" with open(os.path.join(".", imgpath), 'rb') as file: image_data1 = file.read() image = cv2_to_base64(image_data1) data = {"key": ["image"], "value": [image]} - for i in range(5): + for i in range(1): r = requests.post(url=url, data=json.dumps(data)) print(r.json()) diff --git a/deploy/paddleserving/recognition/pipeline_rpc_client.py b/deploy/paddleserving/recognition/pipeline_rpc_client.py index fa43cf432..8a3257dc0 100644 --- a/deploy/paddleserving/recognition/pipeline_rpc_client.py +++ b/deploy/paddleserving/recognition/pipeline_rpc_client.py @@ -30,5 +30,5 @@ if __name__ == "__main__": image = cv2_to_base64(image_data) for i in range(1): - ret = client.predict(feed_dict={"image": image}, fetch=["label", "dist"]) + ret = client.predict(feed_dict={"image": image}, fetch=["result"]) print(ret) diff --git a/deploy/paddleserving/recognition/recognition_web_service.py b/deploy/paddleserving/recognition/recognition_web_service.py index 0e72a913c..442b312b8 100644 --- a/deploy/paddleserving/recognition/recognition_web_service.py +++ b/deploy/paddleserving/recognition/recognition_web_service.py @@ -11,20 +11,81 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import sys -from paddle_serving_app.reader import Sequential, URL2Image, Resize, CenterCrop, RGB2BGR, Transpose, Div, Normalize, Base64ToImage -try: - from paddle_serving_server_gpu.web_service import WebService, Op -except ImportError: - from paddle_serving_server.web_service import WebService, Op +from paddle_serving_server.web_service import WebService, Op import logging import numpy as np -import base64, cv2 +import sys +import cv2 +from paddle_serving_app.reader import * +import base64 import os import faiss import pickle +import json -class RecogOp(Op): +class DetOp(Op): + def init_op(self): + self.img_preprocess = Sequential([ + BGR2RGB(), Div(255.0), + Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], False), + Resize((640, 640)), Transpose((2, 0, 1)) + ]) + + self.img_postprocess = RCNNPostprocess("label_list.txt", "output") + self.threshold = 0.2 + self.max_det_results = 5 + + def generate_scale(self, im): + """ + Args: + im (np.ndarray): image (np.ndarray) + Returns: + im_scale_x: the resize ratio of X + im_scale_y: the resize ratio of Y + """ + target_size = [640, 640] + origin_shape = im.shape[:2] + resize_h, resize_w = target_size + im_scale_y = resize_h / float(origin_shape[0]) + im_scale_x = resize_w / float(origin_shape[1]) + return im_scale_y, im_scale_x + + def preprocess(self, input_dicts, data_id, log_id): + (_, input_dict), = input_dicts.items() + imgs = [] + raw_imgs = [] + for key in input_dict.keys(): + data = base64.b64decode(input_dict[key].encode('utf8')) + raw_imgs.append(data) + data = np.fromstring(data, np.uint8) + raw_im = cv2.imdecode(data, cv2.IMREAD_COLOR) + + im_scale_y, im_scale_x = self.generate_scale(raw_im) + im = self.img_preprocess(raw_im) + + imgs.append({ + "image": im[np.newaxis, :], + "im_shape": np.array(list(im.shape[1:])).reshape(-1)[np.newaxis,:], + "scale_factor": np.array([im_scale_y, im_scale_x]).astype('float32'), + }) + self.raw_img = raw_imgs + + feed_dict = { + "image": np.concatenate([x["image"] for x in imgs], axis=0), + "im_shape": np.concatenate([x["im_shape"] for x in imgs], axis=0), + "scale_factor": np.concatenate([x["scale_factor"] for x in imgs], axis=0) + } + return feed_dict, False, None, "" + + def postprocess(self, input_dicts, fetch_dict, log_id): + boxes = self.img_postprocess(fetch_dict, visualize=False) + boxes.sort(key = lambda x: x["score"], reverse = True) + boxes = filter(lambda x: x["score"] >= self.threshold, boxes[:self.max_det_results]) + result = json.dumps(list(boxes)) + res_dict = {"bbox_result": result, "image": self.raw_img} + return res_dict, None, "" + +class RecOp(Op): def init_op(self): self.seq = Sequential([ Resize(256), CenterCrop(224), RGB2BGR(), Transpose((2, 0, 1)), @@ -32,7 +93,6 @@ class RecogOp(Op): True) ]) - #load index; and return top1 index_dir = "../../recognition_demo_data_v1.1/gallery_product/index" assert os.path.exists(os.path.join( index_dir, "vector.index")), "vector.index not found ..." @@ -45,35 +105,82 @@ class RecogOp(Op): with open(os.path.join(index_dir, "id_map.pkl"), "rb") as fd: self.id_map = pickle.load(fd) + self.rec_nms_thresold = 0.05 + self.rec_score_thres = 0.5 + def preprocess(self, input_dicts, data_id, log_id): (_, input_dict), = input_dicts.items() - batch_size = len(input_dict.keys()) + raw_img = input_dict["image"][0] + data = np.frombuffer(raw_img, np.uint8) + origin_img = cv2.imdecode(data, cv2.IMREAD_COLOR) + dt_boxes = input_dict["bbox_result"] + boxes = json.loads(dt_boxes) + boxes.append({"category_id": 0, + "score": 1.0, + "bbox": [0, 0, origin_img.shape[1], origin_img.shape[0]] + }) + self.det_boxes = boxes + + #construct batch images for rec imgs = [] - for key in input_dict.keys(): - data = base64.b64decode(input_dict[key].encode('utf8')) - data = np.fromstring(data, np.uint8) - im = cv2.imdecode(data, cv2.IMREAD_COLOR) + for box in boxes: + box = [int(x) for x in box["bbox"]] + im = origin_img[box[1]: box[1] + box[3], box[0]: box[0] + box[2]].copy() img = self.seq(im) imgs.append(img[np.newaxis, :].copy()) + input_imgs = np.concatenate(imgs, axis=0) - return {"x": input_imgs}, False, None, "" + return {"x": input_imgs}, False, None, "" + + def nms_to_rec_results(self, results, thresh = 0.1): + filtered_results = [] + x1 = np.array([r["bbox"][0] for r in results]).astype("float32") + y1 = np.array([r["bbox"][1] for r in results]).astype("float32") + x2 = np.array([r["bbox"][2] for r in results]).astype("float32") + y2 = np.array([r["bbox"][3] for r in results]).astype("float32") + scores = np.array([r["rec_scores"] for r in results]) + + areas = (x2 - x1 + 1) * (y2 - y1 + 1) + order = scores.argsort()[::-1] + while order.size > 0: + i = order[0] + xx1 = np.maximum(x1[i], x1[order[1:]]) + yy1 = np.maximum(y1[i], y1[order[1:]]) + xx2 = np.minimum(x2[i], x2[order[1:]]) + yy2 = np.minimum(y2[i], y2[order[1:]]) + + w = np.maximum(0.0, xx2 - xx1 + 1) + h = np.maximum(0.0, yy2 - yy1 + 1) + inter = w * h + ovr = inter / (areas[i] + areas[order[1:]] - inter) + inds = np.where(ovr <= thresh)[0] + order = order[inds + 1] + filtered_results.append(results[i]) + return filtered_results def postprocess(self, input_dicts, fetch_dict, log_id): score_list = fetch_dict["features"] + scores, docs = self.searcher.search(score_list, 1) + + results = [] + for i in range(scores.shape[0]): + pred = {} + if scores[i][0] >= self.rec_score_thres: + pred["bbox"] = self.det_boxes[i]["bbox"] + pred["rec_docs"] = self.id_map[docs[i][0]].split()[1] + pred["rec_scores"] = scores[i][0] + results.append(pred) - return_top_k = 1 - scores, docs = self.searcher.search(score_list, return_top_k) + #do nms + results = self.nms_to_rec_results(results, self.rec_nms_thresold) + return {"result": str(results)}, None, "" - result = {} - result["label"] = self.id_map[docs[0][0]].split()[1] - result["dist"] = str(scores[0][0]) - return result, None, "" - -class ProductRecognitionService(WebService): +class RecognitionService(WebService): def get_pipeline_response(self, read_op): - image_op = RecogOp(name="recog", input_ops=[read_op]) - return image_op + det_op = DetOp(name="det", input_ops=[read_op]) + rec_op = RecOp(name="rec", input_ops=[det_op]) + return rec_op -uci_service = ProductRecognitionService(name="recog_service") -uci_service.prepare_pipeline_config("config.yml") -uci_service.run_service() +product_recog_service = RecognitionService(name="recognition") +product_recog_service.prepare_pipeline_config("config.yml") +product_recog_service.run_service() From de859b4a3dc35c439d63fb4732b67bae12adcbb1 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Sun, 26 Sep 2021 04:45:12 +0000 Subject: [PATCH 50/81] fix: compatible with opencv under version 4.4.0 --- deploy/python/preprocess.py | 3 +++ ppcls/data/preprocess/ops/operators.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/deploy/python/preprocess.py b/deploy/python/preprocess.py index aaa0bef35..5d7fc9296 100644 --- a/deploy/python/preprocess.py +++ b/deploy/python/preprocess.py @@ -78,6 +78,9 @@ class UnifiedResize(object): if backend.lower() == "cv2": if isinstance(interpolation, str): interpolation = _cv2_interp_from_str[interpolation.lower()] + # compatible with opencv < version 4.4.0 + elif not interpolation: + interpolation = cv2.INTER_LINEAR self.resize_func = partial(cv2.resize, interpolation=interpolation) elif backend.lower() == "pil": if isinstance(interpolation, str): diff --git a/ppcls/data/preprocess/ops/operators.py b/ppcls/data/preprocess/ops/operators.py index 4418f5293..e46823d2a 100644 --- a/ppcls/data/preprocess/ops/operators.py +++ b/ppcls/data/preprocess/ops/operators.py @@ -59,6 +59,9 @@ class UnifiedResize(object): if backend.lower() == "cv2": if isinstance(interpolation, str): interpolation = _cv2_interp_from_str[interpolation.lower()] + # compatible with opencv < version 4.4.0 + elif not interpolation: + interpolation = cv2.INTER_LINEAR self.resize_func = partial(cv2.resize, interpolation=interpolation) elif backend.lower() == "pil": if isinstance(interpolation, str): From 7cf64ce46c4143bebc4b28c2ec70fc6ddca06494 Mon Sep 17 00:00:00 2001 From: stephon Date: Sun, 26 Sep 2021 05:21:35 +0000 Subject: [PATCH 51/81] fix some bugs; same as paddle inference result --- .../recognition/recognition_web_service.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/deploy/paddleserving/recognition/recognition_web_service.py b/deploy/paddleserving/recognition/recognition_web_service.py index 442b312b8..88daf96e6 100644 --- a/deploy/paddleserving/recognition/recognition_web_service.py +++ b/deploy/paddleserving/recognition/recognition_web_service.py @@ -81,16 +81,20 @@ class DetOp(Op): boxes = self.img_postprocess(fetch_dict, visualize=False) boxes.sort(key = lambda x: x["score"], reverse = True) boxes = filter(lambda x: x["score"] >= self.threshold, boxes[:self.max_det_results]) - result = json.dumps(list(boxes)) - res_dict = {"bbox_result": result, "image": self.raw_img} + boxes = list(boxes) + for i in range(len(boxes)): + boxes[i]["bbox"][2] += boxes[i]["bbox"][0] - 1 + boxes[i]["bbox"][3] += boxes[i]["bbox"][1] - 1 + result = json.dumps(boxes) + res_dict = {"bbox_result": result, "image": self.raw_img} return res_dict, None, "" class RecOp(Op): def init_op(self): self.seq = Sequential([ - Resize(256), CenterCrop(224), RGB2BGR(), Transpose((2, 0, 1)), + BGR2RGB(), Resize((224, 224)), Div(255), Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], - True) + False), Transpose((2, 0, 1)) ]) index_dir = "../../recognition_demo_data_v1.1/gallery_product/index" @@ -107,6 +111,8 @@ class RecOp(Op): self.rec_nms_thresold = 0.05 self.rec_score_thres = 0.5 + self.feature_normalize = True + self.return_k = 1 def preprocess(self, input_dicts, data_id, log_id): (_, input_dict), = input_dicts.items() @@ -125,7 +131,7 @@ class RecOp(Op): imgs = [] for box in boxes: box = [int(x) for x in box["bbox"]] - im = origin_img[box[1]: box[1] + box[3], box[0]: box[0] + box[2]].copy() + im = origin_img[box[1]: box[3], box[0]: box[2]].copy() img = self.seq(im) imgs.append(img[np.newaxis, :].copy()) @@ -159,14 +165,20 @@ class RecOp(Op): return filtered_results def postprocess(self, input_dicts, fetch_dict, log_id): - score_list = fetch_dict["features"] - scores, docs = self.searcher.search(score_list, 1) + batch_features = fetch_dict["features"] + + if self.feature_normalize: + feas_norm = np.sqrt( + np.sum(np.square(batch_features), axis=1, keepdims=True)) + batch_features = np.divide(batch_features, feas_norm) + + scores, docs = self.searcher.search(batch_features, self.return_k) results = [] for i in range(scores.shape[0]): pred = {} if scores[i][0] >= self.rec_score_thres: - pred["bbox"] = self.det_boxes[i]["bbox"] + pred["bbox"] = [int(x) for x in self.det_boxes[i]["bbox"]] pred["rec_docs"] = self.id_map[docs[i][0]].split()[1] pred["rec_scores"] = scores[i][0] results.append(pred) From b84e4352b11104176b3baa9c594c404ca3b07c41 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sun, 26 Sep 2021 14:28:12 +0800 Subject: [PATCH 52/81] dbg --- ppcls/data/dataloader/pk_sampler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py index 4e872ac09..ef65be940 100644 --- a/ppcls/data/dataloader/pk_sampler.py +++ b/ppcls/data/dataloader/pk_sampler.py @@ -62,7 +62,7 @@ class PKSampler(DistributedBatchSampler): elif self.sample_method == "sample_avg_prob": counter = [] for label_i in self.label_list: - counter.append(len(self.label_list[label_i])) + counter.append(len(self.label_dict[label_i])) self.prob_list = np.array(counter) / sum(counter) else: logger.error( @@ -83,7 +83,7 @@ class PKSampler(DistributedBatchSampler): np.random.RandomState(self.epoch).shuffle(self.label_list) for i in range(len(self)): batch_index = [] - batch_label_list = np.random.sample( + batch_label_list = np.random.choice( self.label_list, size=label_per_batch, replace=False, From af9aae730e055cf9a162306b3c4b1937a34c9442 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Sun, 26 Sep 2021 07:05:13 +0000 Subject: [PATCH 53/81] add multilabel feature --- deploy/configs/inference_multilabel_cls.yaml | 33 +++++ deploy/images/0517_2715693311.jpg | Bin 0 -> 16727 bytes deploy/python/postprocess.py | 23 +++- deploy/python/predict_cls.py | 2 - deploy/shell/predict.sh | 3 + .../multilabel/multilabel.md | 88 ++++++------ .../quick_start/MobileNetV1_multilabel.yaml | 129 ++++++++++++++++++ .../professional/MobileNetV1_multilabel.yaml | 129 ++++++++++++++++++ ppcls/data/dataloader/multilabel_dataset.py | 7 +- ppcls/data/postprocess/__init__.py | 2 +- ppcls/data/postprocess/topk.py | 16 ++- ppcls/engine/engine.py | 19 +-- ppcls/engine/evaluation/classification.py | 3 +- ppcls/engine/train/train.py | 4 +- ppcls/loss/__init__.py | 1 + ppcls/loss/multilabelloss.py | 43 ++++++ ppcls/metric/__init__.py | 6 +- ppcls/metric/metrics.py | 76 ++++++++++- tools/train.sh | 2 +- train.sh | 7 + 20 files changed, 524 insertions(+), 69 deletions(-) create mode 100644 deploy/configs/inference_multilabel_cls.yaml create mode 100644 deploy/images/0517_2715693311.jpg create mode 100644 ppcls/configs/quick_start/MobileNetV1_multilabel.yaml create mode 100644 ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml create mode 100644 ppcls/loss/multilabelloss.py create mode 100755 train.sh diff --git a/deploy/configs/inference_multilabel_cls.yaml b/deploy/configs/inference_multilabel_cls.yaml new file mode 100644 index 000000000..9dc052979 --- /dev/null +++ b/deploy/configs/inference_multilabel_cls.yaml @@ -0,0 +1,33 @@ +Global: + infer_imgs: "./images/0517_2715693311.jpg" + inference_model_dir: "../inference/" + batch_size: 1 + use_gpu: True + enable_mkldnn: False + cpu_num_threads: 10 + enable_benchmark: True + use_fp16: False + ir_optim: True + use_tensorrt: False + gpu_mem: 8000 + enable_profile: False +PreProcess: + transform_ops: + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + channel_num: 3 + - ToCHWImage: +PostProcess: + main_indicator: MultiLabelTopk + MultiLabelTopk: + topk: 5 + class_id_map_file: None + SavePreLabel: + save_dir: ./pre_label/ diff --git a/deploy/images/0517_2715693311.jpg b/deploy/images/0517_2715693311.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bd9d2f632192b5be14a8f684303bdeb87bedcfaf GIT binary patch literal 16727 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 literal 0 HcmV?d00001 diff --git a/deploy/python/postprocess.py b/deploy/python/postprocess.py index 61b5fbceb..d26cbaa9a 100644 --- a/deploy/python/postprocess.py +++ b/deploy/python/postprocess.py @@ -81,12 +81,14 @@ class Topk(object): class_id_map = None return class_id_map - def __call__(self, x, file_names=None): + def __call__(self, x, file_names=None, multilabel=False): if file_names is not None: assert x.shape[0] == len(file_names) y = [] for idx, probs in enumerate(x): - index = probs.argsort(axis=0)[-self.topk:][::-1].astype("int32") + index = probs.argsort(axis=0)[-self.topk:][::-1].astype( + "int32") if not multilabel else np.where( + probs >= 0.5)[0].astype("int32") clas_id_list = [] score_list = [] label_name_list = [] @@ -108,6 +110,14 @@ class Topk(object): return y +class MultiLabelTopk(Topk): + def __init__(self, topk=1, class_id_map_file=None): + super().__init__() + + def __call__(self, x, file_names=None): + return super().__call__(x, file_names, multilabel=True) + + class SavePreLabel(object): def __init__(self, save_dir): if save_dir is None: @@ -128,23 +138,24 @@ class SavePreLabel(object): os.makedirs(output_dir, exist_ok=True) shutil.copy(image_file, output_dir) + class Binarize(object): - def __init__(self, method = "round"): + def __init__(self, method="round"): self.method = method self.unit = np.array([[128, 64, 32, 16, 8, 4, 2, 1]]).T def __call__(self, x, file_names=None): if self.method == "round": x = np.round(x + 1).astype("uint8") - 1 - + if self.method == "sign": x = ((np.sign(x) + 1) / 2).astype("uint8") embedding_size = x.shape[1] assert embedding_size % 8 == 0, "The Binary index only support vectors with sizes multiple of 8" - + byte = np.zeros([x.shape[0], embedding_size // 8], dtype=np.uint8) for i in range(embedding_size // 8): - byte[:, i:i+1] = np.dot(x[:, i * 8: (i + 1)* 8], self.unit) + byte[:, i:i + 1] = np.dot(x[:, i * 8:(i + 1) * 8], self.unit) return byte diff --git a/deploy/python/predict_cls.py b/deploy/python/predict_cls.py index dc6865404..cdeb32e48 100644 --- a/deploy/python/predict_cls.py +++ b/deploy/python/predict_cls.py @@ -71,7 +71,6 @@ class ClsPredictor(Predictor): output_names = self.paddle_predictor.get_output_names() output_tensor = self.paddle_predictor.get_output_handle(output_names[ 0]) - if self.benchmark: self.auto_logger.times.start() if not isinstance(images, (list, )): @@ -119,7 +118,6 @@ def main(config): ) == len(image_list): if len(batch_imgs) == 0: continue - batch_results = cls_predictor.predict(batch_imgs) for number, result_dict in enumerate(batch_results): filename = batch_names[number] diff --git a/deploy/shell/predict.sh b/deploy/shell/predict.sh index 44be94286..f0f59f4ac 100644 --- a/deploy/shell/predict.sh +++ b/deploy/shell/predict.sh @@ -1,6 +1,9 @@ # classification python3.7 python/predict_cls.py -c configs/inference_cls.yaml +# multilabel_classification +#python3.7 python/predict_cls.py -c configs/inference_multilabel_cls.yaml + # feature extractor # python3.7 python/predict_rec.py -c configs/inference_rec.yaml diff --git a/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md b/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md index ef445ca82..50eec827a 100644 --- a/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md +++ b/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md @@ -25,58 +25,66 @@ tar -xf NUS-SCENE-dataset.tar cd ../../ ``` -## 二、环境准备 - -### 2.1 下载预训练模型 - -本例展示基于ResNet50_vd模型的多标签分类流程,因此首先下载ResNet50_vd的预训练模型 - -```bash -mkdir pretrained -cd pretrained -wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_pretrained.pdparams -cd ../ -``` - -## 三、模型训练 +## 二、模型训练 ```shell -export CUDA_VISIBLE_DEVICES=0 -python -m paddle.distributed.launch \ - --gpus="0" \ +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python3 -m paddle.distributed.launch \ + --gpus="0,1,2,3" \ tools/train.py \ - -c ./configs/quick_start/ResNet50_vd_multilabel.yaml + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml ``` -训练10epoch之后,验证集最好的正确率应该在0.72左右。 +训练10epoch之后,验证集最好的正确率应该在0.95左右。 -## 四、模型评估 +## 三、模型评估 ```bash -python tools/eval.py \ - -c ./configs/quick_start/ResNet50_vd_multilabel.yaml \ - -o pretrained_model="./output/ResNet50_vd/best_model/ppcls" \ - -o load_static_weights=False +python3 tools/eval.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" ``` -评估指标采用mAP,验证集的mAP应该在0.57左右。 - -## 五、模型预测 +## 四、模型预测 ```bash -python tools/infer/infer.py \ - -i "./dataset/NUS-WIDE-SCENE/NUS-SCENE-dataset/images/0199_434752251.jpg" \ - --model ResNet50_vd \ - --pretrained_model "./output/ResNet50_vd/best_model/ppcls" \ - --use_gpu True \ - --load_static_weights False \ - --multilabel True \ - --class_num 33 +python3 tools/infer.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" ``` 得到类似下面的输出: -``` - class id: 3, probability: 0.6025 - class id: 23, probability: 0.5491 - class id: 32, probability: 0.7006 -``` \ No newline at end of file +``` +[{'class_ids': [6, 13, 17, 23, 26, 30], 'scores': [0.95683, 0.5567, 0.55211, 0.99088, 0.5943, 0.78767], 'file_name': './deploy/images/0517_2715693311.jpg', 'label_names': []}] +``` + +## 五、基于预测引擎预测 + +### 5.1 导出inference model + +```bash +python3 tools/export_model.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" +``` +inference model的路径默认在当前路径下`./inference` + +### 5.2 基于预测引擎预测 + +首先进入deploy目录下: + +```bash +cd ./deploy +``` + +通过预测引擎推理预测: + +``` +python3 python/predict_cls.py \ + -c configs/inference_multilabel_cls.yaml +``` + +得到类似下面的输出: +``` +0517_2715693311.jpg: class id(s): [6, 13, 17, 23, 26, 30], score(s): [0.96, 0.56, 0.55, 0.99, 0.59, 0.79], label_name(s): [] +``` diff --git a/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml b/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml new file mode 100644 index 000000000..e9c021b65 --- /dev/null +++ b/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 10 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference + use_multilabel: True +# model architecture +Arch: + name: MobileNetV1 + class_num: 33 + pretrained: True + +# loss function config for traing/eval process +Loss: + Train: + - MultiLabelLoss: + weight: 1.0 + Eval: + - MultiLabelLoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.1 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_test_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 256 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: dataset/NUS-SCENE-dataset/images/0001_109549716.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: MutiLabelTopk + topk: 5 + class_id_map_file: None + +Metric: + Train: + - HammingDistance: + - AccuracyScore: + Eval: + - HammingDistance: + - AccuracyScore: diff --git a/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml b/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml new file mode 100644 index 000000000..7c9e5a7eb --- /dev/null +++ b/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 10 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference + use_multilabel: True +# model architecture +Arch: + name: MobileNetV1 + class_num: 33 + pretrained: True + +# loss function config for traing/eval process +Loss: + Train: + - MultiLabelLoss: + weight: 1.0 + Eval: + - MultiLabelLoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.1 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_test_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 256 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: ./deploy/images/0517_2715693311.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: MultiLabelTopk + topk: 5 + class_id_map_file: None + +Metric: + Train: + - HammingDistance: + - AccuracyScore: + Eval: + - HammingDistance: + - AccuracyScore: diff --git a/ppcls/data/dataloader/multilabel_dataset.py b/ppcls/data/dataloader/multilabel_dataset.py index fafecc711..08d2ba15b 100644 --- a/ppcls/data/dataloader/multilabel_dataset.py +++ b/ppcls/data/dataloader/multilabel_dataset.py @@ -33,7 +33,7 @@ class MultiLabelDataset(CommonDataset): with open(self._cls_path) as fd: lines = fd.readlines() for l in lines: - l = l.strip().split(" ") + l = l.strip().split("\t") self.images.append(os.path.join(self._img_root, l[0])) labels = l[1].split(',') @@ -44,13 +44,14 @@ class MultiLabelDataset(CommonDataset): def __getitem__(self, idx): try: - img = cv2.imread(self.images[idx]) - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + with open(self.images[idx], 'rb') as f: + img = f.read() if self._transform_ops: img = transform(img, self._transform_ops) img = img.transpose((2, 0, 1)) label = np.array(self.labels[idx]).astype("float32") return (img, label) + except Exception as ex: logger.error("Exception occured when parse line: {} with msg: {}". format(self.images[idx], ex)) diff --git a/ppcls/data/postprocess/__init__.py b/ppcls/data/postprocess/__init__.py index 801e7f101..831a4da00 100644 --- a/ppcls/data/postprocess/__init__.py +++ b/ppcls/data/postprocess/__init__.py @@ -16,7 +16,7 @@ import importlib from . import topk -from .topk import Topk +from .topk import Topk, MultiLabelTopk def build_postprocess(config): diff --git a/ppcls/data/postprocess/topk.py b/ppcls/data/postprocess/topk.py index 2410e3291..9c1371bfd 100644 --- a/ppcls/data/postprocess/topk.py +++ b/ppcls/data/postprocess/topk.py @@ -45,15 +45,17 @@ class Topk(object): class_id_map = None return class_id_map - def __call__(self, x, file_names=None): + def __call__(self, x, file_names=None, multilabel=False): assert isinstance(x, paddle.Tensor) if file_names is not None: assert x.shape[0] == len(file_names) - x = F.softmax(x, axis=-1) + x = F.softmax(x, axis=-1) if not multilabel else F.sigmoid(x) x = x.numpy() y = [] for idx, probs in enumerate(x): - index = probs.argsort(axis=0)[-self.topk:][::-1].astype("int32") + index = probs.argsort(axis=0)[-self.topk:][::-1].astype( + "int32") if not multilabel else np.where( + probs >= 0.5)[0].astype("int32") clas_id_list = [] score_list = [] label_name_list = [] @@ -73,3 +75,11 @@ class Topk(object): result["label_names"] = label_name_list y.append(result) return y + + +class MultiLabelTopk(Topk): + def __init__(self, topk=1, class_id_map_file=None): + super().__init__() + + def __call__(self, x, file_names=None): + return super().__call__(x, file_names, multilabel=True) diff --git a/ppcls/engine/engine.py b/ppcls/engine/engine.py index 2d488f8c1..d0f2d6472 100644 --- a/ppcls/engine/engine.py +++ b/ppcls/engine/engine.py @@ -355,7 +355,8 @@ class Engine(object): def export(self): assert self.mode == "export" - model = ExportModel(self.config["Arch"], self.model) + use_multilabel = self.config["Global"].get("use_multilabel", False) + model = ExportModel(self.config["Arch"], self.model, use_multilabel) if self.config["Global"]["pretrained_model"] is not None: load_dygraph_pretrain(model.base_model, self.config["Global"]["pretrained_model"]) @@ -388,10 +389,9 @@ class ExportModel(nn.Layer): ExportModel: add softmax onto the model """ - def __init__(self, config, model): + def __init__(self, config, model, use_multilabel): super().__init__() self.base_model = model - # we should choose a final model to export if isinstance(self.base_model, DistillationModel): self.infer_model_name = config["infer_model_name"] @@ -402,10 +402,13 @@ class ExportModel(nn.Layer): if self.infer_output_key == "features" and isinstance(self.base_model, RecModel): self.base_model.head = IdentityHead() - if config.get("infer_add_softmax", True): - self.softmax = nn.Softmax(axis=-1) + if use_multilabel: + self.out_act = nn.Sigmoid() else: - self.softmax = None + if config.get("infer_add_softmax", True): + self.out_act = nn.Softmax(axis=-1) + else: + self.out_act = None def eval(self): self.training = False @@ -421,6 +424,6 @@ class ExportModel(nn.Layer): x = x[self.infer_model_name] if self.infer_output_key is not None: x = x[self.infer_output_key] - if self.softmax is not None: - x = self.softmax(x) + if self.out_act is not None: + x = self.out_act(x) return x diff --git a/ppcls/engine/evaluation/classification.py b/ppcls/engine/evaluation/classification.py index b1ddc4186..005d740d3 100644 --- a/ppcls/engine/evaluation/classification.py +++ b/ppcls/engine/evaluation/classification.py @@ -52,7 +52,8 @@ def classification_eval(evaler, epoch_id=0): time_info["reader_cost"].update(time.time() - tic) batch_size = batch[0].shape[0] batch[0] = paddle.to_tensor(batch[0]).astype("float32") - batch[1] = batch[1].reshape([-1, 1]).astype("int64") + if not evaler.config["Global"].get("use_multilabel", False): + batch[1] = batch[1].reshape([-1, 1]).astype("int64") # image input out = evaler.model(batch[0]) # calc loss diff --git a/ppcls/engine/train/train.py b/ppcls/engine/train/train.py index 73f225087..e15854834 100644 --- a/ppcls/engine/train/train.py +++ b/ppcls/engine/train/train.py @@ -36,8 +36,8 @@ def train_epoch(trainer, epoch_id, print_batch_step): paddle.to_tensor(batch[0]['label']) ] batch_size = batch[0].shape[0] - batch[1] = batch[1].reshape([-1, 1]).astype("int64") - + if not trainer.config["Global"].get("use_multilabel", False): + batch[1] = batch[1].reshape([-1, 1]).astype("int64") trainer.global_step += 1 # image input if trainer.amp: diff --git a/ppcls/loss/__init__.py b/ppcls/loss/__init__.py index 5421f4212..7c0374808 100644 --- a/ppcls/loss/__init__.py +++ b/ppcls/loss/__init__.py @@ -20,6 +20,7 @@ from .distanceloss import DistanceLoss from .distillationloss import DistillationCELoss from .distillationloss import DistillationGTCELoss from .distillationloss import DistillationDMLLoss +from .multilabelloss import MultiLabelLoss class CombinedLoss(nn.Layer): diff --git a/ppcls/loss/multilabelloss.py b/ppcls/loss/multilabelloss.py new file mode 100644 index 000000000..d30d5b8d1 --- /dev/null +++ b/ppcls/loss/multilabelloss.py @@ -0,0 +1,43 @@ +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class MultiLabelLoss(nn.Layer): + """ + Multi-label loss + """ + + def __init__(self, epsilon=None): + super().__init__() + if epsilon is not None and (epsilon <= 0 or epsilon >= 1): + epsilon = None + self.epsilon = epsilon + + def _labelsmoothing(self, target, class_num): + if target.ndim == 1 or target.shape[-1] != class_num: + one_hot_target = F.one_hot(target, class_num) + else: + one_hot_target = target + soft_target = F.label_smooth(one_hot_target, epsilon=self.epsilon) + soft_target = paddle.reshape(soft_target, shape=[-1, class_num]) + return soft_target + + def _binary_crossentropy(self, input, target, class_num): + if self.epsilon is not None: + target = self._labelsmoothing(target, class_num) + cost = F.binary_cross_entropy_with_logits( + logit=input, label=target) + else: + cost = F.binary_cross_entropy_with_logits( + logit=input, label=target) + + return cost + + def forward(self, x, target): + if isinstance(x, dict): + x = x["logits"] + class_num = x.shape[-1] + loss = self._binary_crossentropy(x, target, class_num) + loss = loss.mean() + return {"MultiLabelLoss": loss} diff --git a/ppcls/metric/__init__.py b/ppcls/metric/__init__.py index 4c817a115..94721235b 100644 --- a/ppcls/metric/__init__.py +++ b/ppcls/metric/__init__.py @@ -19,6 +19,8 @@ from collections import OrderedDict from .metrics import TopkAcc, mAP, mINP, Recallk, Precisionk from .metrics import DistillationTopkAcc from .metrics import GoogLeNetTopkAcc +from .metrics import HammingDistance, AccuracyScore + class CombinedMetrics(nn.Layer): def __init__(self, config_list): @@ -32,7 +34,8 @@ class CombinedMetrics(nn.Layer): metric_name = list(config)[0] metric_params = config[metric_name] if metric_params is not None: - self.metric_func_list.append(eval(metric_name)(**metric_params)) + self.metric_func_list.append( + eval(metric_name)(**metric_params)) else: self.metric_func_list.append(eval(metric_name)()) @@ -42,6 +45,7 @@ class CombinedMetrics(nn.Layer): metric_dict.update(metric_func(*args, **kwargs)) return metric_dict + def build_metrics(config): metrics_list = CombinedMetrics(copy.deepcopy(config)) return metrics_list diff --git a/ppcls/metric/metrics.py b/ppcls/metric/metrics.py index 204d2af09..37509eb14 100644 --- a/ppcls/metric/metrics.py +++ b/ppcls/metric/metrics.py @@ -15,6 +15,12 @@ import numpy as np import paddle import paddle.nn as nn +import paddle.nn.functional as F + +from sklearn.metrics import hamming_loss +from sklearn.metrics import accuracy_score as accuracy_metric +from sklearn.metrics import multilabel_confusion_matrix +from sklearn.preprocessing import binarize class TopkAcc(nn.Layer): @@ -198,7 +204,7 @@ class Precisionk(nn.Layer): equal_flag = paddle.logical_and(equal_flag, keep_mask.astype('bool')) equal_flag = paddle.cast(equal_flag, 'float32') - + Ns = paddle.arange(gallery_img_id.shape[0]) + 1 equal_flag_cumsum = paddle.cumsum(equal_flag, axis=1) Precision_at_k = (paddle.mean(equal_flag_cumsum, axis=0) / Ns).numpy() @@ -232,3 +238,71 @@ class GoogLeNetTopkAcc(TopkAcc): def forward(self, x, label): return super().forward(x[0], label) + + +class MutiLabelMetric(object): + def __init__(self): + pass + + def _multi_hot_encode(self, logits, threshold=0.5): + return binarize(logits, threshold=threshold) + + def __call__(self, output): + output = F.sigmoid(output) + preds = self._multi_hot_encode(logits=output.numpy(), threshold=0.5) + return preds + + +class HammingDistance(MutiLabelMetric): + """ + Soft metric based label for multilabel classification + Returns: + The smaller the return value is, the better model is. + """ + + def __init__(self): + super().__init__() + + def __call__(self, output, target): + preds = super().__call__(output) + metric_dict = dict() + metric_dict["HammingDistance"] = paddle.to_tensor( + hamming_loss(target, preds)) + return metric_dict + + +class AccuracyScore(MutiLabelMetric): + """ + Hard metric for multilabel classification + Args: + base: ["sample", "label"], default="sample" + if "sample", return metric score based sample, + if "label", return metric score based label. + Returns: + accuracy: + """ + + def __init__(self, base="label"): + super().__init__() + assert base in ["sample", "label" + ], 'must be one of ["sample", "label"]' + self.base = base + + def __call__(self, output, target): + preds = super().__call__(output) + metric_dict = dict() + if self.base == "sample": + accuracy = accuracy_metric(target, preds) + elif self.base == "label": + mcm = multilabel_confusion_matrix(target, preds) + tns = mcm[:, 0, 0] + fns = mcm[:, 1, 0] + tps = mcm[:, 1, 1] + fps = mcm[:, 0, 1] + accuracy = (sum(tps) + sum(tns)) / ( + sum(tps) + sum(tns) + sum(fns) + sum(fps)) + precision = sum(tps) / (sum(tps) + sum(fps)) + recall = sum(tps) / (sum(tps) + sum(fns)) + F1 = 2 * (accuracy * recall) / (accuracy + recall) + metric_dict["AccuracyScore"] = paddle.to_tensor(accuracy) + return metric_dict diff --git a/tools/train.sh b/tools/train.sh index 5fced8636..083934a57 100755 --- a/tools/train.sh +++ b/tools/train.sh @@ -4,4 +4,4 @@ # python3.7 tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml # for multi-cards train -python3.7 -m paddle.distributed.launch --gpus="0,1,2,3" tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml \ No newline at end of file +python3.7 -m paddle.distributed.launch --gpus="0,1,2,3" tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml diff --git a/train.sh b/train.sh new file mode 100755 index 000000000..47ae2a68e --- /dev/null +++ b/train.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# for single card train +# python3.7 tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml + +# for multi-cards train +python3.7 -m paddle.distributed.launch --gpus="0" tools/train.py -c ./MobileNetV2.yaml From 5992be4adfa8365a9e18992883a46eca8f7c4a8f Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Sun, 26 Sep 2021 07:20:11 +0000 Subject: [PATCH 54/81] add multilabel feature --- .../multilabel/multilabel_en.md | 82 ++++++----- .../quick_start/MobileNetV1_multilabel.yaml | 129 ------------------ train.sh | 7 - 3 files changed, 46 insertions(+), 172 deletions(-) delete mode 100644 ppcls/configs/quick_start/MobileNetV1_multilabel.yaml delete mode 100755 train.sh diff --git a/docs/en/advanced_tutorials/multilabel/multilabel_en.md b/docs/en/advanced_tutorials/multilabel/multilabel_en.md index 29a18c181..eab289084 100644 --- a/docs/en/advanced_tutorials/multilabel/multilabel_en.md +++ b/docs/en/advanced_tutorials/multilabel/multilabel_en.md @@ -25,58 +25,68 @@ tar -xf NUS-SCENE-dataset.tar cd ../../ ``` -## Environment - -### Download pretrained model - -You can use the following commands to download the pretrained model of ResNet50_vd. - -```bash -mkdir pretrained -cd pretrained -wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_pretrained.pdparams -cd ../ -``` - ## Training ```shell -export CUDA_VISIBLE_DEVICES=0 -python -m paddle.distributed.launch \ - --gpus="0" \ +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python3 -m paddle.distributed.launch \ + --gpus="0,1,2,3" \ tools/train.py \ - -c ./configs/quick_start/ResNet50_vd_multilabel.yaml + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml ``` -After training for 10 epochs, the best accuracy over the validation set should be around 0.72. +After training for 10 epochs, the best accuracy over the validation set should be around 0.95. ## Evaluation ```bash python tools/eval.py \ - -c ./configs/quick_start/ResNet50_vd_multilabel.yaml \ - -o pretrained_model="./output/ResNet50_vd/best_model/ppcls" \ - -o load_static_weights=False + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" ``` -The metric of evaluation is based on mAP, which is commonly used in multilabel task to show model perfermance. The mAP over validation set should be around 0.57. - ## Prediction ```bash -python tools/infer/infer.py \ - -i "./dataset/NUS-WIDE-SCENE/NUS-SCENE-dataset/images/0199_434752251.jpg" \ - --model ResNet50_vd \ - --pretrained_model "./output/ResNet50_vd/best_model/ppcls" \ - --use_gpu True \ - --load_static_weights False \ - --multilabel True \ - --class_num 33 +python3 tools/infer.py + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" ``` You will get multiple output such as the following: -``` - class id: 3, probability: 0.6025 - class id: 23, probability: 0.5491 - class id: 32, probability: 0.7006 -``` \ No newline at end of file +``` +[{'class_ids': [6, 13, 17, 23, 26, 30], 'scores': [0.95683, 0.5567, 0.55211, 0.99088, 0.5943, 0.78767], 'file_name': './deploy/images/0517_2715693311.jpg', 'label_names': []}] +``` + +## Prediction based on prediction engine + +### Export model + +```bash +python3 tools/export_model.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" +``` + +The default path of the inference model is under the current path `./inference` + +### Prediction based on prediction engine + +Enter the deploy directory: + +```bash +cd ./deploy +``` + +Prediction based on prediction engine: + +``` +python3 python/predict_cls.py \ + -c configs/inference_multilabel_cls.yaml +``` + +You will get multiple output such as the following: + +``` +0517_2715693311.jpg: class id(s): [6, 13, 17, 23, 26, 30], score(s): [0.96, 0.56, 0.55, 0.99, 0.59, 0.79], label_name(s): [] +``` diff --git a/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml b/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml deleted file mode 100644 index e9c021b65..000000000 --- a/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml +++ /dev/null @@ -1,129 +0,0 @@ -# global configs -Global: - checkpoints: null - pretrained_model: null - output_dir: ./output/ - device: gpu - save_interval: 1 - eval_during_train: True - eval_interval: 1 - epochs: 10 - print_batch_step: 10 - use_visualdl: False - # used for static mode and model export - image_shape: [3, 224, 224] - save_inference_dir: ./inference - use_multilabel: True -# model architecture -Arch: - name: MobileNetV1 - class_num: 33 - pretrained: True - -# loss function config for traing/eval process -Loss: - Train: - - MultiLabelLoss: - weight: 1.0 - Eval: - - MultiLabelLoss: - weight: 1.0 - - -Optimizer: - name: Momentum - momentum: 0.9 - lr: - name: Cosine - learning_rate: 0.1 - regularizer: - name: 'L2' - coeff: 0.00004 - - -# data loader for train and eval -DataLoader: - Train: - dataset: - name: MultiLabelDataset - image_root: ./dataset/NUS-SCENE-dataset/images/ - cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_train_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - RandCropImage: - size: 224 - - RandFlipImage: - flip_code: 1 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - sampler: - name: DistributedBatchSampler - batch_size: 64 - drop_last: False - shuffle: True - loader: - num_workers: 4 - use_shared_memory: True - - Eval: - dataset: - name: MultiLabelDataset - image_root: ./dataset/NUS-SCENE-dataset/images/ - cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_test_list.txt - transform_ops: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - sampler: - name: DistributedBatchSampler - batch_size: 256 - drop_last: False - shuffle: False - loader: - num_workers: 4 - use_shared_memory: True - -Infer: - infer_imgs: dataset/NUS-SCENE-dataset/images/0001_109549716.jpg - batch_size: 10 - transforms: - - DecodeImage: - to_rgb: True - channel_first: False - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - scale: 1.0/255.0 - mean: [0.485, 0.456, 0.406] - std: [0.229, 0.224, 0.225] - order: '' - - ToCHWImage: - PostProcess: - name: MutiLabelTopk - topk: 5 - class_id_map_file: None - -Metric: - Train: - - HammingDistance: - - AccuracyScore: - Eval: - - HammingDistance: - - AccuracyScore: diff --git a/train.sh b/train.sh deleted file mode 100755 index 47ae2a68e..000000000 --- a/train.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -# for single card train -# python3.7 tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml - -# for multi-cards train -python3.7 -m paddle.distributed.launch --gpus="0" tools/train.py -c ./MobileNetV2.yaml From be806121750f1b6a388e8dc7f59934559f5b6864 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sun, 26 Sep 2021 15:51:18 +0800 Subject: [PATCH 55/81] update Inshop config --- ppcls/configs/Products/ResNet50_vd_Inshop.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ppcls/configs/Products/ResNet50_vd_Inshop.yaml b/ppcls/configs/Products/ResNet50_vd_Inshop.yaml index b29a3a3f0..96fab34f1 100644 --- a/ppcls/configs/Products/ResNet50_vd_Inshop.yaml +++ b/ppcls/configs/Products/ResNet50_vd_Inshop.yaml @@ -90,10 +90,10 @@ DataLoader: r1: 0.3 mean: [0., 0., 0.] sampler: - name: DistributedRandomIdentitySampler + name: PKSampler batch_size: 64 - num_instances: 2 - drop_last: False + sample_per_id: 2 + drop_last: True shuffle: True loader: num_workers: 4 From 8595d189454ad153bc62e8e6e6dba8273959b11c Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sun, 26 Sep 2021 16:07:05 +0800 Subject: [PATCH 56/81] update format --- ppcls/data/dataloader/mix_dataset.py | 2 +- ppcls/data/dataloader/mix_sampler.py | 14 +++++++++----- ppcls/data/dataloader/pk_sampler.py | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ppcls/data/dataloader/mix_dataset.py b/ppcls/data/dataloader/mix_dataset.py index c928ca26d..cbf4b4028 100644 --- a/ppcls/data/dataloader/mix_dataset.py +++ b/ppcls/data/dataloader/mix_dataset.py @@ -23,7 +23,7 @@ from .. import dataloader class MixDataset(Dataset): def __init__(self, datasets_config): - super(MixDataset, self).__init__() + super().__init__() self.dataset_list = [] start_idx = 0 end_idx = 0 diff --git a/ppcls/data/dataloader/mix_sampler.py b/ppcls/data/dataloader/mix_sampler.py index d4206be75..2df3109ce 100644 --- a/ppcls/data/dataloader/mix_sampler.py +++ b/ppcls/data/dataloader/mix_sampler.py @@ -24,8 +24,9 @@ from ppcls.data import dataloader class MixSampler(DistributedBatchSampler): def __init__(self, dataset, batch_size, sample_configs, iter_per_epoch): - super(MixSampler, self).__init__(dataset, batch_size) - assert isinstance(dataset, MixDataset), "MixSampler only support MixDataset" + super().__init__(dataset, batch_size) + assert isinstance(dataset, + MixDataset), "MixSampler only support MixDataset" self.sampler_list = [] self.batch_size = batch_size self.start_list = [] @@ -45,9 +46,11 @@ class MixSampler(DistributedBatchSampler): assert batch_size_i <= len(dataset_list[i][2]) config_i["batch_size"] = batch_size_i if sample_method == "DistributedBatchSampler": - sampler_i = DistributedBatchSampler(dataset_list[i][2], **config_i) + sampler_i = DistributedBatchSampler(dataset_list[i][2], + **config_i) else: - sampler_i = getattr(dataloader, sample_method)(dataset_list[i][2], **config_i) + sampler_i = getattr(dataloader, sample_method)( + dataset_list[i][2], **config_i) self.sampler_list.append(sampler_i) self.iter_list.append(iter(sampler_i)) self.length += len(dataset_list[i][2]) * ratio_i @@ -62,7 +65,8 @@ class MixSampler(DistributedBatchSampler): iter_i = iter(self.sampler_list[i]) self.iter_list[i] = iter_i batch_i = next(iter_i, None) - assert batch_i is not None, "dataset {} return None".format(i) + assert batch_i is not None, "dataset {} return None".format( + i) batch += [idx + self.start_list[i] for idx in batch_i] if len(batch) == self.batch_size: self.iter_counter += 1 diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py index ef65be940..7f718a333 100644 --- a/ppcls/data/dataloader/pk_sampler.py +++ b/ppcls/data/dataloader/pk_sampler.py @@ -42,7 +42,7 @@ class PKSampler(DistributedBatchSampler): shuffle=True, drop_last=True, sample_method="sample_avg_prob"): - super(PKSampler, self).__init__( + super().__init__( dataset, batch_size, shuffle=shuffle, drop_last=drop_last) assert batch_size % sample_per_id == 0, \ "PKSampler configs error, Sample_per_id must be a divisor of batch_size." From a98bd7a73c593e7275f8c63e02f98321da16df31 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sun, 26 Sep 2021 16:42:08 +0800 Subject: [PATCH 57/81] update vehicle and logo config --- ppcls/configs/Logo/ResNet50_ReID.yaml | 10 +++++----- ppcls/configs/Vehicle/ResNet50_ReID.yaml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ppcls/configs/Logo/ResNet50_ReID.yaml b/ppcls/configs/Logo/ResNet50_ReID.yaml index 90ec5caad..c91d36272 100644 --- a/ppcls/configs/Logo/ResNet50_ReID.yaml +++ b/ppcls/configs/Logo/ResNet50_ReID.yaml @@ -84,10 +84,10 @@ DataLoader: - RandomErasing: EPSILON: 0.5 sampler: - name: DistributedRandomIdentitySampler + name: PKSampler batch_size: 128 - num_instances: 2 - drop_last: False + sample_per_id: 2 + drop_last: True loader: num_workers: 6 @@ -97,7 +97,7 @@ DataLoader: dataset: name: LogoDataset image_root: "dataset/LogoDet-3K-crop/val/" - cls_label_path: "dataset/LogoDet-3K-crop/LogoDet-3K+query.txt" + cls_label_path: "dataset/LogoDet-3K-crop/LogoDet-3K+val.txt" transform_ops: - DecodeImage: to_rgb: True @@ -122,7 +122,7 @@ DataLoader: dataset: name: LogoDataset image_root: "dataset/LogoDet-3K-crop/train/" - cls_label_path: "dataset/LogoDet-3K-crop/LogoDet-3K+gallery.txt" + cls_label_path: "dataset/LogoDet-3K-crop/LogoDet-3K+train.txt" transform_ops: - DecodeImage: to_rgb: True diff --git a/ppcls/configs/Vehicle/ResNet50_ReID.yaml b/ppcls/configs/Vehicle/ResNet50_ReID.yaml index ffe983966..345d37445 100644 --- a/ppcls/configs/Vehicle/ResNet50_ReID.yaml +++ b/ppcls/configs/Vehicle/ResNet50_ReID.yaml @@ -88,10 +88,10 @@ DataLoader: mean: [0., 0., 0.] sampler: - name: DistributedRandomIdentitySampler + name: PKSampler batch_size: 128 - num_instances: 2 - drop_last: False + sample_per_id: 2 + drop_last: True shuffle: True loader: num_workers: 6 From da25931466b624767ac1b631325ced009ece99a9 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Sun, 26 Sep 2021 17:09:30 +0800 Subject: [PATCH 58/81] update lr --- ppcls/configs/Logo/ResNet50_ReID.yaml | 2 +- ppcls/configs/Products/ResNet50_vd_Inshop.yaml | 2 +- ppcls/configs/Vehicle/ResNet50_ReID.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ppcls/configs/Logo/ResNet50_ReID.yaml b/ppcls/configs/Logo/ResNet50_ReID.yaml index c91d36272..fa52193fd 100644 --- a/ppcls/configs/Logo/ResNet50_ReID.yaml +++ b/ppcls/configs/Logo/ResNet50_ReID.yaml @@ -54,7 +54,7 @@ Optimizer: momentum: 0.9 lr: name: Cosine - learning_rate: 0.01 + learning_rate: 0.04 regularizer: name: 'L2' coeff: 0.0001 diff --git a/ppcls/configs/Products/ResNet50_vd_Inshop.yaml b/ppcls/configs/Products/ResNet50_vd_Inshop.yaml index 96fab34f1..2571ea483 100644 --- a/ppcls/configs/Products/ResNet50_vd_Inshop.yaml +++ b/ppcls/configs/Products/ResNet50_vd_Inshop.yaml @@ -54,7 +54,7 @@ Optimizer: momentum: 0.9 lr: name: MultiStepDecay - learning_rate: 0.01 + learning_rate: 0.04 milestones: [30, 60, 70, 80, 90, 100] gamma: 0.5 verbose: False diff --git a/ppcls/configs/Vehicle/ResNet50_ReID.yaml b/ppcls/configs/Vehicle/ResNet50_ReID.yaml index 345d37445..6aebcbf0d 100644 --- a/ppcls/configs/Vehicle/ResNet50_ReID.yaml +++ b/ppcls/configs/Vehicle/ResNet50_ReID.yaml @@ -53,7 +53,7 @@ Optimizer: momentum: 0.9 lr: name: Cosine - learning_rate: 0.01 + learning_rate: 0.04 regularizer: name: 'L2' coeff: 0.0005 From fe6f614680649ba48d46a3edc8b7e79e9ea25cbb Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Mon, 27 Sep 2021 03:00:37 +0000 Subject: [PATCH 59/81] Update multilabel --- .../quick_start/professional/MobileNetV1_multilabel.yaml | 8 ++++---- ppcls/engine/evaluation/classification.py | 2 +- ppcls/engine/train/train.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml b/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml index 7c9e5a7eb..6838710c3 100644 --- a/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml +++ b/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml @@ -46,8 +46,8 @@ DataLoader: Train: dataset: name: MultiLabelDataset - image_root: ./dataset/NUS-SCENE-dataset/images/ - cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_train_list.txt + image_root: ./dataset/NUS-WIDE-SCENE/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-WIDE-SCENE/NUS-SCENE-dataset/multilabel_train_list.txt transform_ops: - DecodeImage: to_rgb: True @@ -74,8 +74,8 @@ DataLoader: Eval: dataset: name: MultiLabelDataset - image_root: ./dataset/NUS-SCENE-dataset/images/ - cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_test_list.txt + image_root: ./dataset/NUS-WIDE-SCENE/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-WIDE-SCENE/NUS-SCENE-dataset/multilabel_test_list.txt transform_ops: - DecodeImage: to_rgb: True diff --git a/ppcls/engine/evaluation/classification.py b/ppcls/engine/evaluation/classification.py index 9335e3079..d59f2f41c 100644 --- a/ppcls/engine/evaluation/classification.py +++ b/ppcls/engine/evaluation/classification.py @@ -50,7 +50,7 @@ def classification_eval(engine, epoch_id=0): time_info["reader_cost"].update(time.time() - tic) batch_size = batch[0].shape[0] batch[0] = paddle.to_tensor(batch[0]).astype("float32") - if not evaler.config["Global"].get("use_multilabel", False): + if not engine.config["Global"].get("use_multilabel", False): batch[1] = batch[1].reshape([-1, 1]).astype("int64") # image input out = engine.model(batch[0]) diff --git a/ppcls/engine/train/train.py b/ppcls/engine/train/train.py index 7a70eeeb0..347ff3130 100644 --- a/ppcls/engine/train/train.py +++ b/ppcls/engine/train/train.py @@ -76,8 +76,8 @@ def train_epoch(engine, epoch_id, print_batch_step): tic = time.time() -def forward(trainer, batch): - if not trainer.is_rec: - return trainer.model(batch[0]) +def forward(engine, batch): + if not engine.is_rec: + return engine.model(batch[0]) else: - return trainer.model(batch[0], batch[1]) + return engine.model(batch[0], batch[1]) From f0bf51b341e3a3f2e61ec44d9e573aec233a9a09 Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Mon, 27 Sep 2021 06:31:45 +0000 Subject: [PATCH 60/81] update pretrained_model for vehicle,loge model --- docs/en/application/feature_learning_en.md | 9 ++++----- docs/zh_CN/application/feature_learning.md | 8 ++++---- ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml | 2 +- .../configs/slim/ResNet50_vehicle_cls_quantization.yaml | 2 +- ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml | 4 ++-- .../configs/slim/ResNet50_vehicle_reid_quantization.yaml | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/en/application/feature_learning_en.md b/docs/en/application/feature_learning_en.md index c9bd8197f..34e09d862 100644 --- a/docs/en/application/feature_learning_en.md +++ b/docs/en/application/feature_learning_en.md @@ -22,9 +22,8 @@ The feature learning config file description can be found in [yaml description]( The following are the pretrained models trained on different dataset. -- Vehicle Fine-Grained Classification:[CompCars](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_cls_ResNet50_CompCars_v1.1_pretrained.pdparams) -- Vehicle ReID:[VERI-Wild](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.0_pretrained.pdparams) +- Vehicle Fine-Grained Classification:[CompCars](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_cls_ResNet50_CompCars_v1.2_pretrained.pdparams) +- Vehicle ReID:[VERI-Wild](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.1_pretrained.pdparams) - Cartoon Character Recognition:[iCartoon](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/cartoon_rec_ResNet50_iCartoon_v1.0_pretrained.pdparams) -- Logo Recognition:[Logo 3K](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/logo_rec_ResNet50_Logo3K_v1.0_pretrained.pdparams) -- Product Recognition: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.0.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) - +- Logo Recognition:[Logo 3K](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/logo_rec_ResNet50_Logo3K_v1.1_pretrained.pdparams) +- Product Recognition: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.0.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) diff --git a/docs/zh_CN/application/feature_learning.md b/docs/zh_CN/application/feature_learning.md index cc4279e17..a4b959949 100644 --- a/docs/zh_CN/application/feature_learning.md +++ b/docs/zh_CN/application/feature_learning.md @@ -22,8 +22,8 @@ 以下为各应用在不同数据集下的预训练模型 -- 车辆细分类:[CompCars](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_cls_ResNet50_CompCars_v1.1_pretrained.pdparams) -- 车辆ReID:[VERI-Wild](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.0_pretrained.pdparams) +- 车辆细分类:[CompCars](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_cls_ResNet50_CompCars_v1.2_pretrained.pdparams) +- 车辆ReID:[VERI-Wild](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.1_pretrained.pdparams) - 动漫人物识别:[iCartoon](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/cartoon_rec_ResNet50_iCartoon_v1.0_pretrained.pdparams) -- Logo识别:[Logo3K](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/logo_rec_ResNet50_Logo3K_v1.0_pretrained.pdparams) -- 商品识别: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.0.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) +- Logo识别:[Logo3K](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/logo_rec_ResNet50_Logo3K_v1.1_pretrained.pdparams) +- 商品识别: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.0.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml index cd66708fd..5e59e1b6e 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml @@ -1,7 +1,7 @@ # global configs Global: checkpoints: null - pretrained_model: null + pretrained_model: "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_cls_ResNet50_CompCars_v1.2_pretrained.pdparams" output_dir: "./output_vehicle_cls_prune/" device: "gpu" save_interval: 1 diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml index 9e2a42749..1ec73b0cb 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml @@ -1,7 +1,7 @@ # global configs Global: checkpoints: null - pretrained_model: null + pretrained_model: "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_cls_ResNet50_CompCars_v1.2_pretrained.pdparams" output_dir: "./output_vehicle_cls_pact/" device: "gpu" save_interval: 1 diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml index 5d4761c98..f9c86e2a2 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml @@ -1,8 +1,8 @@ # global configs Global: checkpoints: null - pretrained_model: null - output_dir: "./output_fpgm/" + pretrained_model: "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.1_pretrained.pdparams" + output_dir: "./output_vehicle_reid_prune/" device: "gpu" save_interval: 1 eval_during_train: True diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml index 6c70c87a4..aff5228c1 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml @@ -1,7 +1,7 @@ # global configs Global: checkpoints: null - pretrained_model: null + pretrained_model: "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.1_pretrained.pdparams" output_dir: "./output_vehicle_reid_pact/" device: "gpu" save_interval: 1 From ee1bc18f3a0a47a490355c76d46d6756c028ad21 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Mon, 27 Sep 2021 15:31:31 +0800 Subject: [PATCH 61/81] dbg --- ppcls/data/dataloader/pk_sampler.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py index 7f718a333..f02b75f4b 100644 --- a/ppcls/data/dataloader/pk_sampler.py +++ b/ppcls/data/dataloader/pk_sampler.py @@ -68,13 +68,14 @@ class PKSampler(DistributedBatchSampler): logger.error( "PKSampler only support id_avg_prob and sample_avg_prob sample method, " "but receive {}.".format(self.sample_method)) - if sum(np.abs(self.prob_list - 1) > 0.00000001): + diff = np.abs(sum(self.prob_list) - 1) + if diff > 0.00000001: self.prob_list[-1] = 1 - sum(self.prob_list[:-1]) if self.prob_list[-1] > 1 or self.prob_list[-1] < 0: logger.error("PKSampler prob list error") else: logger.info( - "PKSampler: sum of prob list not equal to 1, change the last prob" + "PKSampler: sum of prob list not equal to 1, diff is {}, change the last prob".format(diff) ) def __iter__(self): From 1d7dffd766a8209cddf1984aba144d762be6ce08 Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Mon, 27 Sep 2021 07:48:15 +0000 Subject: [PATCH 62/81] update pretrained_model for product model --- docs/en/application/feature_learning_en.md | 2 +- docs/zh_CN/application/feature_learning.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/application/feature_learning_en.md b/docs/en/application/feature_learning_en.md index 34e09d862..1f9131e90 100644 --- a/docs/en/application/feature_learning_en.md +++ b/docs/en/application/feature_learning_en.md @@ -26,4 +26,4 @@ The following are the pretrained models trained on different dataset. - Vehicle ReID:[VERI-Wild](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.1_pretrained.pdparams) - Cartoon Character Recognition:[iCartoon](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/cartoon_rec_ResNet50_iCartoon_v1.0_pretrained.pdparams) - Logo Recognition:[Logo 3K](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/logo_rec_ResNet50_Logo3K_v1.1_pretrained.pdparams) -- Product Recognition: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.0.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) +- Product Recognition: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.1.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) diff --git a/docs/zh_CN/application/feature_learning.md b/docs/zh_CN/application/feature_learning.md index a4b959949..5d6d292ab 100644 --- a/docs/zh_CN/application/feature_learning.md +++ b/docs/zh_CN/application/feature_learning.md @@ -26,4 +26,4 @@ - 车辆ReID:[VERI-Wild](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/vehicle_reid_ResNet50_VERIWild_v1.1_pretrained.pdparams) - 动漫人物识别:[iCartoon](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/cartoon_rec_ResNet50_iCartoon_v1.0_pretrained.pdparams) - Logo识别:[Logo3K](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/logo_rec_ResNet50_Logo3K_v1.1_pretrained.pdparams) -- 商品识别: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.0.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) +- 商品识别: [Inshop](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Inshop_pretrained_v1.1.pdparams)、[Aliproduct](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/product_ResNet50_vd_Aliproduct_v1.0_pretrained.pdparams) From 41041092b5940f41d983ea3823b9aadce98a0d65 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Mon, 27 Sep 2021 15:52:19 +0800 Subject: [PATCH 63/81] add comment for pk sampler --- ppcls/data/dataloader/pk_sampler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py index f02b75f4b..b3632bc72 100644 --- a/ppcls/data/dataloader/pk_sampler.py +++ b/ppcls/data/dataloader/pk_sampler.py @@ -81,6 +81,7 @@ class PKSampler(DistributedBatchSampler): def __iter__(self): label_per_batch = self.batch_size // self.sample_per_label if self.shuffle: + # It's not accurate literally, but it helps in some dataset. np.random.RandomState(self.epoch).shuffle(self.label_list) for i in range(len(self)): batch_index = [] From 7bd1638212be65f5cbcd37281498e126d789f665 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Tue, 28 Sep 2021 08:41:49 +0000 Subject: [PATCH 64/81] Add lite detection&recognition inference models --- .../tutorials/quick_start_recognition.md | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/zh_CN/tutorials/quick_start_recognition.md b/docs/zh_CN/tutorials/quick_start_recognition.md index 19b8fed91..92f64f25b 100644 --- a/docs/zh_CN/tutorials/quick_start_recognition.md +++ b/docs/zh_CN/tutorials/quick_start_recognition.md @@ -34,6 +34,8 @@ 检测模型与4个方向(Logo、动漫人物、车辆、商品)的识别inference模型、测试数据下载地址以及对应的配置文件地址如下。 +服务器端通用主体检测模型与各方向识别模型: + | 模型简介 | 推荐场景 | inference模型 | 预测配置文件 | 构建索引库的配置文件 | | ------------ | ------------- | -------- | ------- | -------- | | 通用主体检测模型 | 通用场景 |[模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar) | - | - | @@ -43,6 +45,12 @@ | 商品识别模型 | 商品场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/product_ResNet50_vd_aliproduct_v1.0_infer.tar) | [inference_product.yaml](../../../deploy/configs/inference_product.yaml) | [build_product.yaml](../../../deploy/configs/build_product.yaml) | | 车辆ReID模型 | 车辆ReID场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/vehicle_reid_ResNet50_VERIWild_v1.0_infer.tar) | - | - | +轻量级通用主体检测模型与轻量级通用识别模型: + +| 模型简介 | 推荐场景 | inference模型 | 预测配置文件 | 构建索引库的配置文件 | +| ------------ | ------------- | -------- | ------- | -------- | +| 轻量级通用主体检测模型 | 通用场景 |[模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar) | - | - | +| 轻量级通用识别模型 | 通用场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar) | [inference_product.yaml](../../../deploy/configs/inference_product.yaml) | [build_product.yaml](../../../deploy/configs/build_product.yaml) | 本章节demo数据下载地址如下: [数据下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognition_demo_data_v1.1.tar)。 @@ -51,6 +59,7 @@ 1. windows 环境下如果没有安装wget,可以按照下面的步骤安装wget与tar命令,也可以在,下载模型时将链接复制到浏览器中下载,并解压放置在相应目录下;linux或者macOS用户可以右键点击,然后复制下载链接,即可通过`wget`命令下载。 2. 如果macOS环境下没有安装`wget`命令,可以运行下面的命令进行安装。 + ```shell # 安装 homebrew ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; @@ -124,6 +133,13 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognit │ └── inference.pdmodel ``` +**注意** +如果使用轻量级通用识别模型,Demo数据需要重新提取特征、够建索引,方式如下: + +```shell +python3.7 python/build_gallery.py -c configs/build_product.yaml -o Global.rec_inference_model_dir=./models/general_PPLCNet_x2_5_lite_v1.0_infer +``` + ### 2.2 商品识别与检索 @@ -244,12 +260,12 @@ cp recognition_demo_data_v1.1/gallery_product/data_file.txt recognition_demo_dat 然后在文件`recognition_demo_data_v1.1/gallery_product/data_file_update.txt`中添加以下的信息, ``` -gallery/anmuxi/001.jpg 安慕希酸奶 -gallery/anmuxi/002.jpg 安慕希酸奶 -gallery/anmuxi/003.jpg 安慕希酸奶 -gallery/anmuxi/004.jpg 安慕希酸奶 -gallery/anmuxi/005.jpg 安慕希酸奶 -gallery/anmuxi/006.jpg 安慕希酸奶 +gallery/anmuxi/001.jpg 安慕希酸奶 +gallery/anmuxi/002.jpg 安慕希酸奶 +gallery/anmuxi/003.jpg 安慕希酸奶 +gallery/anmuxi/004.jpg 安慕希酸奶 +gallery/anmuxi/005.jpg 安慕希酸奶 +gallery/anmuxi/006.jpg 安慕希酸奶 ``` 每一行的文本中,第一个字段表示图像的相对路径,第二个字段表示图像对应的标签信息,中间用`tab`键分隔开(注意:有些编辑器会将`tab`自动转换为`空格`,这种情况下会导致文件解析报错)。 From 5b5e21fe7197bf05a91981a8c49a3a66440fec87 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Tue, 28 Sep 2021 08:49:29 +0000 Subject: [PATCH 65/81] Update quick_start_recognition.md --- docs/zh_CN/tutorials/quick_start_recognition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh_CN/tutorials/quick_start_recognition.md b/docs/zh_CN/tutorials/quick_start_recognition.md index 92f64f25b..f11bef49c 100644 --- a/docs/zh_CN/tutorials/quick_start_recognition.md +++ b/docs/zh_CN/tutorials/quick_start_recognition.md @@ -58,7 +58,7 @@ 1. windows 环境下如果没有安装wget,可以按照下面的步骤安装wget与tar命令,也可以在,下载模型时将链接复制到浏览器中下载,并解压放置在相应目录下;linux或者macOS用户可以右键点击,然后复制下载链接,即可通过`wget`命令下载。 2. 如果macOS环境下没有安装`wget`命令,可以运行下面的命令进行安装。 - +3. 轻量级通用识别模型的预测配置文件和构建索引的配置文件目前使用的是服务器端商品识别模型的配置,您可以自行修改模型的路径完成相应的索引构建和识别预测。 ```shell # 安装 homebrew From 6f68cbefdb2b39964f2dc7c5b4de46d99248e423 Mon Sep 17 00:00:00 2001 From: Bin Lu Date: Tue, 28 Sep 2021 17:39:21 +0800 Subject: [PATCH 66/81] Update README_CN.md --- deploy/paddleserving/recognition/README_CN.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/paddleserving/recognition/README_CN.md b/deploy/paddleserving/recognition/README_CN.md index 58efd6ba7..db208dab9 100644 --- a/deploy/paddleserving/recognition/README_CN.md +++ b/deploy/paddleserving/recognition/README_CN.md @@ -119,6 +119,8 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognit ## Paddle Serving pipeline部署 +**注意** pipeline部署方式暂不支持windows平台 + 1. 下载PaddleClas代码,若已下载可跳过此步骤 ``` git clone https://github.com/PaddlePaddle/PaddleClas From 4aa4a6e699a0cf0a9a4dd2238a475b998a62d100 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Tue, 28 Sep 2021 09:39:29 +0000 Subject: [PATCH 67/81] Update quick_start_recognition.md --- docs/en/tutorials/quick_start_recognition_en.md | 13 +++++++++++++ docs/zh_CN/tutorials/quick_start_recognition.md | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/en/tutorials/quick_start_recognition_en.md b/docs/en/tutorials/quick_start_recognition_en.md index 4d82cd2af..32ac9d3a3 100644 --- a/docs/en/tutorials/quick_start_recognition_en.md +++ b/docs/en/tutorials/quick_start_recognition_en.md @@ -43,6 +43,11 @@ The detection model with the recognition inference model for the 4 directions (L | Product Recignition Model | Product Scenario | [Model Download Link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/product_ResNet50_vd_aliproduct_v1.0_infer.tar) | [inference_product.yaml](../../../deploy/configs/inference_product.yaml) | [build_product.yaml](../../../deploy/configs/build_product.yaml) | | Vehicle ReID Model | Vehicle ReID Scenario | [Model Download Link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/vehicle_reid_ResNet50_VERIWild_v1.0_infer.tar) | - | - | +| Models Introduction | Recommended Scenarios | inference Model | Predict Config File | Config File to Build Index Database | +| ------------ | ------------- | -------- | ------- | -------- | +| Lightweight generic mainbody detection model | General Scenarios |[Model Download Link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar) | - | - | +| Lightweight generic recognition model | General Scenarios | [Model Download Link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar) | [inference_product.yaml](../../../deploy/configs/inference_product.yaml) | [build_product.yaml](../../../deploy/configs/build_product.yaml) | + Demo data in this tutorial can be downloaded here: [download link](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognition_demo_data_en_v1.1.tar). @@ -50,6 +55,7 @@ Demo data in this tutorial can be downloaded here: [download link](https://paddl **Attention** 1. If you do not have wget installed on Windows, you can download the model by copying the link into your browser and unzipping it in the appropriate folder; for Linux or macOS users, you can right-click and copy the download link to download it via the `wget` command. 2. If you want to install `wget` on macOS, you can run the following command. +3. The predict config file of the lightweight generic recognition model and the config file to build index database are used for the config of product recognition model of server-side. You can modify the path of the model to complete the index building and prediction. ```shell # install homebrew @@ -123,6 +129,13 @@ The `models` folder should have the following file structure. │ └── inference.pdmodel ``` +**Attention** +If you want to use the lightweight generic recognition model, you need to re-extract the features of the demo data and re-build the index. The way is as follows: + +```shell +python3.7 python/build_gallery.py -c configs/build_product.yaml -o Global.rec_inference_model_dir=./models/general_PPLCNet_x2_5_lite_v1.0_infer +``` + ### 2.2 Product Recognition and Retrieval diff --git a/docs/zh_CN/tutorials/quick_start_recognition.md b/docs/zh_CN/tutorials/quick_start_recognition.md index f11bef49c..4a7bf5f96 100644 --- a/docs/zh_CN/tutorials/quick_start_recognition.md +++ b/docs/zh_CN/tutorials/quick_start_recognition.md @@ -260,12 +260,12 @@ cp recognition_demo_data_v1.1/gallery_product/data_file.txt recognition_demo_dat 然后在文件`recognition_demo_data_v1.1/gallery_product/data_file_update.txt`中添加以下的信息, ``` -gallery/anmuxi/001.jpg 安慕希酸奶 -gallery/anmuxi/002.jpg 安慕希酸奶 -gallery/anmuxi/003.jpg 安慕希酸奶 -gallery/anmuxi/004.jpg 安慕希酸奶 -gallery/anmuxi/005.jpg 安慕希酸奶 -gallery/anmuxi/006.jpg 安慕希酸奶 +gallery/anmuxi/001.jpg 安慕希酸奶 +gallery/anmuxi/002.jpg 安慕希酸奶 +gallery/anmuxi/003.jpg 安慕希酸奶 +gallery/anmuxi/004.jpg 安慕希酸奶 +gallery/anmuxi/005.jpg 安慕希酸奶 +gallery/anmuxi/006.jpg 安慕希酸奶 ``` 每一行的文本中,第一个字段表示图像的相对路径,第二个字段表示图像对应的标签信息,中间用`tab`键分隔开(注意:有些编辑器会将`tab`自动转换为`空格`,这种情况下会导致文件解析报错)。 From 938c7a9679f96c1fdbc17d0f420d502fbaf6fa1f Mon Sep 17 00:00:00 2001 From: Bin Lu Date: Tue, 28 Sep 2021 17:40:02 +0800 Subject: [PATCH 68/81] Update README_CN.md --- deploy/paddleserving/recognition/README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/paddleserving/recognition/README_CN.md b/deploy/paddleserving/recognition/README_CN.md index db208dab9..b8b8128a0 100644 --- a/deploy/paddleserving/recognition/README_CN.md +++ b/deploy/paddleserving/recognition/README_CN.md @@ -119,7 +119,7 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognit ## Paddle Serving pipeline部署 -**注意** pipeline部署方式暂不支持windows平台 +**注意:** pipeline部署方式不支持windows平台 1. 下载PaddleClas代码,若已下载可跳过此步骤 ``` From db5f4da81be0e85b700b6b47b607ef700237cfb3 Mon Sep 17 00:00:00 2001 From: Bin Lu Date: Tue, 28 Sep 2021 17:41:15 +0800 Subject: [PATCH 69/81] Update README.md --- deploy/paddleserving/recognition/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/paddleserving/recognition/README.md b/deploy/paddleserving/recognition/README.md index 0ece4fbd4..005e41816 100644 --- a/deploy/paddleserving/recognition/README.md +++ b/deploy/paddleserving/recognition/README.md @@ -121,6 +121,8 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognit ## Paddle Serving pipeline deployment +**Attention:** pipeline deployment mode does not support Windows platform + 1. Download the PaddleClas code, if you have already downloaded it, you can skip this step. ``` git clone https://github.com/PaddlePaddle/PaddleClas From c4d54dd0e055cf4090929cd4c63c0dc9d93776ca Mon Sep 17 00:00:00 2001 From: weishengyu Date: Wed, 29 Sep 2021 10:59:16 +0800 Subject: [PATCH 70/81] remove trick code --- ppcls/data/dataloader/pk_sampler.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ppcls/data/dataloader/pk_sampler.py b/ppcls/data/dataloader/pk_sampler.py index b3632bc72..bf563a6c1 100644 --- a/ppcls/data/dataloader/pk_sampler.py +++ b/ppcls/data/dataloader/pk_sampler.py @@ -80,10 +80,7 @@ class PKSampler(DistributedBatchSampler): def __iter__(self): label_per_batch = self.batch_size // self.sample_per_label - if self.shuffle: - # It's not accurate literally, but it helps in some dataset. - np.random.RandomState(self.epoch).shuffle(self.label_list) - for i in range(len(self)): + for _ in range(len(self)): batch_index = [] batch_label_list = np.random.choice( self.label_list, From 2f1892855b47923359bfbadf999ac8dd04fe72b5 Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Wed, 29 Sep 2021 04:26:51 +0000 Subject: [PATCH 71/81] kl_quant for whole_chain and add readme --- tests/README.md | 70 +++++++++++++++++++++++++ tests/config/MobileNetV3_large_x1_0.txt | 2 +- tests/config/ResNet50_vd.txt | 2 +- tests/prepare.sh | 1 + tests/test.sh | 27 ++++++---- 5 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..55b3690b1 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,70 @@ + +# 从训练到推理部署工具链测试方法介绍 + +test.sh和config文件夹下的txt文件配合使用,完成Clas模型从训练到预测的流程测试。 + +# 安装依赖 +- 安装PaddlePaddle >= 2.0 +- 安装PaddleClass依赖 + ``` + pip3 install -r ../requirements.txt + ``` +- 安装autolog + ``` + git clone https://github.com/LDOUBLEV/AutoLog + cd AutoLog + pip3 install -r requirements.txt + python3 setup.py bdist_wheel + pip3 install ./dist/auto_log-1.0.0-py3-none-any.whl + cd ../ + ``` + +# 目录介绍 + +```bash +tests/ +├── config # 测试模型的参数配置文件 +| |--- *.txt +└── prepare.sh # 完成test.sh运行所需要的数据和模型下载 +└── test.sh # 测试主程序 +``` + +# 使用方法 + +test.sh包四种运行模式,每种模式的运行数据不同,分别用于测试速度和精度,分别是: + +- 模式1:lite_train_infer,使用少量数据训练,用于快速验证训练到预测的走通流程,不验证精度和速度; +```shell +bash tests/prepare.sh ./tests/config/ResNet50_vd.txt 'lite_train_infer' +bash tests/test.sh ./tests/config/ResNet50_vd.txt 'lite_train_infer' +``` + +- 模式2:whole_infer,使用少量数据训练,一定量数据预测,用于验证训练后的模型执行预测,预测速度是否合理; +```shell +bash tests/prepare.sh ./tests/config/ResNet50_vd.txt 'whole_infer' +bash tests/test.sh ./tests/config/ResNet50_vd.txt 'whole_infer' +``` + +- 模式3:infer 不训练,全量数据预测,走通开源模型评估、动转静,检查inference model预测时间和精度; +```shell +bash tests/prepare.sh ./tests/config/ResNet50_vd.txt 'infer' +# 用法1: +bash tests/test.sh ./tests/config/ResNet50_vd.txt 'infer' +``` + +需注意的是,模型的离线量化需使用`infer`模式进行测试 + +- 模式4:whole_train_infer , CE: 全量数据训练,全量数据预测,验证模型训练精度,预测精度,预测速度; +```shell +bash tests/prepare.sh ./tests/config/ResNet50_vd.txt 'whole_train_infer' +bash tests/test.sh ./tests/config/ResNet50_vd.txt 'whole_train_infer' +``` + +- 模式5:cpp_infer , CE: 验证inference model的c++预测是否走通; +```shell +bash tests/prepare.sh ./tests/config/ResNet50_vd.txt 'cpp_infer' +bash tests/test.sh ./tests/config/ResNet50_vd.txt 'cpp_infer' +``` + +# 日志输出 +最终在```tests/output```目录下生成.log后缀的日志文件 diff --git a/tests/config/MobileNetV3_large_x1_0.txt b/tests/config/MobileNetV3_large_x1_0.txt index 0afd21490..f5add2e9a 100644 --- a/tests/config/MobileNetV3_large_x1_0.txt +++ b/tests/config/MobileNetV3_large_x1_0.txt @@ -35,7 +35,7 @@ export1:null export2:null inference_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/whole_chain/MobileNetV3_large_x1_0_inference.tar infer_model:../inference/ -infer_export:null +kl_quant:deploy/slim/quant_post_static.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x1_0.yaml -o Global.save_inference_dir=./inference infer_quant:Fasle inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False diff --git a/tests/config/ResNet50_vd.txt b/tests/config/ResNet50_vd.txt index 445609bf8..aa7f9ac10 100644 --- a/tests/config/ResNet50_vd.txt +++ b/tests/config/ResNet50_vd.txt @@ -35,7 +35,7 @@ export1:null export2:null infer_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/whole_chain/ResNet50_vd_inference.tar infer_model:../inference/ -infer_export:null +kl_quant:deploy/slim/quant_post_static.py -c ppcls/configs/ImageNet/ResNet/ResNet50_vd.yaml -o Global.save_inference_dir=./inference infer_quant:Fasle inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False diff --git a/tests/prepare.sh b/tests/prepare.sh index 35782fd01..6f9701a10 100644 --- a/tests/prepare.sh +++ b/tests/prepare.sh @@ -42,6 +42,7 @@ elif [ ${MODE} = "infer" ] || [ ${MODE} = "cpp_infer" ];then ln -s whole_chain_infer ILSVRC2012 cd ILSVRC2012 mv val.txt val_list.txt + ln -s val_list.txt train_list.txt cd ../../ # download inference model eval "wget -nc $inference_model_url" diff --git a/tests/test.sh b/tests/test.sh index b573bf483..b36991264 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -299,17 +299,6 @@ if [ ${MODE} = "infer" ]; then infer_quant_flag=(${infer_is_quant}) cd deploy for infer_model in ${infer_model_dir_list[*]}; do - # run export - if [ ${infer_run_exports[Count]} != "null" ];then - set_export_weight=$(func_set_params "${export_weight}" "${infer_model}") - set_save_infer_key=$(func_set_params "${save_infer_key}" "${infer_model}") - export_cmd="${python} ${norm_export} ${set_export_weight} ${set_save_infer_key}" - eval $export_cmd - status_export=$? - if [ ${status_export} = 0 ];then - status_check $status_export "${export_cmd}" "../${status_log}" - fi - fi #run inference is_quant=${infer_quant_flag[Count]} echo "is_quant: ${is_quant}" @@ -317,6 +306,22 @@ if [ ${MODE} = "infer" ]; then Count=$(($Count + 1)) done cd .. + + # for kl_quant + echo "kl_quant" + if [ ${infer_run_exports} ]; then + command="${python} ${infer_run_exports}" + eval $command + last_status=${PIPESTATUS[0]} + status_check $last_status "${command}" "${status_log}" + cd inference/quant_post_static_model + ln -s __model__ inference.pdmodel + ln -s __params__ inference.pdiparams + cd ../../deploy + is_quant=True + func_inference "${python}" "${inference_py}" "${infer_model}/quant_post_static_model" "../${LOG_PATH}" "${infer_img_dir}" ${is_quant} + cd .. + fi elif [ ${MODE} = "cpp_infer" ]; then cd deploy func_cpp_inference "./cpp/build/clas_system" "../${LOG_PATH}" "${infer_img_dir}" From 0dccfb917d7bda3e78bcaa59dc8459eb14cba003 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Thu, 30 Sep 2021 06:49:41 +0000 Subject: [PATCH 72/81] fix: interpolation maybe be 0 --- deploy/python/preprocess.py | 2 +- ppcls/data/preprocess/ops/operators.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/python/preprocess.py b/deploy/python/preprocess.py index 5d7fc9296..1da32ad6e 100644 --- a/deploy/python/preprocess.py +++ b/deploy/python/preprocess.py @@ -79,7 +79,7 @@ class UnifiedResize(object): if isinstance(interpolation, str): interpolation = _cv2_interp_from_str[interpolation.lower()] # compatible with opencv < version 4.4.0 - elif not interpolation: + elif interpolation is None: interpolation = cv2.INTER_LINEAR self.resize_func = partial(cv2.resize, interpolation=interpolation) elif backend.lower() == "pil": diff --git a/ppcls/data/preprocess/ops/operators.py b/ppcls/data/preprocess/ops/operators.py index e46823d2a..e551fb768 100644 --- a/ppcls/data/preprocess/ops/operators.py +++ b/ppcls/data/preprocess/ops/operators.py @@ -60,7 +60,7 @@ class UnifiedResize(object): if isinstance(interpolation, str): interpolation = _cv2_interp_from_str[interpolation.lower()] # compatible with opencv < version 4.4.0 - elif not interpolation: + elif interpolation is None: interpolation = cv2.INTER_LINEAR self.resize_func = partial(cv2.resize, interpolation=interpolation) elif backend.lower() == "pil": From c7aeec28e248a7c6b08aa2553d5f18616bf50659 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Thu, 30 Sep 2021 06:52:15 +0000 Subject: [PATCH 73/81] fix: support static graph --- ppcls/optimizer/__init__.py | 3 ++- ppcls/optimizer/optimizer.py | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/ppcls/optimizer/__init__.py b/ppcls/optimizer/__init__.py index cc64a9caa..61db39f89 100644 --- a/ppcls/optimizer/__init__.py +++ b/ppcls/optimizer/__init__.py @@ -41,7 +41,8 @@ def build_lr_scheduler(lr_config, epochs, step_each_epoch): return lr -def build_optimizer(config, epochs, step_each_epoch, model_list): +# model_list is None in static graph +def build_optimizer(config, epochs, step_each_epoch, model_list=None): config = copy.deepcopy(config) # step1 build lr lr = build_lr_scheduler(config.pop('lr'), epochs, step_each_epoch) diff --git a/ppcls/optimizer/optimizer.py b/ppcls/optimizer/optimizer.py index 72310f27b..eb6e4f4ab 100644 --- a/ppcls/optimizer/optimizer.py +++ b/ppcls/optimizer/optimizer.py @@ -43,7 +43,9 @@ class Momentum(object): self.multi_precision = multi_precision def __call__(self, model_list): - parameters = sum([m.parameters() for m in model_list], []) + # model_list is None in static graph + parameters = sum([m.parameters() for m in model_list], + []) if model_list else None opt = optim.Momentum( learning_rate=self.learning_rate, momentum=self.momentum, @@ -79,7 +81,9 @@ class Adam(object): self.multi_precision = multi_precision def __call__(self, model_list): - parameters = sum([m.parameters() for m in model_list], []) + # model_list is None in static graph + parameters = sum([m.parameters() for m in model_list], + []) if model_list else None opt = optim.Adam( learning_rate=self.learning_rate, beta1=self.beta1, @@ -123,7 +127,9 @@ class RMSProp(object): self.grad_clip = grad_clip def __call__(self, model_list): - parameters = sum([m.parameters() for m in model_list], []) + # model_list is None in static graph + parameters = sum([m.parameters() for m in model_list], + []) if model_list else None opt = optim.RMSProp( learning_rate=self.learning_rate, momentum=self.momentum, @@ -160,18 +166,21 @@ class AdamW(object): self.one_dim_param_no_weight_decay = one_dim_param_no_weight_decay def __call__(self, model_list): - parameters = sum([m.parameters() for m in model_list], []) + # model_list is None in static graph + parameters = sum([m.parameters() for m in model_list], + []) if model_list else None + # TODO(gaotingquan): model_list is None when in static graph, "no_weight_decay" not work. self.no_weight_decay_param_name_list = [ p.name for model in model_list for n, p in model.named_parameters() if any(nd in n for nd in self.no_weight_decay_name_list) - ] + ] if model_list else [] if self.one_dim_param_no_weight_decay: self.no_weight_decay_param_name_list += [ p.name for model in model_list for n, p in model.named_parameters() if len(p.shape) == 1 - ] + ] if model_list else [] opt = optim.AdamW( learning_rate=self.learning_rate, From 7dcb2d4fd0fab1cfb021fc25bd868976100e5559 Mon Sep 17 00:00:00 2001 From: gaotingquan Date: Thu, 30 Sep 2021 10:48:23 +0000 Subject: [PATCH 74/81] fix: raise exception raise exception about using no_weight_decay of AdamW in static graph --- ppcls/optimizer/optimizer.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ppcls/optimizer/optimizer.py b/ppcls/optimizer/optimizer.py index eb6e4f4ab..f429755fc 100644 --- a/ppcls/optimizer/optimizer.py +++ b/ppcls/optimizer/optimizer.py @@ -18,6 +18,8 @@ from __future__ import print_function from paddle import optimizer as optim +from ppcls.utils import logger + class Momentum(object): """ @@ -171,6 +173,13 @@ class AdamW(object): []) if model_list else None # TODO(gaotingquan): model_list is None when in static graph, "no_weight_decay" not work. + if model_list is None: + if self.one_dim_param_no_weight_decay or len( + self.no_weight_decay_name_list) != 0: + msg = "\"AdamW\" does not support setting \"no_weight_decay\" in static graph. Please use dynamic graph." + logger.error(Exception(msg)) + raise Exception(msg) + self.no_weight_decay_param_name_list = [ p.name for model in model_list for n, p in model.named_parameters() if any(nd in n for nd in self.no_weight_decay_name_list) From 27be97d557be0269590747244f7d6a74fcf393b3 Mon Sep 17 00:00:00 2001 From: lilithzhou <80816848+lilith-zy@users.noreply.github.com> Date: Fri, 8 Oct 2021 15:15:47 +0800 Subject: [PATCH 75/81] Develop (#1285) * Update wx_group.png * Update wx_group.png --- docs/images/wx_group.png | Bin 206022 -> 61360 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/wx_group.png b/docs/images/wx_group.png index 94f87f6f0b6178c5d491cbc0ccd2d0efb6702e61..d4d46ed117eb76859d21fe342f934b4916e3fe4e 100644 GIT binary patch literal 61360 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

  • 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�y}7wn`u4(=kKg{fpd~ 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�EI+qx3}>)FMCdH`2}tlftRP*WA3Lm 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 Date: Sat, 9 Oct 2021 14:43:33 +0800 Subject: [PATCH 76/81] add mainbody doc of lite (#1283) * add mainbody doc of lite * Update mainbody_detection.md * fix typo --- .../mainbody_detection.md | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 docs/zh_CN_tmp/image_recognition_pipeline/mainbody_detection.md diff --git a/docs/zh_CN_tmp/image_recognition_pipeline/mainbody_detection.md b/docs/zh_CN_tmp/image_recognition_pipeline/mainbody_detection.md new file mode 100644 index 000000000..8431edb14 --- /dev/null +++ b/docs/zh_CN_tmp/image_recognition_pipeline/mainbody_detection.md @@ -0,0 +1,218 @@ +# 主体检测 + + +主体检测技术是目前应用非常广泛的一种检测技术,它指的是检测出图片中一个或者多个主体的坐标位置,然后将图像中的对应区域裁剪下来,进行识别,从而完成整个识别过程。主体检测是识别任务的前序步骤,可以有效提升识别精度。 + +本部分主要从数据集、模型选择和模型训练 3 个方面对该部分内容进行介绍。 + +## 1. 数据集 + +在 PaddleClas 的识别任务中,训练主体检测模型时主要用到了以下几个数据集。 + +| 数据集 | 数据量 | 主体检测任务中使用的数据量 | 场景 | 数据集地址 | +| :------------: | :-------------: | :-------: | :-------: | :--------: | +| Objects365 | 170W | 6k | 通用场景 | [地址](https://www.objects365.org/overview.html) | +| COCO2017 | 12W | 5k | 通用场景 | [地址](https://cocodataset.org/) | +| iCartoonFace | 2k | 2k | 动漫人脸检测 | [地址](https://github.com/luxiangju-PersonAI/iCartoonFace) | +| LogoDet-3k | 3k | 2k | Logo检测 | [地址](https://github.com/Wangjing1551/LogoDet-3K-Dataset) | +| RPC | 3k | 3k | 商品检测 | [地址](https://rpc-dataset.github.io/) | + +在实际训练的过程中,将所有数据集混合在一起。由于是主体检测,这里将所有标注出的检测框对应的类别都修改为 `前景` 的类别,最终融合的数据集中只包含 1 个类别,即前景。 + + +## 2. 模型选择 + +目标检测方法种类繁多,比较常用的有两阶段检测器(如FasterRCNN系列等);单阶段检测器(如YOLO、SSD等);anchor-free检测器(如PicoDet、FCOS等)。PaddleDetection中针对服务端使用场景,自研了 PP-YOLO 系列模型;针对端侧(CPU和移动端等)使用场景,自研了 PicoDet 系列模型,在服务端和端侧均处于业界较为领先的水平。 + +基于上述研究,PaddleClas 中提供了 2 个通用主体检测模型,为轻量级与服务端主体检测模型,分别适用于端侧场景以及服务端场景。下面的表格中给出了在上述 5 个数据集上的平均 mAP 以及它们的模型大小、预测速度对比信息。 + +| 模型 | 模型结构 | 预训练模型下载地址 | inference模型下载地址 | mAP | inference模型大小(MB) | 单张图片预测耗时(不包含预处理)(ms) | +| :------------: | :-------------: | :------: | :-------: | :--------: | :-------: | :--------: | +| 轻量级主体检测模型 | PicoDet | [地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_pretrained.pdparams) | [地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar) | 40.1% | 30.1 | 29.8 | +| 服务端主体检测模型 | PP-YOLOv2 | [地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/ppyolov2_r50vd_dcn_mainbody_v1.0_pretrained.pdparams) | [地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar) | 42.5% | 210.5 | 466.6 | + + +* 注意 + * 速度评测机器的CPU具体信息为:`Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz`,速度指标为开启 mkldnn ,线程数设置为 10 测试得到。 + * 主体检测的预处理过程较为耗时,平均每张图在上述机器上的时间在 40~55 ms 左右,没有包含在上述的预测耗时统计中。 + + +### 2.1 轻量级主体检测模型 + +PicoDet 由 [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) 提出,是一个适用于CPU或者移动端场景的目标检测算法。具体地,它融合了下面一系列优化算法。 + +- [ATSS](https://arxiv.org/abs/1912.02424) +- [Generalized Focal Loss](https://arxiv.org/abs/2006.04388) +- 余弦学习率策略 +- Cycle-EMA +- 轻量级检测 head + + +更多关于 PicoDet 的优化细节与 benchmark 可以参考 [PicoDet 系列模型介绍](https://github.com/PaddlePaddle/PaddleDetection/blob/develop/configs/picodet/README.md)。 + +在轻量级主体检测任务中,为了更好地兼顾检测速度与效果,我们使用 PPLCNet_x2_5 作为主体检测模型的骨干网络,同时将训练与预测的图像尺度修改为了 640x640,其余配置与 [picodet_m_shufflenetv2_416_coco.yml](https://github.com/PaddlePaddle/PaddleDetection/blob/develop/configs/picodet/picodet_m_shufflenetv2_416_coco.yml)完全一致。将数据集更换为自定义的主体检测数据集,进行训练,最终得到检测模型。 + + +### 2.2 服务端主体检测模型 + +PP-YOLO 由 [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) 提出,从骨干网络、数据增广、正则化策略、损失函数、后处理等多个角度对 yolov3 模型进行深度优化,最终在"速度-精度"方面达到了业界领先的水平。具体地,优化的策略如下。 + +- 更优的骨干网络: ResNet50vd-DCN +- 更大的训练batch size: 8 GPUs,每GPU batch_size=24,对应调整学习率和迭代轮数 +- [Drop Block](https://arxiv.org/abs/1810.12890) +- [Exponential Moving Average](https://www.investopedia.com/terms/e/ema.asp) +- [IoU Loss](https://arxiv.org/pdf/1902.09630.pdf) +- [Grid Sensitive](https://arxiv.org/abs/2004.10934) +- [Matrix NMS](https://arxiv.org/pdf/2003.10152.pdf) +- [CoordConv](https://arxiv.org/abs/1807.03247) +- [Spatial Pyramid Pooling](https://arxiv.org/abs/1406.4729) +- 更优的预训练模型 + +更多关于 PP-YOLO 的详细介绍可以参考:[PP-YOLO 模型](https://github.com/PaddlePaddle/PaddleDetection/blob/release%2F2.1/configs/ppyolo/README_cn.md)。 + +在服务端主体检测任务中,为了保证检测效果,我们使用 ResNet50vd-DCN 作为检测模型的骨干网络,使用配置文件 [ppyolov2_r50vd_dcn_365e_coco.yml](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml) ,更换为自定义的主体检测数据集,进行训练,最终得到检测模型。 + + +## 3. 模型训练 + +本节主要介绍怎样基于PaddleDetection,基于自己的数据集,训练主体检测模型。 + +### 3.1 环境准备 + +下载PaddleDetection代码,安装requirements。 + +```shell +cd +git clone https://github.com/PaddlePaddle/PaddleDetection.git + +cd PaddleDetection +# 安装其他依赖 +pip install -r requirements.txt +``` + +更多安装教程,请参考: [安装文档](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/docs/tutorials/INSTALL_cn.md) + +### 3.2 数据准备 + +对于自定义数据集,首先需要将自己的数据集修改为COCO格式,可以参考[自定义检测数据集教程](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/static/docs/tutorials/Custom_DataSet.md)制作COCO格式的数据集。 + +主体检测任务中,所有的检测框均属于前景,在这里需要将标注文件中,检测框的`category_id`修改为1,同时将整个标注文件中的`categories`映射表修改为下面的格式,即整个类别映射表中只包含`前景`类别。 + +```json +[{u'id': 1, u'name': u'foreground', u'supercategory': u'foreground'}] +``` + +### 3.3 配置文件改动和说明 + +我们使用 `configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml` 配置进行训练,配置文件摘要如下: + +
    + +
    + +从上图看到 `ppyolov2_r50vd_dcn_365e_coco.yml` 配置需要依赖其他的配置文件,这些配置文件的含义如下: + +``` +coco_detection.yml:主要说明了训练数据和验证数据的路径 + +runtime.yml:主要说明了公共的运行参数,比如是否使用GPU、每多少个epoch存储checkpoint等 + +optimizer_365e.yml:主要说明了学习率和优化器的配置 + +ppyolov2_r50vd_dcn.yml:主要说明模型和主干网络的情况 + +ppyolov2_reader.yml:主要说明数据读取器配置,如 batch size,并发加载子进程数等,同时包含读取后预处理操作,如resize、数据增强等等 +``` + +在主体检测任务中,需要将 `datasets/coco_detection.yml` 中的 `num_classes` 参数修改为 1 (只有 1 个前景类别),同时将训练集和测试集的路径修改为自定义数据集的路径。 + +此外,也可以根据实际情况,修改上述文件,比如,如果显存溢出,可以将 batch size 和学习率等比缩小等。 + + +### 3.4 启动训练 + +PaddleDetection 提供了单卡/多卡训练模式,满足用户多种训练需求。 + +* GPU 单卡训练 + +```bash +# windows和Mac下不需要执行该命令 +export CUDA_VISIBLE_DEVICES=0 +python tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml +``` + +* GPU多卡训练 + +```bash +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --eval +``` + +--eval:表示边训练边验证。 + + +* (**推荐**)模型微调 +如果希望加载 PaddleClas 中已经训练好的主体检测模型,在自己的数据集上进行模型微调,可以使用下面的命令进行训练。 + +```bash +export CUDA_VISIBLE_DEVICES=0 +# 指定pretrain_weights参数,加载通用的主体检测预训练模型 +python tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml -o pretrain_weights=https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/ppyolov2_r50vd_dcn_mainbody_v1.0_pretrained.pdparams +``` + +* 模型恢复训练 + +在日常训练过程中,有的用户由于一些原因导致训练中断,可以使用 `-r` 的命令恢复训练: + +```bash +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --eval -r output/ppyolov2_r50vd_dcn_365e_coco/10000 +``` + +注意:如果遇到 "`Out of memory error`" 问题, 尝试在 `ppyolov2_reader.yml` 文件中调小`batch_size`,同时等比例调小学习率。 + + +### 3.5 模型预测与调试 + +使用下面的命令完成 PaddleDetection 的预测过程。 + +```bash +export CUDA_VISIBLE_DEVICES=0 +python tools/infer.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --infer_img=your_image_path.jpg --output_dir=infer_output/ --draw_threshold=0.5 -o weights=output/ppyolov2_r50vd_dcn_365e_coco/model_final +``` + +`--draw_threshold` 是个可选参数. 根据 [NMS](https://ieeexplore.ieee.org/document/1699659) 的计算,不同阈值会产生不同的结果 `keep_top_k` 表示设置输出目标的最大数量,默认值为 100 ,用户可以根据自己的实际情况进行设定。 + +### 3.6 模型导出与预测部署。 + +执行导出模型脚本: + +```bash +python tools/export_model.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --output_dir=./inference -o weights=output/ppyolov2_r50vd_dcn_365e_coco/model_final.pdparams +``` + +预测模型会导出到 `inference/ppyolov2_r50vd_dcn_365e_coco` 目录下,分别为 `infer_cfg.yml` (预测不需要), `model.pdiparams`, `model.pdiparams.info`, `model.pdmodel` 。 + +注意: `PaddleDetection` 导出的inference模型的文件格式为 `model.xxx`,这里如果希望与PaddleClas的inference模型文件格式保持一致,需要将其 `model.xxx` 文件修改为 `inference.xxx` 文件,用于后续主体检测的预测部署。 + +更多模型导出教程,请参考: [EXPORT_MODEL](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/deploy/EXPORT_MODEL.md) + +最终,目录 `inference/ppyolov2_r50vd_dcn_365e_coco` 中包含 `inference.pdiparams`, `inference.pdiparams.info` 以及 `inference.pdmodel` 文件,其中 `inference.pdiparams` 为保存的 inference 模型权重文件, `inference.pdmodel` 为保存的 inference 模型结构文件。 + + +导出模型之后,在主体检测与识别任务中,就可以将检测模型的路径更改为该 inference 模型路径,完成预测。 + +以商品识别为例,其配置文件为 [inference_product.yaml](../../../deploy/configs/inference_product.yaml) ,修改其中的 `Global.det_inference_model_dir` 字段为导出的主体检测 inference 模型目录,参考[图像识别快速开始教程](../tutorials/quick_start_recognition.md) ,即可完成商品检测与识别过程。 + + +### FAQ + +#### Q:可以使用其他的主体检测模型结构吗? + +* A:可以的,但是目前的检测预处理过程仅适配了 PicoDet 以及 YOLO 系列的预处理,因此在使用的时候,建议优先使用这两个系列的模型进行训练,如果希望使用 Faster RCNN 等其他系列的模型,需要按照 PaddleDetection 的数据预处理,修改下预处理逻辑,这块如果您有需求或者有问题的话,欢迎提 issue 或者在微信群里反馈。 + +#### Q:可以修改主体检测的预测尺度吗? + +* A:可以的,但是需要注意 2 个地方 + * PaddleClas 中提供的主体检测模型是基于 `640x640` 的分辨率去训练的,因此预测的时候也是默认使用 `640x640` 的分辨率进行预测,使用其他分辨率预测的话,精度会有所降低。 + * 在模型导出的时候,建议也修改下模型导出的分辨率,保持模型导出、模型预测的分辨率一致。 From 3b5f2688aa4a3506d80b6c38b056fb651dcafea0 Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Sat, 9 Oct 2021 08:42:12 +0000 Subject: [PATCH 77/81] fix distributied training bug for rec slim model --- ppcls/configs/Vehicle/ResNet50.yaml | 4 ++-- ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml | 4 ++-- ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml | 6 +++--- ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml | 4 ++-- ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ppcls/configs/Vehicle/ResNet50.yaml b/ppcls/configs/Vehicle/ResNet50.yaml index 6994789d5..ba9008943 100644 --- a/ppcls/configs/Vehicle/ResNet50.yaml +++ b/ppcls/configs/Vehicle/ResNet50.yaml @@ -87,9 +87,9 @@ DataLoader: mean: [0., 0., 0.] sampler: - name: DistributedRandomIdentitySampler + name: PKSampler batch_size: 128 - num_instances: 2 + sample_per_id: 2 drop_last: False shuffle: True loader: diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml index 5e59e1b6e..4d4f08da5 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_prune.yaml @@ -92,9 +92,9 @@ DataLoader: mean: [0., 0., 0.] sampler: - name: DistributedRandomIdentitySampler + name: PKSampler batch_size: 128 - num_instances: 2 + sample_per_id: 2 drop_last: False shuffle: True loader: diff --git a/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml index 1ec73b0cb..0e45a5a9b 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_cls_quantization.yaml @@ -91,9 +91,9 @@ DataLoader: mean: [0., 0., 0.] sampler: - name: DistributedRandomIdentitySampler - batch_size: 128 - num_instances: 2 + name: PKSampler + batch_size: 64 + sample_per_id: 2 drop_last: False shuffle: True loader: diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml index f9c86e2a2..736c9847a 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_prune.yaml @@ -95,9 +95,9 @@ DataLoader: mean: [0., 0., 0.] sampler: - name: DistributedRandomIdentitySampler + name: PKSampler batch_size: 128 - num_instances: 2 + sample_per_id: 2 drop_last: False shuffle: True loader: diff --git a/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml index aff5228c1..72dc31865 100644 --- a/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml +++ b/ppcls/configs/slim/ResNet50_vehicle_reid_quantization.yaml @@ -94,9 +94,9 @@ DataLoader: mean: [0., 0., 0.] sampler: - name: DistributedRandomIdentitySampler + name: PKSampler batch_size: 64 - num_instances: 2 + sample_per_id: 2 drop_last: False shuffle: True loader: From 0e2080b2ae5599477ec2a0b7ab728a3d766c5638 Mon Sep 17 00:00:00 2001 From: weishengyu Date: Mon, 11 Oct 2021 17:14:54 +0800 Subject: [PATCH 78/81] update visualdl version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0dd675319..79f548c22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ opencv-python==4.4.0.46 pillow tqdm PyYAML -visualdl >= 2.0.0b +visualdl >= 2.2.0 scipy scikit-learn==0.23.2 gast==0.3.3 From f8b420946c103bba47c250be632dbd617b2b6ba7 Mon Sep 17 00:00:00 2001 From: littletomatodonkey Date: Mon, 11 Oct 2021 17:23:31 +0800 Subject: [PATCH 79/81] fix cspnet (#1282) * fix cspnet * minor fix --- ppcls/arch/backbone/__init__.py | 1 + ppcls/arch/backbone/model_zoo/cspnet.py | 374 ++++++++++++++++++ .../configs/ImageNet/CSPNet/CSPDarkNet53.yaml | 131 ++++++ 3 files changed, 506 insertions(+) create mode 100644 ppcls/arch/backbone/model_zoo/cspnet.py create mode 100644 ppcls/configs/ImageNet/CSPNet/CSPDarkNet53.yaml diff --git a/ppcls/arch/backbone/__init__.py b/ppcls/arch/backbone/__init__.py index d2efcdc07..9dd929bf8 100644 --- a/ppcls/arch/backbone/__init__.py +++ b/ppcls/arch/backbone/__init__.py @@ -58,6 +58,7 @@ from ppcls.arch.backbone.model_zoo.dla import DLA34, DLA46_c, DLA46x_c, DLA60, D from ppcls.arch.backbone.model_zoo.rednet import RedNet26, RedNet38, RedNet50, RedNet101, RedNet152 from ppcls.arch.backbone.model_zoo.tnt import TNT_small from ppcls.arch.backbone.model_zoo.hardnet import HarDNet68, HarDNet85, HarDNet39_ds, HarDNet68_ds +from ppcls.arch.backbone.model_zoo.cspnet import CSPDarkNet53 from ppcls.arch.backbone.variant_models.resnet_variant import ResNet50_last_stage_stride1 from ppcls.arch.backbone.variant_models.vgg_variant import VGG19Sigmoid diff --git a/ppcls/arch/backbone/model_zoo/cspnet.py b/ppcls/arch/backbone/model_zoo/cspnet.py new file mode 100644 index 000000000..30528214f --- /dev/null +++ b/ppcls/arch/backbone/model_zoo/cspnet.py @@ -0,0 +1,374 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr + +from ppcls.utils.save_load import load_dygraph_pretrain, load_dygraph_pretrain_from_url + +MODEL_URLS = { + "CSPDarkNet53": + "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/CSPDarkNet53_pretrained.pdparams" +} + +MODEL_CFGS = { + "CSPDarkNet53": dict( + stem=dict( + out_chs=32, kernel_size=3, stride=1, pool=''), + stage=dict( + out_chs=(64, 128, 256, 512, 1024), + depth=(1, 2, 8, 8, 4), + stride=(2, ) * 5, + exp_ratio=(2., ) + (1., ) * 4, + bottle_ratio=(0.5, ) + (1.0, ) * 4, + block_ratio=(1., ) + (0.5, ) * 4, + down_growth=True, )) +} + +__all__ = ['CSPDarkNet53' + ] # model_registry will add each entrypoint fn to this + + +class ConvBnAct(nn.Layer): + def __init__(self, + input_channels, + output_channels, + kernel_size=1, + stride=1, + padding=None, + dilation=1, + groups=1, + act_layer=nn.LeakyReLU, + norm_layer=nn.BatchNorm2D): + super().__init__() + if padding is None: + padding = (kernel_size - 1) // 2 + self.conv = nn.Conv2D( + in_channels=input_channels, + out_channels=output_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + weight_attr=ParamAttr(), + bias_attr=False) + + self.bn = norm_layer(num_features=output_channels) + self.act = act_layer() + + def forward(self, inputs): + x = self.conv(inputs) + x = self.bn(x) + if self.act is not None: + x = self.act(x) + return x + + +def create_stem(in_chans=3, + out_chs=32, + kernel_size=3, + stride=2, + pool='', + act_layer=None, + norm_layer=None): + stem = nn.Sequential() + if not isinstance(out_chs, (tuple, list)): + out_chs = [out_chs] + assert len(out_chs) + in_c = in_chans + for i, out_c in enumerate(out_chs): + conv_name = f'conv{i + 1}' + stem.add_sublayer( + conv_name, + ConvBnAct( + in_c, + out_c, + kernel_size, + stride=stride if i == 0 else 1, + act_layer=act_layer, + norm_layer=norm_layer)) + in_c = out_c + last_conv = conv_name + if pool: + stem.add_sublayer( + 'pool', nn.MaxPool2D( + kernel_size=3, stride=2, padding=1)) + return stem, dict( + num_chs=in_c, reduction=stride, module='.'.join(['stem', last_conv])) + + +class DarkBlock(nn.Layer): + def __init__(self, + in_chs, + out_chs, + dilation=1, + bottle_ratio=0.5, + groups=1, + act_layer=nn.ReLU, + norm_layer=nn.BatchNorm2D, + attn_layer=None, + drop_block=None): + super(DarkBlock, self).__init__() + mid_chs = int(round(out_chs * bottle_ratio)) + ckwargs = dict(act_layer=act_layer, norm_layer=norm_layer) + self.conv1 = ConvBnAct(in_chs, mid_chs, kernel_size=1, **ckwargs) + self.conv2 = ConvBnAct( + mid_chs, + out_chs, + kernel_size=3, + dilation=dilation, + groups=groups, + **ckwargs) + + def forward(self, x): + shortcut = x + x = self.conv1(x) + x = self.conv2(x) + x = x + shortcut + return x + + +class CrossStage(nn.Layer): + def __init__(self, + in_chs, + out_chs, + stride, + dilation, + depth, + block_ratio=1., + bottle_ratio=1., + exp_ratio=1., + groups=1, + first_dilation=None, + down_growth=False, + cross_linear=False, + block_dpr=None, + block_fn=DarkBlock, + **block_kwargs): + super(CrossStage, self).__init__() + first_dilation = first_dilation or dilation + down_chs = out_chs if down_growth else in_chs + exp_chs = int(round(out_chs * exp_ratio)) + block_out_chs = int(round(out_chs * block_ratio)) + conv_kwargs = dict( + act_layer=block_kwargs.get('act_layer'), + norm_layer=block_kwargs.get('norm_layer')) + + if stride != 1 or first_dilation != dilation: + self.conv_down = ConvBnAct( + in_chs, + down_chs, + kernel_size=3, + stride=stride, + dilation=first_dilation, + groups=groups, + **conv_kwargs) + prev_chs = down_chs + else: + self.conv_down = None + prev_chs = in_chs + + self.conv_exp = ConvBnAct( + prev_chs, exp_chs, kernel_size=1, **conv_kwargs) + prev_chs = exp_chs // 2 # output of conv_exp is always split in two + + self.blocks = nn.Sequential() + for i in range(depth): + self.blocks.add_sublayer( + str(i), + block_fn(prev_chs, block_out_chs, dilation, bottle_ratio, + groups, **block_kwargs)) + prev_chs = block_out_chs + + # transition convs + self.conv_transition_b = ConvBnAct( + prev_chs, exp_chs // 2, kernel_size=1, **conv_kwargs) + self.conv_transition = ConvBnAct( + exp_chs, out_chs, kernel_size=1, **conv_kwargs) + + def forward(self, x): + if self.conv_down is not None: + x = self.conv_down(x) + x = self.conv_exp(x) + split = x.shape[1] // 2 + xs, xb = x[:, :split], x[:, split:] + xb = self.blocks(xb) + xb = self.conv_transition_b(xb) + out = self.conv_transition(paddle.concat([xs, xb], axis=1)) + return out + + +class DarkStage(nn.Layer): + def __init__(self, + in_chs, + out_chs, + stride, + dilation, + depth, + block_ratio=1., + bottle_ratio=1., + groups=1, + first_dilation=None, + block_fn=DarkBlock, + block_dpr=None, + **block_kwargs): + super().__init__() + first_dilation = first_dilation or dilation + + self.conv_down = ConvBnAct( + in_chs, + out_chs, + kernel_size=3, + stride=stride, + dilation=first_dilation, + groups=groups, + act_layer=block_kwargs.get('act_layer'), + norm_layer=block_kwargs.get('norm_layer')) + + prev_chs = out_chs + block_out_chs = int(round(out_chs * block_ratio)) + self.blocks = nn.Sequential() + for i in range(depth): + self.blocks.add_sublayer( + str(i), + block_fn(prev_chs, block_out_chs, dilation, bottle_ratio, + groups, **block_kwargs)) + prev_chs = block_out_chs + + def forward(self, x): + x = self.conv_down(x) + x = self.blocks(x) + return x + + +def _cfg_to_stage_args(cfg, curr_stride=2, output_stride=32): + # get per stage args for stage and containing blocks, calculate strides to meet target output_stride + num_stages = len(cfg['depth']) + if 'groups' not in cfg: + cfg['groups'] = (1, ) * num_stages + if 'down_growth' in cfg and not isinstance(cfg['down_growth'], + (list, tuple)): + cfg['down_growth'] = (cfg['down_growth'], ) * num_stages + stage_strides = [] + stage_dilations = [] + stage_first_dilations = [] + dilation = 1 + for cfg_stride in cfg['stride']: + stage_first_dilations.append(dilation) + if curr_stride >= output_stride: + dilation *= cfg_stride + stride = 1 + else: + stride = cfg_stride + curr_stride *= stride + stage_strides.append(stride) + stage_dilations.append(dilation) + cfg['stride'] = stage_strides + cfg['dilation'] = stage_dilations + cfg['first_dilation'] = stage_first_dilations + stage_args = [ + dict(zip(cfg.keys(), values)) for values in zip(*cfg.values()) + ] + return stage_args + + +class CSPNet(nn.Layer): + def __init__(self, + cfg, + in_chans=3, + class_num=1000, + output_stride=32, + global_pool='avg', + drop_rate=0., + act_layer=nn.LeakyReLU, + norm_layer=nn.BatchNorm2D, + zero_init_last_bn=True, + stage_fn=CrossStage, + block_fn=DarkBlock): + super().__init__() + self.class_num = class_num + self.drop_rate = drop_rate + assert output_stride in (8, 16, 32) + layer_args = dict(act_layer=act_layer, norm_layer=norm_layer) + + # Construct the stem + self.stem, stem_feat_info = create_stem(in_chans, **cfg['stem'], + **layer_args) + self.feature_info = [stem_feat_info] + prev_chs = stem_feat_info['num_chs'] + curr_stride = stem_feat_info[ + 'reduction'] # reduction does not include pool + if cfg['stem']['pool']: + curr_stride *= 2 + + # Construct the stages + per_stage_args = _cfg_to_stage_args( + cfg['stage'], curr_stride=curr_stride, output_stride=output_stride) + self.stages = nn.LayerList() + for i, sa in enumerate(per_stage_args): + self.stages.add_sublayer( + str(i), + stage_fn( + prev_chs, **sa, **layer_args, block_fn=block_fn)) + prev_chs = sa['out_chs'] + curr_stride *= sa['stride'] + self.feature_info += [ + dict( + num_chs=prev_chs, + reduction=curr_stride, + module=f'stages.{i}') + ] + + # Construct the head + self.num_features = prev_chs + + self.pool = nn.AdaptiveAvgPool2D(1) + self.flatten = nn.Flatten(1) + self.fc = nn.Linear( + prev_chs, + class_num, + weight_attr=ParamAttr(), + bias_attr=ParamAttr()) + + def forward(self, x): + x = self.stem(x) + for stage in self.stages: + x = stage(x) + x = self.pool(x) + x = self.flatten(x) + x = self.fc(x) + return x + + +def _load_pretrained(pretrained, model, model_url, use_ssld=False): + if pretrained is False: + pass + elif pretrained is True: + load_dygraph_pretrain_from_url(model, model_url, use_ssld=use_ssld) + elif isinstance(pretrained, str): + load_dygraph_pretrain(model, pretrained) + else: + raise RuntimeError( + "pretrained type is not available. Please use `string` or `boolean` type." + ) + + +def CSPDarkNet53(pretrained=False, use_ssld=False, **kwargs): + model = CSPNet(MODEL_CFGS["CSPDarkNet53"], block_fn=DarkBlock, **kwargs) + _load_pretrained( + pretrained, model, MODEL_URLS["CSPDarkNet53"], use_ssld=use_ssld) + return model diff --git a/ppcls/configs/ImageNet/CSPNet/CSPDarkNet53.yaml b/ppcls/configs/ImageNet/CSPNet/CSPDarkNet53.yaml new file mode 100644 index 000000000..cce4e3e59 --- /dev/null +++ b/ppcls/configs/ImageNet/CSPNet/CSPDarkNet53.yaml @@ -0,0 +1,131 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 120 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference + # training model under @to_static + to_static: False + +# model architecture +Arch: + name: CSPDarkNet53 + class_num: 1000 + +# loss function config for traing/eval process +Loss: + Train: + - CELoss: + weight: 1.0 + Eval: + - CELoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Piecewise + decay_epochs: [30, 60, 90] + values: [0.1, 0.01, 0.001, 0.0001] + regularizer: + name: 'L2' + coeff: 0.0001 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 256 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: ImageNetDataset + image_root: ./dataset/ILSVRC2012/ + cls_label_path: ./dataset/ILSVRC2012/val_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 288 + - CropImage: + size: 256 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: docs/images/whl/demo.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 288 + - CropImage: + size: 256 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: Topk + topk: 5 + class_id_map_file: ppcls/utils/imagenet1k_label_list.txt + +Metric: + Train: + - TopkAcc: + topk: [1, 5] + Eval: + - TopkAcc: + topk: [1, 5] From e64b93e05808cd6ee7256a479aef4816fbe8e81a Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Tue, 12 Oct 2021 03:45:59 +0000 Subject: [PATCH 80/81] add lite model for whole chain --- tests/config/GhostNet_x0_5.txt | 51 ++++++++++++++++++++++++ tests/config/GhostNet_x1_0.txt | 51 ++++++++++++++++++++++++ tests/config/GhostNet_x1_3.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV1_x0_25.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV1_x0_5.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV1_x0_75.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV2_x0_25.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV2_x0_5.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV2_x0_75.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV2_x1_5.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV2_x2_0.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_large_x0_35.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_large_x0_5.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_large_x0_75.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_large_x1_0.txt | 4 +- tests/config/MobileNetV3_large_x1_25.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_small_x0_35.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_small_x0_5.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_small_x0_75.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_small_x1_0.txt | 51 ++++++++++++++++++++++++ tests/config/MobileNetV3_small_x1_25.txt | 51 ++++++++++++++++++++++++ tests/config/PPLCNet_x0_25.txt | 51 ++++++++++++++++++++++++ tests/config/PPLCNet_x0_35.txt | 51 ++++++++++++++++++++++++ tests/config/PPLCNet_x0_5.txt | 51 ++++++++++++++++++++++++ tests/config/PPLCNet_x1_0.txt | 51 ++++++++++++++++++++++++ tests/config/ResNet50_vd.txt | 4 +- tests/config/ShuffleNetV2_x0_25.txt | 51 ++++++++++++++++++++++++ tests/config/ShuffleNetV2_x0_33.txt | 51 ++++++++++++++++++++++++ tests/config/ShuffleNetV2_x0_5.txt | 51 ++++++++++++++++++++++++ tests/prepare.sh | 21 ++++++++-- tests/test.sh | 19 +++++---- 31 files changed, 1410 insertions(+), 15 deletions(-) create mode 100644 tests/config/GhostNet_x0_5.txt create mode 100644 tests/config/GhostNet_x1_0.txt create mode 100644 tests/config/GhostNet_x1_3.txt create mode 100644 tests/config/MobileNetV1_x0_25.txt create mode 100644 tests/config/MobileNetV1_x0_5.txt create mode 100644 tests/config/MobileNetV1_x0_75.txt create mode 100644 tests/config/MobileNetV2_x0_25.txt create mode 100644 tests/config/MobileNetV2_x0_5.txt create mode 100644 tests/config/MobileNetV2_x0_75.txt create mode 100644 tests/config/MobileNetV2_x1_5.txt create mode 100644 tests/config/MobileNetV2_x2_0.txt create mode 100644 tests/config/MobileNetV3_large_x0_35.txt create mode 100644 tests/config/MobileNetV3_large_x0_5.txt create mode 100644 tests/config/MobileNetV3_large_x0_75.txt create mode 100644 tests/config/MobileNetV3_large_x1_25.txt create mode 100644 tests/config/MobileNetV3_small_x0_35.txt create mode 100644 tests/config/MobileNetV3_small_x0_5.txt create mode 100644 tests/config/MobileNetV3_small_x0_75.txt create mode 100644 tests/config/MobileNetV3_small_x1_0.txt create mode 100644 tests/config/MobileNetV3_small_x1_25.txt create mode 100644 tests/config/PPLCNet_x0_25.txt create mode 100644 tests/config/PPLCNet_x0_35.txt create mode 100644 tests/config/PPLCNet_x0_5.txt create mode 100644 tests/config/PPLCNet_x1_0.txt create mode 100644 tests/config/ShuffleNetV2_x0_25.txt create mode 100644 tests/config/ShuffleNetV2_x0_33.txt create mode 100644 tests/config/ShuffleNetV2_x0_5.txt diff --git a/tests/config/GhostNet_x0_5.txt b/tests/config/GhostNet_x0_5.txt new file mode 100644 index 000000000..9505f9ad3 --- /dev/null +++ b/tests/config/GhostNet_x0_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:GhostNet_x0_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x0_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x0_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x0_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x0_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/GhostNet_x1_0.txt b/tests/config/GhostNet_x1_0.txt new file mode 100644 index 000000000..bcef875c9 --- /dev/null +++ b/tests/config/GhostNet_x1_0.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:GhostNet_x1_0 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x1_0.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x1_0.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x1_0.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_0_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/GhostNet_x1_3.txt b/tests/config/GhostNet_x1_3.txt new file mode 100644 index 000000000..7501dec59 --- /dev/null +++ b/tests/config/GhostNet_x1_3.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:GhostNet_x1_3 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x1_3.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x1_3.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/GhostNet/GhostNet_x1_3.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_3_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV1_x0_25.txt b/tests/config/MobileNetV1_x0_25.txt new file mode 100644 index 000000000..7b8271752 --- /dev/null +++ b/tests/config/MobileNetV1_x0_25.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV1_x0_25 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_25.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_25.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_25.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV1_x0_25_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV1_x0_5.txt b/tests/config/MobileNetV1_x0_5.txt new file mode 100644 index 000000000..b7ba44dff --- /dev/null +++ b/tests/config/MobileNetV1_x0_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV1_x0_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV1_x0_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV1_x0_75.txt b/tests/config/MobileNetV1_x0_75.txt new file mode 100644 index 000000000..dbab4deb1 --- /dev/null +++ b/tests/config/MobileNetV1_x0_75.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV1_x0_75 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_75.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_75.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV1/MobileNetV1_x0_75.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV1_x0_75_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV2_x0_25.txt b/tests/config/MobileNetV2_x0_25.txt new file mode 100644 index 000000000..7fb93555d --- /dev/null +++ b/tests/config/MobileNetV2_x0_25.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV2_x0_25 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_25.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_25.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_25.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_x0_25_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV2_x0_5.txt b/tests/config/MobileNetV2_x0_5.txt new file mode 100644 index 000000000..5a36396d0 --- /dev/null +++ b/tests/config/MobileNetV2_x0_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV2_x0_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_x0_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV2_x0_75.txt b/tests/config/MobileNetV2_x0_75.txt new file mode 100644 index 000000000..cf67fefd3 --- /dev/null +++ b/tests/config/MobileNetV2_x0_75.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV2_x0_75 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_75.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_75.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x0_75.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_x0_75_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV2_x1_5.txt b/tests/config/MobileNetV2_x1_5.txt new file mode 100644 index 000000000..657bb2457 --- /dev/null +++ b/tests/config/MobileNetV2_x1_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV2_x1_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x1_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x1_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x1_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_x1_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV2_x2_0.txt b/tests/config/MobileNetV2_x2_0.txt new file mode 100644 index 000000000..9ef458084 --- /dev/null +++ b/tests/config/MobileNetV2_x2_0.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV2_x2_0 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x2_0.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x2_0.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV2/MobileNetV2_x2_0.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_x2_0_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_large_x0_35.txt b/tests/config/MobileNetV3_large_x0_35.txt new file mode 100644 index 000000000..e139c0ae9 --- /dev/null +++ b/tests/config/MobileNetV3_large_x0_35.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_large_x0_35 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_35.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_35.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_35.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x0_35_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_large_x0_5.txt b/tests/config/MobileNetV3_large_x0_5.txt new file mode 100644 index 000000000..e1ee2c53f --- /dev/null +++ b/tests/config/MobileNetV3_large_x0_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_large_x0_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x0_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_large_x0_75.txt b/tests/config/MobileNetV3_large_x0_75.txt new file mode 100644 index 000000000..685c1f6fb --- /dev/null +++ b/tests/config/MobileNetV3_large_x0_75.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_large_x0_75 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_75.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_75.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x0_75.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x0_75_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_large_x1_0.txt b/tests/config/MobileNetV3_large_x1_0.txt index f5add2e9a..f290db3ea 100644 --- a/tests/config/MobileNetV3_large_x1_0.txt +++ b/tests/config/MobileNetV3_large_x1_0.txt @@ -31,11 +31,11 @@ norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNe quant_export:tools/export_model.py -c ppcls/configs/slim/MobileNetV3_large_x1_0_quantization.yaml fpgm_export:tools/export_model.py -c ppcls/configs/slim/MobileNetV3_large_x1_0_prune.yaml distill_export:null -export1:null +kl_quant:deploy/slim/quant_post_static.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x1_0.yaml -o Global.save_inference_dir=./inference export2:null inference_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/whole_chain/MobileNetV3_large_x1_0_inference.tar infer_model:../inference/ -kl_quant:deploy/slim/quant_post_static.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x1_0.yaml -o Global.save_inference_dir=./inference +infer_export:null infer_quant:Fasle inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False diff --git a/tests/config/MobileNetV3_large_x1_25.txt b/tests/config/MobileNetV3_large_x1_25.txt new file mode 100644 index 000000000..7f3570835 --- /dev/null +++ b/tests/config/MobileNetV3_large_x1_25.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_large_x1_25 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x1_25.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x1_25.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_large_x1_25.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_large_x1_25_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_small_x0_35.txt b/tests/config/MobileNetV3_small_x0_35.txt new file mode 100644 index 000000000..40fc59878 --- /dev/null +++ b/tests/config/MobileNetV3_small_x0_35.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_small_x0_35 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_35.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_35.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_35.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x0_35_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_small_x0_5.txt b/tests/config/MobileNetV3_small_x0_5.txt new file mode 100644 index 000000000..d444054fb --- /dev/null +++ b/tests/config/MobileNetV3_small_x0_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_small_x0_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x0_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_small_x0_75.txt b/tests/config/MobileNetV3_small_x0_75.txt new file mode 100644 index 000000000..95cd5b65d --- /dev/null +++ b/tests/config/MobileNetV3_small_x0_75.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_small_x0_75 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_75.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_75.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x0_75.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x0_75_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_small_x1_0.txt b/tests/config/MobileNetV3_small_x1_0.txt new file mode 100644 index 000000000..7e3e7e86c --- /dev/null +++ b/tests/config/MobileNetV3_small_x1_0.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_small_x1_0 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x1_0.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x1_0.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x1_0.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x1_0_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/MobileNetV3_small_x1_25.txt b/tests/config/MobileNetV3_small_x1_25.txt new file mode 100644 index 000000000..617b49cb2 --- /dev/null +++ b/tests/config/MobileNetV3_small_x1_25.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:MobileNetV3_small_x1_25 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x1_25.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x1_25.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/MobileNetV3/MobileNetV3_small_x1_25.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/MobileNetV3_small_x1_25_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/PPLCNet_x0_25.txt b/tests/config/PPLCNet_x0_25.txt new file mode 100644 index 000000000..c3166ec1e --- /dev/null +++ b/tests/config/PPLCNet_x0_25.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:PPLCNet_x0_25 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_25.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_25.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_25.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_25_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/PPLCNet_x0_35.txt b/tests/config/PPLCNet_x0_35.txt new file mode 100644 index 000000000..347661c95 --- /dev/null +++ b/tests/config/PPLCNet_x0_35.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:PPLCNet_x0_35 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_35.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_35.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_35.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_35_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/PPLCNet_x0_5.txt b/tests/config/PPLCNet_x0_5.txt new file mode 100644 index 000000000..b57d27dd9 --- /dev/null +++ b/tests/config/PPLCNet_x0_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:PPLCNet_x0_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x0_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/PPLCNet_x1_0.txt b/tests/config/PPLCNet_x1_0.txt new file mode 100644 index 000000000..be0c45ac5 --- /dev/null +++ b/tests/config/PPLCNet_x1_0.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:PPLCNet_x1_0 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_0.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_0.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/PPLCNet/PPLCNet_x1_0.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_0_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/ResNet50_vd.txt b/tests/config/ResNet50_vd.txt index aa7f9ac10..a2c7ea256 100644 --- a/tests/config/ResNet50_vd.txt +++ b/tests/config/ResNet50_vd.txt @@ -31,11 +31,11 @@ norm_export:tools/export_model.py -c ppcls/configs/ImageNet/ResNet/ResNet50_vd.y quant_export:tools/export_model.py -c ppcls/configs/slim/ResNet50_vd_quantization.yaml fpgm_export:tools/export_model.py -c ppcls/configs/slim/ResNet50_vd_prune.yaml distill_export:null -export1:null +kl_quant:deploy/slim/quant_post_static.py -c ppcls/configs/ImageNet/ResNet/ResNet50_vd.yaml -o Global.save_inference_dir=./inference export2:null infer_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/whole_chain/ResNet50_vd_inference.tar infer_model:../inference/ -kl_quant:deploy/slim/quant_post_static.py -c ppcls/configs/ImageNet/ResNet/ResNet50_vd.yaml -o Global.save_inference_dir=./inference +infer_export:null infer_quant:Fasle inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False diff --git a/tests/config/ShuffleNetV2_x0_25.txt b/tests/config/ShuffleNetV2_x0_25.txt new file mode 100644 index 000000000..9ad0ebd5b --- /dev/null +++ b/tests/config/ShuffleNetV2_x0_25.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:ShuffleNetV2_x0_25 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_25.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_25.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_25.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ShuffleNetV2_x0_25_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/ShuffleNetV2_x0_33.txt b/tests/config/ShuffleNetV2_x0_33.txt new file mode 100644 index 000000000..70dc11a77 --- /dev/null +++ b/tests/config/ShuffleNetV2_x0_33.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:ShuffleNetV2_x0_33 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_33.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_33.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_33.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ShuffleNetV2_x0_33_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/config/ShuffleNetV2_x0_5.txt b/tests/config/ShuffleNetV2_x0_5.txt new file mode 100644 index 000000000..84f1df60b --- /dev/null +++ b/tests/config/ShuffleNetV2_x0_5.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:ShuffleNetV2_x0_5 +python:python3.7 +gpu_list:0|0,1 +-o Global.device:gpu +-o Global.auto_cast:null +-o Global.epochs:lite_train_infer=2|whole_train_infer=120 +-o Global.output_dir:./output/ +-o DataLoader.Train.sampler.batch_size:8 +-o Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./dataset/ILSVRC2012/val +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_5.yaml -o Global.seed=1234 -o DataLoader.Train.sampler.shuffle=False -o DataLoader.Train.loader.num_workers=0 -o DataLoader.Train.loader.use_shared_memory=False +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_5.yaml +null:null +## +===========================infer_params========================== +-o Global.save_inference_dir:./inference +-o Global.pretrained_model: +norm_export:tools/export_model.py -c ppcls/configs/ImageNet/ShuffleNet/ShuffleNetV2_x0_5.yaml +quant_export:null +fpgm_export:null +distill_export:null +kl_quant:null +export2:null +pretrained_model_url:https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ShuffleNetV2_x0_5_pretrained.pdparams +infer_model:../inference/ +infer_export:True +infer_quant:Fasle +inference:python/predict_cls.py -c configs/inference_cls.yaml +-o Global.use_gpu:True|False +-o Global.enable_mkldnn:True|False +-o Global.cpu_num_threads:1|6 +-o Global.batch_size:1 +-o Global.use_tensorrt:True|False +-o Global.use_fp16:True|False +-o Global.inference_model_dir:../inference +-o Global.infer_imgs:../dataset/ILSVRC2012/val +-o Global.save_log_path:null +-o Global.benchmark:True +null:null diff --git a/tests/prepare.sh b/tests/prepare.sh index 6f9701a10..4c2aa052a 100644 --- a/tests/prepare.sh +++ b/tests/prepare.sh @@ -7,6 +7,15 @@ dataline=$(cat ${FILENAME}) # parser params IFS=$'\n' lines=(${dataline}) + +function func_parser_key(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[0]} + echo ${tmp} +} + function func_parser_value(){ strs=$1 IFS=":" @@ -20,7 +29,8 @@ function func_parser_value(){ fi } model_name=$(func_parser_value "${lines[1]}") -inference_model_url=$(func_parser_value "${lines[35]}") +model_url_value=$(func_parser_value "${lines[35]}") +model_url_key=$(func_parser_key "${lines[35]}") if [ ${MODE} = "lite_train_infer" ] || [ ${MODE} = "whole_infer" ];then # pretrain lite train data @@ -44,9 +54,12 @@ elif [ ${MODE} = "infer" ] || [ ${MODE} = "cpp_infer" ];then mv val.txt val_list.txt ln -s val_list.txt train_list.txt cd ../../ - # download inference model - eval "wget -nc $inference_model_url" - tar xf "${model_name}_inference.tar" + # download inference or pretrained model + eval "wget -nc $model_url_value" + if [[ $model_url_key == *inference* ]]; then + rm -rf inference + tar xf "${model_name}_inference.tar" + fi elif [ ${MODE} = "whole_train_infer" ];then cd dataset diff --git a/tests/test.sh b/tests/test.sh index b36991264..56af96ac2 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -113,14 +113,14 @@ norm_export=$(func_parser_value "${lines[29]}") pact_export=$(func_parser_value "${lines[30]}") fpgm_export=$(func_parser_value "${lines[31]}") distill_export=$(func_parser_value "${lines[32]}") -export_key1=$(func_parser_key "${lines[33]}") -export_value1=$(func_parser_value "${lines[33]}") +kl_quant_cmd_key=$(func_parser_key "${lines[33]}") +kl_quant_cmd_value=$(func_parser_value "${lines[33]}") export_key2=$(func_parser_key "${lines[34]}") export_value2=$(func_parser_value "${lines[34]}") # parser inference model infer_model_dir_list=$(func_parser_value "${lines[36]}") -infer_export_list=$(func_parser_value "${lines[37]}") +infer_export_flag=$(func_parser_value "${lines[37]}") infer_is_quant=$(func_parser_value "${lines[38]}") # parser inference inference_py=$(func_parser_value "${lines[39]}") @@ -295,8 +295,13 @@ if [ ${MODE} = "infer" ]; then eval $env export Count=0 IFS="|" - infer_run_exports=(${infer_export_list}) + infer_export_flag=(${infer_export_flag}) infer_quant_flag=(${infer_is_quant}) + if [ ${infer_export_flag} != "null" ] && [ ${infer_export_flag} != "False" ]; then + rm -rf ${infer_model_dir_list/..\//} + export_cmd="${python} ${norm_export} -o Global.pretrained_model=${model_name}_pretrained -o Global.save_inference_dir=${infer_model_dir_list/..\//}" + eval $export_cmd + fi cd deploy for infer_model in ${infer_model_dir_list[*]}; do #run inference @@ -308,9 +313,9 @@ if [ ${MODE} = "infer" ]; then cd .. # for kl_quant - echo "kl_quant" - if [ ${infer_run_exports} ]; then - command="${python} ${infer_run_exports}" + if [ ${kl_quant_cmd_value} != "null" ] && [ ${kl_quant_cmd_value} != "False" ]; then + echo "kl_quant" + command="${python} ${kl_quant_cmd_value}" eval $command last_status=${PIPESTATUS[0]} status_check $last_status "${command}" "${status_log}" From 936ead600144f5be5fe594577958ea8ef2549b6c Mon Sep 17 00:00:00 2001 From: dongshuilong Date: Tue, 12 Oct 2021 11:02:19 +0000 Subject: [PATCH 81/81] change infer batch_size to 1|16 for whole_train --- tests/config/DarkNet53.txt | 2 +- tests/config/GhostNet_x0_5.txt | 2 +- tests/config/GhostNet_x1_0.txt | 2 +- tests/config/GhostNet_x1_3.txt | 2 +- tests/config/HRNet_W18_C.txt | 2 +- tests/config/LeViT_128S.txt | 2 +- tests/config/MobileNetV1.txt | 2 +- tests/config/MobileNetV1_x0_25.txt | 2 +- tests/config/MobileNetV1_x0_5.txt | 2 +- tests/config/MobileNetV1_x0_75.txt | 2 +- tests/config/MobileNetV2.txt | 2 +- tests/config/MobileNetV2_x0_25.txt | 2 +- tests/config/MobileNetV2_x0_5.txt | 2 +- tests/config/MobileNetV2_x0_75.txt | 2 +- tests/config/MobileNetV2_x1_5.txt | 2 +- tests/config/MobileNetV2_x2_0.txt | 2 +- tests/config/MobileNetV3_large_x0_35.txt | 2 +- tests/config/MobileNetV3_large_x0_5.txt | 2 +- tests/config/MobileNetV3_large_x0_75.txt | 2 +- tests/config/MobileNetV3_large_x1_0.txt | 2 +- tests/config/MobileNetV3_large_x1_25.txt | 2 +- tests/config/MobileNetV3_small_x0_35.txt | 2 +- tests/config/MobileNetV3_small_x0_5.txt | 2 +- tests/config/MobileNetV3_small_x0_75.txt | 2 +- tests/config/MobileNetV3_small_x1_0.txt | 2 +- tests/config/MobileNetV3_small_x1_25.txt | 2 +- tests/config/PPLCNet_x0_25.txt | 2 +- tests/config/PPLCNet_x0_35.txt | 2 +- tests/config/PPLCNet_x0_5.txt | 2 +- tests/config/PPLCNet_x1_0.txt | 2 +- tests/config/ResNeXt101_vd_64x4d.txt | 2 +- tests/config/ResNet50_vd.txt | 2 +- tests/config/ShuffleNetV2_x0_25.txt | 2 +- tests/config/ShuffleNetV2_x0_33.txt | 2 +- tests/config/ShuffleNetV2_x0_5.txt | 2 +- tests/config/ShuffleNetV2_x1_0.txt | 2 +- tests/config/SwinTransformer_tiny_patch4_window7_224.txt | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/tests/config/DarkNet53.txt b/tests/config/DarkNet53.txt index 589d65511..9b9b95c88 100644 --- a/tests/config/DarkNet53.txt +++ b/tests/config/DarkNet53.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/GhostNet_x0_5.txt b/tests/config/GhostNet_x0_5.txt index 9505f9ad3..b752da46a 100644 --- a/tests/config/GhostNet_x0_5.txt +++ b/tests/config/GhostNet_x0_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/GhostNet_x1_0.txt b/tests/config/GhostNet_x1_0.txt index bcef875c9..c927b2bf3 100644 --- a/tests/config/GhostNet_x1_0.txt +++ b/tests/config/GhostNet_x1_0.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/GhostNet_x1_3.txt b/tests/config/GhostNet_x1_3.txt index 7501dec59..5766332f3 100644 --- a/tests/config/GhostNet_x1_3.txt +++ b/tests/config/GhostNet_x1_3.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/HRNet_W18_C.txt b/tests/config/HRNet_W18_C.txt index 0609047b8..c5334e38d 100644 --- a/tests/config/HRNet_W18_C.txt +++ b/tests/config/HRNet_W18_C.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/LeViT_128S.txt b/tests/config/LeViT_128S.txt index 35feb2ffb..6ddc0cd9f 100644 --- a/tests/config/LeViT_128S.txt +++ b/tests/config/LeViT_128S.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|Fasle -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV1.txt b/tests/config/MobileNetV1.txt index 278906219..319f852db 100644 --- a/tests/config/MobileNetV1.txt +++ b/tests/config/MobileNetV1.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV1_x0_25.txt b/tests/config/MobileNetV1_x0_25.txt index 7b8271752..a46a8e30f 100644 --- a/tests/config/MobileNetV1_x0_25.txt +++ b/tests/config/MobileNetV1_x0_25.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV1_x0_5.txt b/tests/config/MobileNetV1_x0_5.txt index b7ba44dff..bca197b79 100644 --- a/tests/config/MobileNetV1_x0_5.txt +++ b/tests/config/MobileNetV1_x0_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV1_x0_75.txt b/tests/config/MobileNetV1_x0_75.txt index dbab4deb1..ba81e35ce 100644 --- a/tests/config/MobileNetV1_x0_75.txt +++ b/tests/config/MobileNetV1_x0_75.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV2.txt b/tests/config/MobileNetV2.txt index 3904cb5d6..d4cf3fc5d 100644 --- a/tests/config/MobileNetV2.txt +++ b/tests/config/MobileNetV2.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV2_x0_25.txt b/tests/config/MobileNetV2_x0_25.txt index 7fb93555d..5368aef95 100644 --- a/tests/config/MobileNetV2_x0_25.txt +++ b/tests/config/MobileNetV2_x0_25.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV2_x0_5.txt b/tests/config/MobileNetV2_x0_5.txt index 5a36396d0..ba35c3aff 100644 --- a/tests/config/MobileNetV2_x0_5.txt +++ b/tests/config/MobileNetV2_x0_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV2_x0_75.txt b/tests/config/MobileNetV2_x0_75.txt index cf67fefd3..0f5a8ab8a 100644 --- a/tests/config/MobileNetV2_x0_75.txt +++ b/tests/config/MobileNetV2_x0_75.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV2_x1_5.txt b/tests/config/MobileNetV2_x1_5.txt index 657bb2457..55da65b12 100644 --- a/tests/config/MobileNetV2_x1_5.txt +++ b/tests/config/MobileNetV2_x1_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV2_x2_0.txt b/tests/config/MobileNetV2_x2_0.txt index 9ef458084..cd32244ff 100644 --- a/tests/config/MobileNetV2_x2_0.txt +++ b/tests/config/MobileNetV2_x2_0.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_large_x0_35.txt b/tests/config/MobileNetV3_large_x0_35.txt index e139c0ae9..bc6e77c0b 100644 --- a/tests/config/MobileNetV3_large_x0_35.txt +++ b/tests/config/MobileNetV3_large_x0_35.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_large_x0_5.txt b/tests/config/MobileNetV3_large_x0_5.txt index e1ee2c53f..693423dac 100644 --- a/tests/config/MobileNetV3_large_x0_5.txt +++ b/tests/config/MobileNetV3_large_x0_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_large_x0_75.txt b/tests/config/MobileNetV3_large_x0_75.txt index 685c1f6fb..24b35de56 100644 --- a/tests/config/MobileNetV3_large_x0_75.txt +++ b/tests/config/MobileNetV3_large_x0_75.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_large_x1_0.txt b/tests/config/MobileNetV3_large_x1_0.txt index f290db3ea..873072f25 100644 --- a/tests/config/MobileNetV3_large_x1_0.txt +++ b/tests/config/MobileNetV3_large_x1_0.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_large_x1_25.txt b/tests/config/MobileNetV3_large_x1_25.txt index 7f3570835..34057be52 100644 --- a/tests/config/MobileNetV3_large_x1_25.txt +++ b/tests/config/MobileNetV3_large_x1_25.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_small_x0_35.txt b/tests/config/MobileNetV3_small_x0_35.txt index 40fc59878..0f8b75e99 100644 --- a/tests/config/MobileNetV3_small_x0_35.txt +++ b/tests/config/MobileNetV3_small_x0_35.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_small_x0_5.txt b/tests/config/MobileNetV3_small_x0_5.txt index d444054fb..0693c0865 100644 --- a/tests/config/MobileNetV3_small_x0_5.txt +++ b/tests/config/MobileNetV3_small_x0_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_small_x0_75.txt b/tests/config/MobileNetV3_small_x0_75.txt index 95cd5b65d..ce4242737 100644 --- a/tests/config/MobileNetV3_small_x0_75.txt +++ b/tests/config/MobileNetV3_small_x0_75.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_small_x1_0.txt b/tests/config/MobileNetV3_small_x1_0.txt index 7e3e7e86c..a1f2b8b70 100644 --- a/tests/config/MobileNetV3_small_x1_0.txt +++ b/tests/config/MobileNetV3_small_x1_0.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/MobileNetV3_small_x1_25.txt b/tests/config/MobileNetV3_small_x1_25.txt index 617b49cb2..49831b667 100644 --- a/tests/config/MobileNetV3_small_x1_25.txt +++ b/tests/config/MobileNetV3_small_x1_25.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/PPLCNet_x0_25.txt b/tests/config/PPLCNet_x0_25.txt index c3166ec1e..0777cfa4f 100644 --- a/tests/config/PPLCNet_x0_25.txt +++ b/tests/config/PPLCNet_x0_25.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/PPLCNet_x0_35.txt b/tests/config/PPLCNet_x0_35.txt index 347661c95..0ce679e4d 100644 --- a/tests/config/PPLCNet_x0_35.txt +++ b/tests/config/PPLCNet_x0_35.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/PPLCNet_x0_5.txt b/tests/config/PPLCNet_x0_5.txt index b57d27dd9..fbee882a9 100644 --- a/tests/config/PPLCNet_x0_5.txt +++ b/tests/config/PPLCNet_x0_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/PPLCNet_x1_0.txt b/tests/config/PPLCNet_x1_0.txt index be0c45ac5..1e9911649 100644 --- a/tests/config/PPLCNet_x1_0.txt +++ b/tests/config/PPLCNet_x1_0.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/ResNeXt101_vd_64x4d.txt b/tests/config/ResNeXt101_vd_64x4d.txt index 90b796557..aab18d564 100644 --- a/tests/config/ResNeXt101_vd_64x4d.txt +++ b/tests/config/ResNeXt101_vd_64x4d.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/ResNet50_vd.txt b/tests/config/ResNet50_vd.txt index a2c7ea256..9b8f27cc6 100644 --- a/tests/config/ResNet50_vd.txt +++ b/tests/config/ResNet50_vd.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/ShuffleNetV2_x0_25.txt b/tests/config/ShuffleNetV2_x0_25.txt index 9ad0ebd5b..1c80e4f4a 100644 --- a/tests/config/ShuffleNetV2_x0_25.txt +++ b/tests/config/ShuffleNetV2_x0_25.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/ShuffleNetV2_x0_33.txt b/tests/config/ShuffleNetV2_x0_33.txt index 70dc11a77..34e813fb4 100644 --- a/tests/config/ShuffleNetV2_x0_33.txt +++ b/tests/config/ShuffleNetV2_x0_33.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/ShuffleNetV2_x0_5.txt b/tests/config/ShuffleNetV2_x0_5.txt index 84f1df60b..b918a7807 100644 --- a/tests/config/ShuffleNetV2_x0_5.txt +++ b/tests/config/ShuffleNetV2_x0_5.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/ShuffleNetV2_x1_0.txt b/tests/config/ShuffleNetV2_x1_0.txt index a208fa14d..1055910ac 100644 --- a/tests/config/ShuffleNetV2_x1_0.txt +++ b/tests/config/ShuffleNetV2_x1_0.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference diff --git a/tests/config/SwinTransformer_tiny_patch4_window7_224.txt b/tests/config/SwinTransformer_tiny_patch4_window7_224.txt index f2937b14b..94acb1503 100644 --- a/tests/config/SwinTransformer_tiny_patch4_window7_224.txt +++ b/tests/config/SwinTransformer_tiny_patch4_window7_224.txt @@ -41,7 +41,7 @@ inference:python/predict_cls.py -c configs/inference_cls.yaml -o Global.use_gpu:True|False -o Global.enable_mkldnn:True|False -o Global.cpu_num_threads:1|6 --o Global.batch_size:1 +-o Global.batch_size:1|16 -o Global.use_tensorrt:True|False -o Global.use_fp16:True|False -o Global.inference_model_dir:../inference