| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503 |
- # 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 contextlib
- import inspect
- import os
- import pickle
- import socket
- import time
- import warnings
- import numpy as np
- import paddle
- import paddle.distributed as dist
- from paddle import base
- from paddle.autograd import no_grad
- from paddle.base import core
- from paddle.base.executor import global_scope
- from paddle.base.framework import (
- Variable,
- _current_expected_place as _get_device,
- _get_paddle_place,
- )
- from paddle.distributed import fleet
- from paddle.distributed.fleet.base import role_maker
- from paddle.framework import in_dynamic_mode
- from paddle.framework.io_utils import is_belong_to_optimizer
- from paddle.io import DataLoader, Dataset, DistributedBatchSampler
- from paddle.jit.translated_layer import INFER_MODEL_SUFFIX, INFER_PARAMS_SUFFIX
- from paddle.metric import Metric
- from paddle.static import InputSpec as Input
- from .callbacks import EarlyStopping, config_callbacks
- from .model_summary import summary
- __all__ = []
- _parallel_context_initialized = False
- def to_list(value):
- if value is None:
- return value
- if isinstance(value, (list, tuple)):
- return list(value)
- return [value]
- def to_numpy(var):
- assert isinstance(var, (Variable, base.core.eager.Tensor)), "not a variable"
- if isinstance(var, base.core.eager.Tensor):
- return np.array(var)
- t = global_scope().find_var(var.name).get_tensor()
- return np.array(t)
- def flatten_list(l):
- assert isinstance(l, list), "not a list"
- outl = []
- splits = []
- for sl in l:
- assert isinstance(sl, list), "sub content not a list"
- splits.append(len(sl))
- outl += sl
- return outl, splits
- def restore_flatten_list(l, splits):
- outl = []
- for split in splits:
- assert len(l) >= split, "list length invalid"
- sl, l = l[:split], l[split:]
- outl.append(sl)
- return outl
- def extract_args(func):
- return inspect.getfullargspec(func).args
- def _all_gather(x):
- output = []
- dist.all_gather(output, x)
- output = paddle.concat(output, axis=0)
- return output
- def wait_server_ready(endpoints):
- assert not isinstance(endpoints, str)
- while True:
- all_ok = True
- not_ready_endpoints = []
- for ep in endpoints:
- ip_port = ep.split(":")
- with contextlib.closing(
- socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ) as sock:
- sock.settimeout(2)
- result = sock.connect_ex((ip_port[0], int(ip_port[1])))
- if result != 0:
- all_ok = False
- not_ready_endpoints.append(ep)
- if not all_ok:
- time.sleep(3)
- else:
- break
- def init_communicator(
- program, rank, nranks, wait_port, current_endpoint, endpoints
- ):
- if nranks < 2:
- return
- endpoints_str = ",".join(endpoints)
- other_endpoints = endpoints[:]
- other_endpoints.remove(current_endpoint)
- block = program.global_block()
- if rank == 0 and wait_port:
- wait_server_ready(other_endpoints)
- if core.is_compiled_with_cuda():
- nccl_id_var = block.create_var(
- name=base.unique_name.generate('nccl_id'),
- persistable=True,
- type=base.core.VarDesc.VarType.RAW,
- )
- block.append_op(
- type='c_gen_nccl_id',
- inputs={},
- outputs={'Out': nccl_id_var},
- attrs={
- 'rank': rank,
- 'endpoint': current_endpoint,
- 'other_endpoints': other_endpoints,
- },
- )
- block.append_op(
- type='c_comm_init',
- inputs={'X': nccl_id_var},
- outputs={},
- attrs={
- 'nranks': nranks,
- 'rank': rank,
- 'ring_id': 0,
- 'endpoints': endpoints_str,
- },
- )
- elif core.is_compiled_with_xpu():
- bkcl_id_var = block.create_var(
- name=base.unique_name.generate('bkcl_id'),
- persistable=True,
- type=base.core.VarDesc.VarType.RAW,
- )
- block.append_op(
- type='c_gen_bkcl_id',
- inputs={},
- outputs={'Out': bkcl_id_var},
- attrs={
- 'rank': rank,
- 'endpoint': current_endpoint,
- 'other_endpoints': other_endpoints,
- },
- )
- block.append_op(
- type='c_comm_init',
- inputs={'X': bkcl_id_var},
- outputs={},
- attrs={
- 'nranks': nranks,
- 'rank': rank,
- 'ring_id': 0,
- 'endpoints': endpoints_str,
- },
- )
- elif (
- paddle.distributed.ParallelEnv().device_type
- in paddle.device.get_all_custom_device_type()
- ):
- xccl_id_var = block.create_var(
- name=base.unique_name.generate('xccl_id'),
- persistable=True,
- type=base.core.VarDesc.VarType.RAW,
- )
- block.append_op(
- type='c_gen_xccl_id',
- inputs={},
- outputs={'Out': xccl_id_var},
- attrs={
- 'rank': rank,
- 'endpoint': current_endpoint,
- 'other_endpoints': other_endpoints,
- },
- )
- block.append_op(
- type='c_comm_init',
- inputs={'X': xccl_id_var},
- outputs={},
- attrs={
- 'nranks': nranks,
- 'rank': rank,
- 'ring_id': 0,
- 'endpoints': endpoints_str,
- },
- )
- def prepare_distributed_context(place=None):
- if place is None:
- place = (
- base.CUDAPlace(paddle.distributed.ParallelEnv().dev_id)
- if paddle.distributed.ParallelEnv().nranks > 1
- else base.CUDAPlace(0)
- )
- place = _get_paddle_place(place)
- strategy = paddle.distributed.parallel.ParallelStrategy()
- strategy.nranks = paddle.distributed.ParallelEnv().nranks
- strategy.local_rank = paddle.distributed.ParallelEnv().local_rank
- strategy.trainer_endpoints = (
- paddle.distributed.ParallelEnv().trainer_endpoints
- )
- strategy.current_endpoint = (
- paddle.distributed.ParallelEnv().current_endpoint
- )
- if strategy.nranks < 2:
- return
- global _parallel_context_initialized
- if not _parallel_context_initialized and isinstance(place, base.CUDAPlace):
- def _init_context():
- communicator_prog = base.Program()
- init_communicator(
- communicator_prog,
- strategy.local_rank,
- strategy.nranks,
- True,
- strategy.current_endpoint,
- strategy.trainer_endpoints,
- )
- exe = base.Executor(place)
- exe.run(communicator_prog)
- if in_dynamic_mode():
- base.disable_dygraph()
- _init_context()
- base.enable_dygraph(place)
- else:
- assert "Only support CUDAPlace for now."
- _parallel_context_initialized = True
- return strategy
- def _update_input_info(inputs):
- "Get input shape list by given inputs in Model initialization."
- shapes = None
- dtypes = None
- if isinstance(inputs, Input):
- shapes = [list(inputs.shape)]
- dtypes = [inputs.dtype]
- elif isinstance(inputs, (list, tuple)):
- shapes = [list(input.shape) for input in inputs]
- dtypes = [input.dtype for input in inputs]
- elif isinstance(inputs, dict):
- shapes = [list(inputs[name].shape) for name in inputs]
- dtypes = [inputs[name].dtype for name in inputs]
- else:
- return None
- return shapes, dtypes
- class StaticGraphAdapter:
- """
- Model training/inference with a static graph.
- """
- def __init__(self, model):
- super().__init__()
- self.model = model
- # with `_build_once` gone, parameters are now created in `__init__`
- # so we need to keep track of the parameters already created
- self._startup_prog = base.default_startup_program()
- self._orig_prog = base.default_main_program()
- self._label_vars = {} # label variables
- self._input_vars = {} # label variables
- self._endpoints = {}
- self._loss_endpoint = None
- self._executor = None
- self._progs = {}
- self._compiled_progs = {}
- self._merge_count = {
- 'eval_total': 0,
- 'test_total': 0,
- 'eval_batch': 0,
- 'test_batch': 0,
- }
- self._nranks = paddle.distributed.ParallelEnv().nranks
- self._local_rank = paddle.distributed.ParallelEnv().local_rank
- self._amp_level = "O0"
- self._amp_configs = {}
- self._amp_custom_lists = {}
- self._use_fp16_guard = None
- @property
- def mode(self):
- return self.model.mode
- @mode.setter
- def mode(self, value):
- self.model.mode = value
- def train_batch(self, inputs, labels=None, update=True):
- assert (
- self.model._optimizer
- ), "model not ready, please call `model.prepare()` first"
- self.mode = 'train'
- assert (
- update is True
- ), "Does not support `update == False` in static graph mode by now."
- return self._run(inputs, labels)
- def eval_batch(self, inputs, labels=None):
- self.mode = 'eval'
- return self._run(inputs, labels)
- def predict_batch(self, inputs):
- self.mode = 'test'
- return self._run(inputs, None)
- def parameters(self, *args, **kwargs):
- return self.model.network.parameters(*args, **kwargs)
- def save(self, path):
- def _save(state, path):
- if not state:
- return
- state = {
- k: to_numpy(v) if isinstance(v, Variable) else v
- for k, v in state.items()
- }
- with open(path, 'wb') as f:
- pickle.dump(state, f)
- base = os.path.basename(path)
- assert base != "", "path should be of 'dirname/filename' format"
- dir_name = os.path.dirname(path)
- if dir_name and not os.path.exists(dir_name):
- os.makedirs(dir_name)
- param_path = path + ".pdparams"
- _save(self.model.network.state_dict(), param_path)
- prog = self._progs.get('train', None)
- if prog is None or self.model._optimizer is None:
- return
- # XXX `optimizer.state_dict()` only work in dygraph mode
- optim_path = path + ".pdopt"
- optim = {
- p.name: p for p in filter(is_belong_to_optimizer, prog.list_vars())
- }
- if not optim:
- return
- _save(optim, optim_path)
- # TODO: Support save/load scaler state in static graph
- def load(self, param_state_pairs, optim_state):
- if self._executor is None:
- executor = base.Executor(base.CPUPlace())._default_executor
- else:
- executor = self._executor._default_executor
- # restore parameter states
- base.core._create_loaded_parameter(
- [param for param, state in param_state_pairs],
- global_scope(),
- executor,
- )
- for param, state in param_state_pairs:
- self._set_var(param, state)
- # restore optimizer states
- # FIXME what if a different optimizer is used?
- if not self.model._optimizer or not optim_state:
- return
- self._load_optimizer(optim_state, executor)
- def _load_optimizer(self, state, executor):
- prog = self._progs.get('train', None)
- optim = list(filter(is_belong_to_optimizer, prog.list_vars()))
- if not optim:
- return
- base.core._create_loaded_parameter(optim, global_scope(), executor)
- converted_state = dict(state)
- for var in optim:
- if var.name in ["@LR_DECAY_COUNTER@", "global_step"]:
- # When using learning rate scheduler, dygraph would name the
- # global step var as "global_step" to save, while static-graph
- # would has a state var named as "@LR_DECAY_COUNTER@".
- # NOTE: dygraph saved global_step is 1 larger than that in
- # static-graph, since the time of global_step to increase is
- # different.
- state_val = (
- (np.array(converted_state.pop("global_step")) - 1)
- if "global_step" in converted_state
- else converted_state.pop("@LR_DECAY_COUNTER@", None)
- )
- if state_val is not None:
- converted_state[var.name] = state_val
- elif var.name.startswith("learning_rate_"):
- # When using static learning rate, static-graph would make it
- # a persistable var named 'unique_name.generate("learning_rate")',
- # However, dygraph wouldn't save it.
- if var.name not in state:
- continue
- else:
- # moment and other accumulators
- if var.name not in converted_state:
- # try to convert from dygraph name
- opt_name = self.model._optimizer._name
- opt_cls_name = self.model._optimizer.__class__.__name__
- opt_unq_name = None
- for name in self.model._optimizer._accumulators.keys():
- accum_name = (
- name
- if opt_name is None
- else name[len(opt_name) + 1 :]
- )
- for (
- param_name,
- state_var,
- ) in self.model._optimizer._accumulators[name].items():
- if opt_unq_name is None:
- # can not infer out the exact unique(opt_name),
- # thus try to extract rather than generate
- for state_key in sorted(
- state.keys(),
- key=lambda x: len(x),
- reverse=True,
- ):
- prefix = (
- param_name
- + "_"
- + (
- opt_cls_name
- if opt_name is None
- else opt_name
- )
- + "_"
- )
- if state_key.startswith(prefix):
- prefix_offset = state_key[
- len(prefix) :
- ].find("_") + len(prefix)
- opt_unq_name = state_key[
- len(
- param_name + "_"
- ) : prefix_offset
- ]
- # TODO: assert
- # assert opt_unq_name is None
- # gen(param.name + "_" + gen(opt_name) + "_" + accum_name)
- # always end with "_0" since the unique optimizer._name
- dy_state_name = (
- param_name
- + "_"
- + opt_unq_name
- + "_"
- + accum_name
- + "_0"
- )
- converted_state[
- state_var.name
- ] = converted_state.pop(dy_state_name)
- assert (
- var.name in converted_state
- ), f"variable [{var.name}] is not in optimizer state file"
- self._set_var(var, converted_state[var.name])
- def _set_var(self, var, ndarray):
- t = global_scope().find_var(var.name).get_tensor()
- p = t._place()
- if p.is_cpu_place():
- place = base.CPUPlace()
- elif p.is_cuda_pinned_place():
- place = base.CUDAPinnedPlace()
- else:
- p = base.core.Place()
- p.set_place(t._place())
- place = base.CUDAPlace(p.gpu_device_id())
- t.set(ndarray, place)
- def _run(self, inputs, labels=None):
- compiled_prog = self._compiled_progs.get(self.mode, None)
- assert (
- compiled_prog
- ), "Model is not ready, please call `model.prepare()` first"
- inputs = to_list(inputs)
- if labels is not None:
- labels = to_list(labels)
- assert len(inputs) == len(self._input_vars[self.mode]), (
- "number of inputs"
- + " does not match number of arguments of `forward` method"
- )
- feed = {}
- input_names = [v.name for v in self._input_vars[self.mode]]
- input_dtypes = [v.dtype for v in self._input_vars[self.mode]]
- for idx, n in enumerate(input_names):
- # train and test may take different arguments
- if inputs[idx] is not None:
- feed[n] = inputs[idx]
- if self._amp_level == 'O2' and input_dtypes[idx] == paddle.float16:
- if isinstance(feed[n], core.LoDTensor):
- feed[n] = feed[n]._as_type(core.VarDesc.VarType.FP16)
- elif isinstance(feed[n], np.array):
- feed[n] = feed[n].astype('float16')
- if labels is not None:
- for idx, v in enumerate(self._label_vars[self.mode]):
- feed[v.name] = labels[idx]
- endpoints = self._endpoints[self.mode]
- if self.mode == 'test':
- fetch_list = endpoints['output']
- else:
- metric_list, metric_splits = flatten_list(endpoints['metric'])
- fetch_list = endpoints['loss'] + metric_list
- num_loss = len(endpoints['loss'])
- # if fetch Variable is same as input Variable, do not fetch
- # from program, get it from input directly
- pruned_fetch_list = []
- pruned_fetch_idx_name_map = [""] * len(fetch_list)
- for i, fetch_var in enumerate(fetch_list):
- if fetch_var.name in feed.keys():
- pruned_fetch_idx_name_map[i] = fetch_var.name
- else:
- pruned_fetch_list.append(fetch_var)
- rets = self._executor.run(
- compiled_prog,
- feed=feed,
- fetch_list=pruned_fetch_list,
- return_numpy=False,
- )
- # restore pruned fetch_list Variable from feeds
- for i, name in enumerate(pruned_fetch_idx_name_map):
- if len(name) > 0:
- rets.insert(i, feed[name])
- # LoDTensor cannot be fetch as numpy directly
- rets = [np.array(v) for v in rets]
- if self.mode == 'test':
- return rets[:]
- metric_states = restore_flatten_list(rets[num_loss:], metric_splits)
- metrics = []
- for metric, state in zip(self.model._metrics, metric_states):
- # cut off padding size
- if (
- self.mode != 'train'
- and self.model._test_dataloader is not None
- and isinstance(self.model._test_dataloader, DataLoader)
- and self._nranks > 1
- ):
- total_size = len(self.model._test_dataloader.dataset)
- # TODO: fixme if have better way to get batch size
- samples = state[0].shape[0]
- current_count = self._merge_count.get(self.mode + '_total', 0)
- if current_count + samples >= total_size:
- state = [
- s[: int(total_size - current_count), ...] for s in state
- ]
- self._merge_count[self.mode + '_total'] = 0
- self._merge_count[self.mode + '_batch'] = int(
- total_size - current_count
- )
- else:
- self._merge_count[self.mode + '_total'] += samples
- self._merge_count[self.mode + '_batch'] = samples
- metrics.append(metric.update(*state))
- if num_loss and len(metrics):
- return rets[:num_loss], metrics
- else:
- return rets[:num_loss] if num_loss else metrics
- def prepare(self):
- modes = ['train', 'eval', 'test']
- for mode in modes:
- self._make_program(mode)
- self._compile_and_initialize(self._progs[mode], mode)
- def _make_program(self, mode):
- prog = self._progs.get(mode, None)
- if prog is not None:
- return
- prog = self._orig_prog.clone()
- # NOTE: When defining learning rate scheduling in static-graph, ops to
- # increase the global step var and calculate learning rate would be
- # prepended into _orig_prog. test program marked by `_orig_prog.clone`
- # also would include these ops. Thus must prune these ops in test
- # program, otherwise the global step would be changed in test.
- if mode != 'train':
- for op in list(prog.global_block().ops):
- prog.global_block()._remove_op(0)
- if (
- mode == 'train'
- and self.model._optimizer
- and self.model._optimizer._learning_rate_map
- ):
- # HACK workaround learning rate map issue
- lr_var = self.model._optimizer._learning_rate_map[self._orig_prog]
- new_lr_var = prog.global_block().vars[lr_var.name]
- self.model._optimizer._learning_rate_map[prog] = new_lr_var
- losses = []
- metrics = []
- with base.program_guard(prog, self._startup_prog):
- inputs = self.model._inputs
- labels = self.model._labels if self.model._labels else []
- inputs = [k._create_feed_layer() for k in to_list(inputs)]
- labels = [k._create_feed_layer() for k in to_list(labels)]
- self._label_vars[mode] = labels
- outputs = to_list(self.model.network.forward(*inputs))
- if mode != 'test' and self.model._loss:
- losses = self.model._loss(*(outputs + labels))
- if self._nranks > 1 and mode != 'train':
- outputs = [_all_gather(o) for o in outputs]
- if mode != 'test':
- labels = [_all_gather(l) for l in labels]
- if mode != 'test':
- for metric in self.model._metrics:
- metrics.append(to_list(metric.compute(*(outputs + labels))))
- if mode == 'train' and self.model._optimizer:
- self._loss_endpoint = paddle.add_n(losses)
- if self._nranks > 1:
- role = role_maker.PaddleCloudRoleMaker(is_collective=True)
- fleet.init(role)
- dist_strategy = fleet.DistributedStrategy()
- if self._amp_level != 'O0':
- dist_strategy.amp = True
- dist_strategy.amp_configs = self._amp_configs.copy()
- dist_strategy.amp_configs.update(self._amp_custom_lists)
- dist_strategy.amp_configs['use_pure_fp16'] = (
- self._amp_level == 'O2'
- )
- self.model._optimizer = fleet.distributed_optimizer(
- self.model._optimizer, strategy=dist_strategy
- )
- elif self._amp_level != "O0" and core.is_compiled_with_cuda:
- amp_lists = (
- paddle.static.amp.AutoMixedPrecisionLists(
- **self._amp_custom_lists
- )
- if self._amp_custom_lists
- else None
- )
- self.model._optimizer = paddle.static.amp.decorate(
- self.model._optimizer,
- amp_lists=amp_lists,
- use_pure_fp16=self._amp_level == "O2",
- use_fp16_guard=self._use_fp16_guard,
- **self._amp_configs,
- )
- self.model._optimizer.minimize(self._loss_endpoint)
- if mode != 'train': # clone again to put it in test mode
- prog = prog.clone(for_test=True)
- self._input_vars[mode] = inputs
- self._progs[mode] = prog
- self._endpoints[mode] = {
- "output": outputs,
- "loss": to_list(losses),
- "metric": metrics,
- }
- def _compile_and_initialize(self, prog, mode):
- compiled_prog = self._compiled_progs.get(mode, None)
- if compiled_prog is not None:
- return compiled_prog
- assert (
- self.model._place is not None
- ), "device is not set, please call `model.prepare()` first"
- place = self.model._place
- # XXX *ALL WEIGHTS* should be initialized upon model construction
- # even if `forward()` may run different code path for different mode
- # therefore startup program only needs to run once
- if self._executor is None:
- self._executor = base.Executor(place)
- # XXX incremental initialization
- uninitialized = []
- for var_py in self._startup_prog.list_vars():
- var = base.global_scope().find_var(var_py.name)
- if (
- not var_py.name.startswith('nccl_id')
- and var
- and var.get_tensor()._is_initialized()
- ):
- continue
- uninitialized.append(var_py)
- # for RawProgramOptimizer, it will insert OP with no outputs like:
- # c_comm_init(inputs={X=['comm_id_0']}
- # but we cannot prune this op.
- block = self._startup_prog.global_block()
- for op in block.ops:
- if op.type == "c_comm_init":
- uninitialized.append(op)
- if uninitialized:
- startup_prog = self._startup_prog._prune(uninitialized)
- self._executor.run(startup_prog)
- if (
- self._amp_level == "O2"
- and mode == 'train'
- and core.is_compiled_with_cuda()
- ):
- self.model._optimizer.amp_init(place)
- if self._nranks < 2:
- compiled_prog = base.CompiledProgram(prog)
- else:
- compiled_prog = prog
- self._compiled_progs[mode] = compiled_prog
- class DynamicGraphAdapter:
- def __init__(self, model):
- super().__init__()
- self.model = model
- self._nranks = paddle.distributed.ParallelEnv().nranks
- self._local_rank = paddle.distributed.ParallelEnv().local_rank
- self._merge_count = {
- 'eval_total': 0,
- 'test_total': 0,
- 'eval_batch': 0,
- 'test_batch': 0,
- }
- self._input_info = None
- self._amp_level = "O0"
- self._amp_configs = {}
- self._amp_custom_lists = {}
- self._use_fp16_guard = True
- if self._nranks > 1:
- dist.init_parallel_env()
- strategy = paddle.distributed.parallel.ParallelStrategy()
- strategy.nranks = paddle.distributed.ParallelEnv().nranks
- strategy.local_rank = paddle.distributed.ParallelEnv().local_rank
- strategy.trainer_endpoints = (
- paddle.distributed.ParallelEnv().trainer_endpoints
- )
- strategy.current_endpoint = (
- paddle.distributed.ParallelEnv().current_endpoint
- )
- self.ddp_model = paddle.DataParallel(self.model.network, strategy)
- @property
- def mode(self):
- return self.model.mode
- @mode.setter
- def mode(self, value):
- self.model.mode = value
- # TODO multi device in dygraph mode not implemented at present time
- def train_batch(self, inputs, labels=None, update=True):
- assert (
- self.model._optimizer
- ), "model not ready, please call `model.prepare()` first"
- self.model.network.train()
- self.mode = 'train'
- inputs = to_list(inputs)
- self._input_info = _update_input_info(inputs)
- labels = labels or []
- labels = [paddle.to_tensor(l) for l in to_list(labels)]
- # scaler should be initialized only once
- if self._amp_level != "O0" and self.model._scaler is None:
- self.model._scaler = paddle.amp.GradScaler(**self._amp_configs)
- with paddle.amp.auto_cast(
- enable=self._amp_level != 'O0',
- **self._amp_custom_lists,
- level=self._amp_level,
- ):
- if self._nranks > 1:
- outputs = self.ddp_model(*[paddle.to_tensor(x) for x in inputs])
- else:
- outputs = self.model.network(
- *[paddle.to_tensor(x) for x in inputs]
- )
- losses = self.model._loss(*(to_list(outputs) + labels))
- losses = to_list(losses)
- final_loss = paddle.add_n(losses)
- if self._amp_level != "O0":
- scaled = self.model._scaler.scale(final_loss)
- scaled.backward()
- if update:
- self.model._scaler.minimize(self.model._optimizer, scaled)
- self.model.network.clear_gradients()
- else:
- final_loss.backward()
- if update:
- self.model._optimizer.minimize(final_loss)
- self.model.network.clear_gradients()
- metrics = []
- for metric in self.model._metrics:
- metric_outs = metric.compute(*(to_list(outputs) + labels))
- m = metric.update(*[to_numpy(m) for m in to_list(metric_outs)])
- metrics.append(m)
- return (
- ([to_numpy(l) for l in losses], metrics)
- if len(metrics) > 0
- else [to_numpy(l) for l in losses]
- )
- def eval_batch(self, inputs, labels=None):
- self.model.network.eval()
- self.mode = 'eval'
- inputs = to_list(inputs)
- self._input_info = _update_input_info(inputs)
- labels = labels or []
- labels = [paddle.to_tensor(l) for l in to_list(labels)]
- outputs = self.model.network(*[paddle.to_tensor(x) for x in inputs])
- # Transform data to expected device
- expected_device = paddle.device.get_device()
- for o in to_list(outputs):
- o._to(device=expected_device)
- for l in labels:
- l._to(device=expected_device)
- if self.model._loss:
- losses = self.model._loss(*(to_list(outputs) + labels))
- losses = to_list(losses)
- if self._nranks > 1:
- outputs = [_all_gather(o) for o in to_list(outputs)]
- labels = [_all_gather(l) for l in labels]
- if self.model._test_dataloader is not None and isinstance(
- self.model._test_dataloader, DataLoader
- ):
- total_size = len(self.model._test_dataloader.dataset)
- samples = outputs[0].shape[0]
- current_count = self._merge_count.get(self.mode + '_total', 0)
- if current_count + samples >= total_size:
- outputs = [
- o[: int(total_size - current_count)] for o in outputs
- ]
- labels = [
- l[: int(total_size - current_count)] for l in labels
- ]
- self._merge_count[self.mode + '_total'] = 0
- self._merge_count[self.mode + '_batch'] = int(
- total_size - current_count
- )
- else:
- self._merge_count[self.mode + '_total'] += samples
- self._merge_count[self.mode + '_batch'] = samples
- metrics = []
- for metric in self.model._metrics:
- # cut off padding value.
- metric_outs = metric.compute(*(to_list(outputs) + labels))
- m = metric.update(*[to_numpy(m) for m in to_list(metric_outs)])
- metrics.append(m)
- if self.model._loss and len(metrics):
- return [to_numpy(l) for l in losses], metrics
- elif self.model._loss:
- return [to_numpy(l) for l in losses]
- else:
- return metrics
- def predict_batch(self, inputs):
- self.model.network.eval()
- self.mode = 'test'
- inputs = [paddle.to_tensor(x) for x in to_list(inputs)]
- self._input_info = _update_input_info(inputs)
- outputs = self.model.network(*inputs)
- if self._nranks > 1 and isinstance(self.model._place, base.CUDAPlace):
- outputs = [_all_gather(o) for o in to_list(outputs)]
- return [to_numpy(o) for o in to_list(outputs)]
- def parameters(self, *args, **kwargs):
- return self.model.network.parameters(*args, **kwargs)
- def save(self, path):
- params = self.model.network.state_dict()
- paddle.save(params, path + '.pdparams')
- if self.model._optimizer is not None:
- if self.model._optimizer.state_dict():
- optim = self.model._optimizer.state_dict()
- paddle.save(optim, path + '.pdopt')
- if hasattr(self.model, '_scaler') and self.model._scaler is not None:
- if self.model._scaler.state_dict():
- scaler = self.model._scaler.state_dict()
- paddle.save(scaler, path + '.pdscaler')
- def load(self, param_state_pairs, optim_state, scaler_state=None):
- # restore parameter states
- for param, state in param_state_pairs:
- param.set_value(state)
- if hasattr(self.model, '_scaler') and self.model._scaler is not None:
- if scaler_state:
- self.model._scaler.load_state_dict(scaler_state)
- # restore optimizer states
- if not self.model._optimizer or not optim_state:
- return
- # If optimizer performs set_state_dict when state vars haven't been created,
- # which would happen when set_state_dict before minimize, the state would be
- # stored in optimizer._accumulators_holder and loaded lazily.
- # To contrive this when loading from static-graph saved states, extend
- # state dict to include keys named according to dygraph naming rules.
- # TODO: if len(self.model._optimizer._accumulators) > 0
- converted_state = dict(optim_state)
- opt_unq_name = self.model._optimizer._name
- if opt_unq_name is None:
- opt_unq_name = ''
- opt_cls_name = self.model._optimizer.__class__.__name__
- opt_name = opt_unq_name[: opt_unq_name.rfind("_")] # remove suffix idx
- param_names = [param.name for param in self.model.network.parameters()]
- for var_name, state_var in sorted(
- optim_state.items(), key=lambda x: len(x[0]), reverse=True
- ):
- if var_name in ["@LR_DECAY_COUNTER@", "global_step"]:
- # NOTE: dygraph saved global_step is 1 larger than that in
- # static-graph, since the time of global_step to increase is
- # different.
- if var_name == "@LR_DECAY_COUNTER@":
- converted_state["global_step"] = (
- np.array(converted_state.pop("@LR_DECAY_COUNTER@")) + 1
- )
- else:
- # moment and other accumulators
- # extend state dict to include promising dygraph names
- for param_name in param_names:
- if var_name.startswith(param_name + "_" + opt_name):
- # when init optimizer with name
- accum_name = var_name[
- len(param_name + "_" + opt_name + "_") :
- ]
- elif (
- var_name.startswith(param_name + "_")
- and opt_name == opt_cls_name
- ):
- # when init optimizer without name
- accum_name = var_name[len(param_name + "_") :]
- else:
- continue
- # remove suffix idx
- accum_name = accum_name[: accum_name.rfind("_")]
- # state names always end with "_0" in dygraph because of the
- # unique optimizer._name
- dy_state_name = (
- param_name
- + "_"
- + opt_unq_name
- + "_"
- + accum_name
- + "_0"
- )
- converted_state[dy_state_name] = state_var
- if not hasattr(self.model._optimizer, 'set_state_dict'):
- warnings.warn(
- "paddle.base.optimizer is deprecated in API 2.0, please use paddle.optimizer instead."
- )
- self.model._optimizer.set_dict(converted_state)
- else:
- self.model._optimizer.set_state_dict(converted_state)
- def prepare(self):
- if (
- self._amp_level == "O2"
- and self.model.mode == 'train'
- and core.is_compiled_with_cuda()
- ):
- self.model.network, self.model._optimizer = paddle.amp.decorate(
- models=self.model.network,
- optimizers=self.model._optimizer,
- level='O2',
- )
- if self._amp_level != "O0":
- self.model._scaler = None
- class Model:
- """
- An Model object is network with training and inference features.
- Dynamic graph and static graph are supported at the same time,
- switched by `paddle.enable_static()`. The usage is as follows.
- But note, the switching between dynamic and static should be before
- instantiating a Model. The input description, i.e, paddle.static.InputSpec,
- must be required for static graph.
- When training on GPU, auto mixed precision (AMP O1) and pure float16
- (AMP O2) training are both supported in static graph mode and dynamic mode.
- In static graph mode, before training with pure float16 (AMP O2),
- `multi_precision` could be set to True when creating optimizer, which can
- avoid poor accuracy or slow convergence in a way, and inputs of dtype float
- should be cast to float16 by users. `paddle.static.amp.fp16_guard` API
- should be also used to limit the range of pure float16 training, otherwise,
- 'use_fp16_guard' should be set to False by users. However, limiting the
- range of is not supported during training using AMP.
- Args:
- network (paddle.nn.Layer): The network is an instance of
- paddle.nn.Layer.
- inputs (InputSpec|list|tuple|dict|None, optional): `inputs`, entry points of network,
- could be a InputSpec instance, or list/tuple of InputSpec instances,
- or dict ({name: InputSpec}), and it couldn't be None in static
- graph. Default: None.
- labels (InputSpec|list|tuple|None, optional): `labels`, entry points of network,
- could be a InputSpec instance or list/tuple of InputSpec instances,
- or None. For static graph, if labels is required in loss,
- labels must be set. Otherwise, it could be None. Default: None.
- Examples:
- 1. A common example
- .. code-block:: python
- :name: code-example1
- >>> import paddle
- >>> import paddle.nn as nn
- >>> import paddle.vision.transforms as T
- >>> from paddle.static import InputSpec
- >>> device = paddle.set_device('cpu') # or 'gpu'
- >>> net = nn.Sequential(
- ... nn.Flatten(1),
- ... nn.Linear(784, 200),
- ... nn.Tanh(),
- ... nn.Linear(200, 10))
- ...
- >>> # inputs and labels are not required for dynamic graph.
- >>> input = InputSpec([None, 784], 'float32', 'x')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(net, input, label)
- >>> optim = paddle.optimizer.SGD(learning_rate=1e-3,
- ... parameters=model.parameters())
- ...
- >>> model.prepare(optim,
- ... paddle.nn.CrossEntropyLoss(),
- ... paddle.metric.Accuracy())
- ...
- >>> transform = T.Compose([
- ... T.Transpose(),
- ... T.Normalize([127.5], [127.5])
- >>> ])
- >>> data = paddle.vision.datasets.MNIST(mode='train', transform=transform)
- >>> model.fit(data, epochs=2, batch_size=32, verbose=1)
- 2. An example using mixed precision training.
- .. code-block:: python
- :name: code-example2
- >>> # doctest: +REQUIRES(env:GPU)
- >>> import paddle
- >>> paddle.device.set_device('gpu')
- >>> import paddle.nn as nn
- >>> import paddle.vision.transforms as T
- >>> def run_example_code():
- ... device = paddle.set_device('gpu')
- ...
- ... net = nn.Sequential(nn.Flatten(1), nn.Linear(784, 200), nn.Tanh(),
- ... nn.Linear(200, 10))
- ...
- ... model = paddle.Model(net)
- ... optim = paddle.optimizer.SGD(learning_rate=1e-3, parameters=model.parameters())
- ...
- ... amp_configs = {
- ... "level": "O1",
- ... "custom_white_list": {'conv2d'},
- ... "use_dynamic_loss_scaling": True
- ... }
- ... model.prepare(optim,
- ... paddle.nn.CrossEntropyLoss(),
- ... paddle.metric.Accuracy(),
- ... amp_configs=amp_configs)
- ...
- ... transform = T.Compose([T.Transpose(), T.Normalize([127.5], [127.5])])
- ... data = paddle.vision.datasets.MNIST(mode='train', transform=transform)
- ... model.fit(data, epochs=2, batch_size=32, verbose=1)
- ...
- >>> # mixed precision training is only supported on GPU now.
- >>> if paddle.is_compiled_with_cuda():
- ... run_example_code()
- ...
- """
- def __init__(self, network, inputs=None, labels=None):
- self.mode = 'train'
- self.network = network
- self._inputs = None
- self._labels = None
- self._loss = None
- self._loss_weights = None
- self._optimizer = None
- self._input_info = None
- self._is_shape_inferred = False
- self._test_dataloader = None
- self.stop_training = False
- if not in_dynamic_mode():
- if not isinstance(inputs, (list, tuple, dict, Input)):
- raise TypeError(
- "'inputs' must be list or tuple or dict, and couldn't be None."
- )
- elif inputs:
- self._input_info = _update_input_info(inputs)
- self._inputs = self._verify_spec(inputs, is_input=True)
- self._labels = self._verify_spec(labels)
- # init backend
- if in_dynamic_mode():
- self._adapter = DynamicGraphAdapter(self)
- else:
- self._adapter = StaticGraphAdapter(self)
- def train_batch(self, inputs, labels=None, update=True):
- """
- Run one training step on one batch of data. And using `update` indicates
- whether optimizer update gradients computing by this batch.
- Args:
- inputs (numpy.ndarray|Tensor|list): Batch of input data. It could
- be a numpy array or paddle.Tensor, or a list of arrays or
- tensors (in case the model has multiple inputs).
- labels (numpy.ndarray|Tensor|list, optional): Batch of labels. It could be
- a numpy array or paddle.Tensor, or a list of arrays or tensors
- (in case the model has multiple labels). If has no labels,
- set None. Default: None.
- update (bool, optional): Whether update parameters after loss.backward() computing.
- Set it to False to accumulate gradients. Default: True.
- Returns:
- A list of scalar training loss if the model has no metrics,
- or a tuple (list of scalar loss, list of metrics) if the model
- set metrics.
- Examples:
- .. code-block:: python
- >>> import paddle
- >>> import paddle.nn as nn
- >>> from paddle.static import InputSpec
- >>> paddle.seed(2023)
- >>> device = paddle.set_device('cpu') # or 'gpu'
- >>> net = nn.Sequential(
- ... nn.Linear(784, 200),
- ... nn.Tanh(),
- ... nn.Linear(200, 10))
- ...
- >>> input = InputSpec([None, 784], 'float32', 'x')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(net, input, label)
- >>> optim = paddle.optimizer.SGD(learning_rate=1e-3,
- ... parameters=model.parameters())
- >>> model.prepare(optim, paddle.nn.CrossEntropyLoss())
- >>> data = paddle.rand((4, 784), dtype="float32")
- >>> label = paddle.randint(0, 10, (4, 1), dtype="int64")
- >>> loss = model.train_batch([data], [label])
- >>> print(loss)
- [array(3.0039132, dtype=float32)]
- """
- loss = self._adapter.train_batch(inputs, labels, update)
- if in_dynamic_mode() and self._input_info is None:
- self._update_inputs()
- return loss
- @no_grad()
- def eval_batch(self, inputs, labels=None):
- """
- Run one evaluating step on a batch of data.
- Args:
- inputs (numpy.ndarray|Tensor|list): Batch of input data. It could
- be a numpy array or paddle.Tensor, or a list of arrays or
- tensors (in case the model has multiple inputs).
- labels (numpy.ndarray|Tensor|list, optional): Batch of labels. It could be
- a numpy array or paddle.Tensor, or a list of arrays or tensors
- (in case the model has multiple labels). If has no labels,
- set None. Default: None.
- Returns:
- A list of scalar testing loss if the model has no metrics,
- or a tuple (list of scalar loss, list of metrics) if the model
- set metrics.
- Examples:
- .. code-block:: python
- >>> import paddle
- >>> import paddle.nn as nn
- >>> from paddle.static import InputSpec
- >>> paddle.seed(2023)
- >>> device = paddle.set_device('cpu') # or 'gpu'
- >>> net = nn.Sequential(
- ... nn.Linear(784, 200),
- ... nn.Tanh(),
- ... nn.Linear(200, 10))
- ...
- >>> input = InputSpec([None, 784], 'float32', 'x')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(net, input, label)
- >>> optim = paddle.optimizer.SGD(learning_rate=1e-3,
- ... parameters=model.parameters())
- >>> model.prepare(optim,
- ... paddle.nn.CrossEntropyLoss(),
- ... metrics=paddle.metric.Accuracy())
- >>> data = paddle.rand((4, 784), dtype="float32")
- >>> label = paddle.randint(0, 10, (4, 1), dtype="int64")
- >>> loss, acc = model.eval_batch([data], [label])
- >>> print(loss, acc)
- [array(3.0039132, dtype=float32)] [0.0]
- """
- loss = self._adapter.eval_batch(inputs, labels)
- if in_dynamic_mode() and self._input_info is None:
- self._update_inputs()
- return loss
- @no_grad()
- def predict_batch(self, inputs):
- """
- Run one predicting step on a batch of data.
- Args:
- inputs (numpy.ndarray|Tensor|list): Batch of input data. It could
- be a numpy array or paddle.Tensor, or a list of arrays or
- tensors (in case the model has multiple inputs).
- Returns:
- A list of numpy.ndarray of predictions, that is the outputs
- of Model forward.
- Examples:
- .. code-block:: python
- >>> import paddle
- >>> import paddle.nn as nn
- >>> from paddle.static import InputSpec
- >>> paddle.seed(2023)
- >>> device = paddle.set_device('cpu') # or 'gpu'
- >>> input = InputSpec([None, 784], 'float32', 'x')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> net = nn.Sequential(
- ... nn.Linear(784, 200),
- ... nn.Tanh(),
- ... nn.Linear(200, 10),
- ... nn.Softmax())
- ...
- >>> model = paddle.Model(net, input, label)
- >>> model.prepare()
- >>> data = paddle.rand((1, 784), dtype="float32")
- >>> out = model.predict_batch([data])
- >>> print(out)
- [array([[0.10844935, 0.04650883, 0.11790176, 0.04962315, 0.10899059,
- 0.08197589, 0.03125402, 0.03232312, 0.3786293 , 0.04434395]],
- dtype=float32)]
- """
- loss = self._adapter.predict_batch(inputs)
- if in_dynamic_mode() and self._input_info is None:
- self._update_inputs()
- return loss
- def save(self, path, training=True):
- """
- This function saves parameters, optimizer information or model and
- parameters only for inference to path. It depends on the parameter
- `training`.
- If `training` is set to True, the parameters saved contain all
- the trainable Variable, will save to a file with suffix ".pdparams".
- The optimizer information contains all the variable used by optimizer.
- For Adam optimizer, contains beta1, beta2, momentum etc. All the
- information will save to a file with suffix ".pdopt". (If the optimizer
- have no variable need to save (like SGD), the fill will not generated).
- This function will silently overwrite existing file at the target location.
- If `training` is set to False, only inference model will be saved.
- Args:
- path (str): The file prefix to save model. The format
- is 'dirname/file_prefix' or 'file_prefix'. if empty str.
- A exception will be raised.
- training (bool, optional): Whether to save for training. If not, save
- for inference only. Default: True.
- Returns:
- None
- Examples:
- .. code-block:: python
- >>> import paddle
- >>> import paddle.nn as nn
- >>> import paddle.vision.transforms as T
- >>> from paddle.static import InputSpec
- >>> from paddle.vision.datasets import MNIST
- >>> dynamic = True # False
- >>> # If use static graph, do not set
- >>> if not dynamic:
- ... paddle.enable_static()
- >>> transform = T.Compose([T.Transpose(),
- ... T.Normalize([127.5], [127.5])])
- >>> train_dataset = MNIST(mode='train', transform=transform)
- >>> train_loader = paddle.io.DataLoader(train_dataset, batch_size=64)
- >>> val_dataset = MNIST(mode='test', transform=transform)
- >>> val_loader = paddle.io.DataLoader(val_dataset, batch_size=64)
- >>> input = InputSpec([None, 1, 28, 28], 'float32', 'image')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(paddle.vision.models.LeNet(), input, label)
- >>> optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
- >>> model.prepare(optim, paddle.nn.CrossEntropyLoss(), paddle.metric.Accuracy(topk=(1, 2)))
- >>> model.fit(train_loader, val_loader, epochs=2, verbose=0)
- >>> model.save('checkpoint/test') # save for training
- >>> model.save('inference_model', False) # save for inference
- """
- if paddle.distributed.ParallelEnv().local_rank == 0:
- if not training:
- self._save_inference_model(path)
- else:
- self._adapter.save(path)
- def load(self, path, skip_mismatch=False, reset_optimizer=False):
- """
- Load from files storing the model states and optimizer states. The file
- for optimizer states is not necessary if no need to restore the optimizer.
- NOTE: parameters are retrieved out from the file storing model states
- according to their structured names.
- For fine-tuning or transfer-learning models where some of the layers have
- changed, keep parameters needed to restore have same structured names in
- the pre-trained model and fine-tuning model.
- Args:
- path (str): The prefix of files storing the model states and
- optimizer states. The files would be `path.pdparams` and
- `path.pdopt` separately, and the latter is not necessary
- when no need to restore.
- skip_mismatch (bool, optional): Whether to skip the loading of mismatch
- parameter or raise an error when mismatch happens (not found
- the parameter in file storing model states of or receives a
- mismatch shape). Default: False.
- reset_optimizer (bool, optional): If True, ignore the providing file storing
- optimizer states and initialize optimizer states from scratch.
- Otherwise, restore optimizer states from `path.pdopt` if
- a optimizer has been set to the model. Default: False.
- Returns:
- None
- Examples:
- .. code-block:: python
- >>> import paddle
- >>> import paddle.nn as nn
- >>> from paddle.static import InputSpec
- >>> device = paddle.set_device('cpu')
- >>> input = InputSpec([None, 784], 'float32', 'x')
- >>> model = paddle.Model(nn.Sequential(
- ... nn.Linear(784, 200),
- ... nn.Tanh(),
- ... nn.Linear(200, 10),
- ... nn.Softmax()), input)
- ...
- >>> model.save('checkpoint/test')
- >>> model.load('checkpoint/test')
- """
- def _load_state_from_path(path):
- if not os.path.exists(path):
- return
- with open(path, 'rb') as f:
- return pickle.load(f, encoding='latin1')
- def _check_match(key, param):
- state = param_state.get(key, None)
- if state is None:
- raise ValueError(f"{key} is not found in the providing file.")
- if list(state.shape) != list(param.shape):
- raise ValueError(
- f"{key} receives a shape {list(state.shape)}, but the expected shape is {list(param.shape)}."
- )
- return param, state
- def _strip_postfix(path):
- path, ext = os.path.splitext(path)
- assert ext in [
- '',
- '.pdparams',
- '.pdopt',
- '.pdmodel',
- ], f"Unknown postfix {ext} from weights"
- return path
- path = _strip_postfix(path)
- param_state = _load_state_from_path(path + ".pdparams")
- assert param_state, "Failed to load parameters, please check path."
- matched_param_state = []
- for key, param in self.network.state_dict().items():
- try:
- match_res = _check_match(key, param)
- except ValueError as err:
- if skip_mismatch:
- warnings.warn(f"Skip loading for {key}. " + str(err))
- # reset optimizer when mismatch happens
- reset_optimizer = True
- else:
- raise err
- matched_param_state.append(match_res)
- optim_state = (
- None if reset_optimizer else _load_state_from_path(path + ".pdopt")
- )
- # TODO: support save/load scaler state in static graph
- if in_dynamic_mode():
- scaler_state = None
- if hasattr(self, '_scaler') and self._scaler is not None:
- if os.path.exists(path + '.pdscaler'):
- scaler_state = paddle.load(path + '.pdscaler')
- return self._adapter.load(
- matched_param_state, optim_state, scaler_state
- )
- else:
- return self._adapter.load(matched_param_state, optim_state)
- def parameters(self, *args, **kwargs):
- """
- Returns a list of parameters of the model.
- Returns:
- A list of Parameter in static graph.
- A list of ParamBase in dynamic graph.
- Examples:
- .. code-block:: python
- >>> import paddle
- >>> import paddle.nn as nn
- >>> from paddle.static import InputSpec
- >>> paddle.seed(2023)
- >>> input = InputSpec([None, 784], 'float32', 'x')
- >>> model = paddle.Model(nn.Sequential(
- ... nn.Linear(784, 200),
- ... nn.Tanh(),
- ... nn.Linear(200, 10)), input)
- ...
- >>> params = model.parameters()
- >>> print(params)
- [Parameter containing:
- Tensor(shape=[784, 200], dtype=float32, place=Place(cpu), stop_gradient=False,
- [[ 0.05713400, 0.00314646, -0.03754271, ..., -0.02529256,
- 0.04872842, -0.06670858],
- ...,
- [ 0.06268418, 0.06550254, -0.02103353, ..., 0.06395906,
- 0.05509177, -0.06355451]]), Parameter containing:
- Tensor(shape=[200], dtype=float32, place=Place(cpu), stop_gradient=False,
- [0., 0., 0., ..., 0., 0.]), Parameter containing:
- Tensor(shape=[200, 10], dtype=float32, place=Place(cpu), stop_gradient=False,
- [[ 0.12933084, 0.07726504, 0.05336720, ..., 0.10865459,
- 0.06605886, 0.13684085],
- ...,
- [-0.10171061, -0.01649965, -0.13420501, ..., 0.11190581,
- -0.12700224, 0.02916957]]), Parameter containing:
- Tensor(shape=[10], dtype=float32, place=Place(cpu), stop_gradient=False,
- [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])]
- """
- return self._adapter.parameters()
- def _prepare_amp(self, amp_configs):
- def _check_pure_fp16_configs():
- # pure float16 training has some restricts now
- if self._adapter._amp_level == "O2" and self._optimizer._grad_clip:
- # clip by value is not supported
- assert isinstance(
- self._optimizer._grad_clip,
- (paddle.nn.ClipGradByGlobalNorm, paddle.nn.ClipGradByNorm),
- ), "Only ClipGradByNorm and ClipGradByGlobalNorm are supported in amp training with level=O2 currently."
- self._adapter._amp_custom_lists = {}
- self._adapter._amp_configs = {}
- # check and get level of mixed precision training
- if not amp_configs:
- self._adapter._amp_level = 'O0'
- return
- elif isinstance(amp_configs, str):
- if amp_configs not in ('O0', 'O1', 'O2'):
- raise ValueError(
- "The level of amp_configs should be 'O0', 'O1' or 'O2'."
- )
- self._adapter._amp_level = amp_configs
- _check_pure_fp16_configs()
- return
- else:
- if 'level' not in amp_configs:
- self._adapter._amp_level = 'O1'
- elif amp_configs['level'] not in ('O0', 'O1', 'O2'):
- raise ValueError(
- "amp_configs['level'] should be 'O0', 'O1' or 'O2'."
- )
- else:
- self._adapter._amp_level = amp_configs['level']
- amp_config_key_set = set(amp_configs.keys()) - {'level'}
- if not amp_config_key_set or self._adapter._amp_level == 'O0':
- return
- if 'use_pure_fp16' in amp_configs:
- raise ValueError(
- "'use_pure_fp16' is an invalid parameter, the level of mixed precision training only depends on 'O1' or 'O2'."
- )
- _check_pure_fp16_configs()
- # construct amp_custom_lists
- if self._adapter._amp_level != 'O0' and amp_config_key_set:
- for param_name in [
- 'custom_white_list',
- 'custom_black_list',
- 'custom_black_varnames',
- ]:
- if param_name in amp_config_key_set:
- self._adapter._amp_custom_lists[param_name] = amp_configs[
- param_name
- ]
- amp_config_key_set -= {param_name}
- def _check_amp_configs(amp_config_key_set):
- accepted_param_set = {
- 'init_loss_scaling',
- 'incr_ratio',
- 'decr_ratio',
- 'incr_every_n_steps',
- 'decr_every_n_nan_or_inf',
- 'use_dynamic_loss_scaling',
- 'use_fp16_guard',
- }
- if amp_config_key_set - accepted_param_set:
- raise ValueError(
- f"Except for 'level', the keys of 'amp_configs' must be accepted by mixed precision APIs, but {tuple(amp_config_key_set - accepted_param_set)} could not be recognized."
- )
- if 'use_fp16_guard' in amp_config_key_set:
- if in_dynamic_mode():
- raise ValueError(
- "'use_fp16_guard' is supported in static graph mode only."
- )
- self._adapter._use_fp16_guard = amp_configs['use_fp16_guard']
- amp_config_key_set.remove('use_fp16_guard')
- return amp_config_key_set
- amp_configs_set = _check_amp_configs(amp_config_key_set)
- for key in amp_configs_set:
- self._adapter._amp_configs[key] = amp_configs[key]
- def prepare(
- self, optimizer=None, loss=None, metrics=None, amp_configs=None
- ):
- """
- Configures the model before running.
- Args:
- optimizer (Optimizer|None, optional): Optimizer must be set in training
- and should be a Optimizer instance. It can be None in eval
- and test mode. Default: None.
- loss (Loss|Callable|None, optional): Loss function can
- be a `paddle.nn.Layer` instance or any callable function
- taken the predicted values and ground truth values as input.
- It can be None when there is no loss. Default: None.
- metrics (Metric|list[Metric]|None, optional): If metrics is set, all
- metrics will be calculated and output in train/eval mode. Default: None.
- amp_configs (str|dict|None, optional): AMP configurations. If AMP or pure
- float16 training is used, the key 'level' of 'amp_configs'
- should be set to 'O1' or 'O2' respectively. Otherwise, the
- value of 'level' defaults to 'O0', which means float32
- training. In addition to 'level', parameters consistent with
- mixed precision API could also be passed in. The supported
- keys are: 'init_loss_scaling', 'incr_ratio', 'decr_ratio',
- 'incr_every_n_steps', 'decr_every_n_nan_or_inf',
- 'use_dynamic_loss_scaling', 'custom_white_list',
- 'custom_black_list', and 'custom_black_varnames'or
- 'use_fp16_guard' is only supported in static graph mode. Mixed
- precision API documentations :ref:`api_paddle_amp_auto_cast`
- and :ref:`api_paddle_amp_GradScaler` could be referenced
- for details. For convenience, 'amp_configs' could be set to
- 'O1' or 'O2' if no more parameters are needed. 'amp_configs'
- could be None in float32 training. Default: None.
- Returns:
- None
- """
- self._place = _get_device()
- if isinstance(self._place, base.CUDAPlace):
- global _parallel_context_initialized
- if (
- paddle.distributed.ParallelEnv().nranks > 1
- and not _parallel_context_initialized
- ):
- if in_dynamic_mode():
- main_prog_seed = base.default_main_program().random_seed
- startup_prog_seed = (
- base.default_startup_program().random_seed
- )
- base.disable_dygraph()
- paddle.disable_static(self._place)
- # enable_dygraph would create and switch to a new program,
- # thus also copy seed to the new program
- base.default_main_program().random_seed = main_prog_seed
- base.default_startup_program().random_seed = (
- startup_prog_seed
- )
- else:
- prepare_distributed_context(self._place)
- _parallel_context_initialized = True
- self._optimizer = optimizer
- if loss is not None:
- if not isinstance(loss, paddle.nn.Layer) and not callable(loss):
- raise TypeError(
- "'loss' must be sub classes of `paddle.nn.Layer` or any callable function."
- )
- self._loss = loss
- metrics = metrics or []
- for metric in to_list(metrics):
- assert isinstance(
- metric, Metric
- ), f"{metric.__class__.__name__} is not sub class of Metric"
- self._metrics = to_list(metrics)
- self._prepare_amp(amp_configs)
- self._adapter.prepare()
- def fit(
- self,
- train_data=None,
- eval_data=None,
- batch_size=1,
- epochs=1,
- eval_freq=1,
- log_freq=10,
- save_dir=None,
- save_freq=1,
- verbose=2,
- drop_last=False,
- shuffle=True,
- num_workers=0,
- callbacks=None,
- accumulate_grad_batches=1,
- num_iters=None,
- ):
- """
- Trains the model for a fixed number of epochs. If `eval_data` is set,
- evaluation will be done at the end of each epoch.
- Args:
- train_data (Dataset|DataLoader, optional): An iterable data loader is used for
- train. An instance of paddle paddle.io.Dataset or
- paddle.io.Dataloader is recommended. Default: None.
- eval_data (Dataset|DataLoader, optional): An iterable data loader is used for
- evaluation at the end of epoch. If None, will not do evaluation.
- An instance of paddle.io.Dataset or paddle.io.Dataloader
- is recommended. Default: None.
- batch_size (int|list, optional): The batch size of train_data and eval_data. When
- train_data and eval_data are both the instance of Dataloader, this
- parameter will be ignored. Default: 1.
- epochs (int, optional): The number of epochs to train the model. Default: 1.
- eval_freq (int, optional): The frequency, in number of epochs, an evaluation
- is performed. Default: 1.
- log_freq (int, optional): The frequency, in number of steps, the training logs
- are printed. Default: 10.
- save_dir(str|None, optional): The directory to save checkpoint during training.
- If None, will not save checkpoint. Default: None.
- save_freq (int, optional): The frequency, in number of epochs, to save
- checkpoint. Default: 1.
- verbose (int, optional): The verbosity mode, should be 0, 1, or 2. 0 = silent,
- 1 = progress bar, 2 = one line per epoch. Default: 2.
- drop_last (bool, optional): Whether drop the last incomplete batch of
- train_data when dataset size is not divisible by the batch size.
- When train_data is an instance of Dataloader, this parameter
- will be ignored. Default: False.
- shuffle (bool, optional): Whether to shuffle train_data. When train_data is
- an instance of Dataloader, this parameter will be ignored.
- Default: True.
- num_workers (int, optional): The number of subprocess to load data, 0 for no
- subprocess used and loading data in main process.
- When train_data and eval_data are both the instance of
- Dataloader, this parameter will be ignored. Default: 0.
- callbacks (Callback|None, optional): A list of `Callback` instances to apply
- during training. If None, :ref:`api_paddle_callbacks_ProgBarLogger` and
- :ref:`api_paddle_callbacks_ModelCheckpoint` are automatically inserted. Default: None.
- accumulate_grad_batches (int, optional): The number of batches to accumulate gradient
- during training process before optimizer updates. It can mimic large batch
- size. Default: 1.
- num_iters (int|None, optional): The number of iterations to evaluate the model.
- If None, evaluate on whole input dataset, otherwise, evaluate `num_iters` times.
- Default: None.
- Returns:
- None
- Examples:
- 1. An example use Dataset and set batch size, shuffle in fit.
- How to make a batch is done internally.
- .. code-block:: python
- :name: code-example3
- >>> # doctest: +TIMEOUT(80)
- >>> import paddle
- >>> import paddle.vision.transforms as T
- >>> from paddle.vision.datasets import MNIST
- >>> from paddle.static import InputSpec
- >>> dynamic = True
- >>> if not dynamic:
- ... paddle.enable_static()
- ...
- >>> transform = T.Compose([T.Transpose(),
- ... T.Normalize([127.5], [127.5])])
- >>> train_dataset = MNIST(mode='train', transform=transform)
- >>> val_dataset = MNIST(mode='test', transform=transform)
- >>> input = InputSpec([None, 1, 28, 28], 'float32', 'image')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(
- ... paddle.vision.models.LeNet(),
- ... input, label)
- >>> optim = paddle.optimizer.Adam(
- ... learning_rate=0.001, parameters=model.parameters())
- >>> model.prepare(
- ... optim,
- ... paddle.nn.CrossEntropyLoss(),
- ... paddle.metric.Accuracy(topk=(1, 2)))
- >>> model.fit(train_dataset,
- ... val_dataset,
- ... epochs=2,
- ... batch_size=64,
- ... save_dir='mnist_checkpoint')
- ...
- 2. An example use DataLoader, batch size and shuffle is set in
- DataLoader.
- .. code-block:: python
- :name: code-example4
- >>> # doctest: +TIMEOUT(80)
- >>> import paddle
- >>> import paddle.vision.transforms as T
- >>> from paddle.vision.datasets import MNIST
- >>> from paddle.static import InputSpec
- >>> dynamic = True
- >>> if not dynamic:
- ... paddle.enable_static()
- ...
- >>> transform = T.Compose([T.Transpose(),
- ... T.Normalize([127.5], [127.5])])
- >>> train_dataset = MNIST(mode='train', transform=transform)
- >>> train_loader = paddle.io.DataLoader(train_dataset,
- ... batch_size=64)
- >>> val_dataset = MNIST(mode='test', transform=transform)
- >>> val_loader = paddle.io.DataLoader(val_dataset,
- ... batch_size=64)
- ...
- >>> input = InputSpec([None, 1, 28, 28], 'float32', 'image')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(
- ... paddle.vision.models.LeNet(), input, label)
- >>> optim = paddle.optimizer.Adam(
- ... learning_rate=0.001, parameters=model.parameters())
- >>> model.prepare(
- ... optim,
- ... paddle.nn.CrossEntropyLoss(),
- ... paddle.metric.Accuracy(topk=(1, 2)))
- >>> model.fit(train_loader,
- ... val_loader,
- ... epochs=2,
- ... save_dir='mnist_checkpoint')
- ...
- """
- assert train_data is not None, "train_data must be given!"
- if isinstance(batch_size, (tuple, list)) and all(
- isinstance(x, int) for x in batch_size
- ):
- assert (
- len(batch_size) == 2
- ), "batch_size length error, expected train_batch_size and eval_batch_size."
- train_batch_size, eval_batch_size = batch_size
- elif isinstance(batch_size, int):
- train_batch_size, eval_batch_size = batch_size, batch_size
- if isinstance(train_data, Dataset):
- train_sampler = DistributedBatchSampler(
- train_data,
- batch_size=train_batch_size,
- shuffle=shuffle,
- drop_last=drop_last,
- )
- train_loader = DataLoader(
- train_data,
- batch_sampler=train_sampler,
- places=self._place,
- num_workers=num_workers,
- return_list=True,
- )
- else:
- train_loader = train_data
- if eval_data is not None and isinstance(eval_data, Dataset):
- eval_sampler = DistributedBatchSampler(
- eval_data, batch_size=eval_batch_size
- )
- eval_loader = DataLoader(
- eval_data,
- batch_sampler=eval_sampler,
- places=self._place,
- num_workers=num_workers,
- return_list=True,
- )
- elif eval_data is not None:
- eval_loader = eval_data
- else:
- eval_loader = None
- do_eval = eval_loader is not None
- self._test_dataloader = eval_loader
- self._accumulate = accumulate_grad_batches
- steps = self._len_data_loader(train_loader)
- self.num_iters = num_iters
- if (
- num_iters is not None
- and isinstance(num_iters, int)
- and isinstance(steps, int)
- ):
- assert num_iters > 0, "num_iters must be greater than 0!"
- epochs = (num_iters // steps) + 1
- steps = min(num_iters, steps)
- cbks = config_callbacks(
- callbacks,
- model=self,
- epochs=epochs,
- steps=steps,
- log_freq=log_freq,
- save_freq=save_freq,
- save_dir=save_dir,
- verbose=verbose,
- metrics=self._metrics_name(),
- )
- if any(isinstance(k, EarlyStopping) for k in cbks) and not do_eval:
- warnings.warn("EarlyStopping needs validation data.")
- cbks.on_begin('train')
- for epoch in range(epochs):
- cbks.on_epoch_begin(epoch)
- logs = self._run_one_epoch(train_loader, cbks, 'train')
- cbks.on_epoch_end(epoch, logs)
- if do_eval and epoch % eval_freq == 0:
- eval_steps = self._len_data_loader(eval_loader)
- cbks.on_begin(
- 'eval',
- {'steps': eval_steps, 'metrics': self._metrics_name()},
- )
- eval_logs = self._run_one_epoch(eval_loader, cbks, 'eval')
- cbks.on_end('eval', eval_logs)
- if self.stop_training:
- break
- cbks.on_end('train', logs)
- self._test_dataloader = None
- def evaluate(
- self,
- eval_data,
- batch_size=1,
- log_freq=10,
- verbose=2,
- num_workers=0,
- callbacks=None,
- num_iters=None,
- ):
- """
- Evaluate the loss and metrics of the model on input dataset.
- Args:
- eval_data (Dataset|DataLoader): An iterable data loader is used for
- evaluation. An instance of paddle.io.Dataset or
- paddle.io.Dataloader is recommended.
- batch_size (int, optional): The batch size of train_data and eval_data.
- When eval_data is the instance of Dataloader, this argument will be
- ignored. Default: 1.
- log_freq (int, optional): The frequency, in number of steps, the eval logs
- are printed. Default: 10.
- verbose (int, optional): The verbosity mode, should be 0, 1, or 2. 0 = silent,
- 1 = progress bar, 2 = one line per epoch. Default: 2.
- num_workers (int, optional): The number of subprocess to load data,
- 0 for no subprocess used and loading data in main process. When
- train_data and eval_data are both the instance of Dataloader,
- this parameter will be ignored. Default: 0.
- callbacks (Callback|None, optional): A list of `Callback` instances to apply
- during training. If None, `ProgBarLogger` and `ModelCheckpoint`
- are automatically inserted. Default: None.
- num_iters (int|None, optional): The number of iterations to evaluate the model.
- If None, evaluate on whole input dataset, otherwise, evaluate `num_iters` times.
- Default: None.
- Returns:
- dict: Result of metric. The key is the names of Metric,
- value is a scalar or numpy.array.
- Examples:
- .. code-block:: python
- >>> # doctest: +SKIP('Cause each step's acc and using time are not same when repeat running')
- >>> import paddle
- >>> import paddle.vision.transforms as T
- >>> from paddle.static import InputSpec
- >>> # declarative mode
- >>> transform = T.Compose([T.Transpose(),
- ... T.Normalize([127.5], [127.5])])
- >>> val_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)
- >>> input = InputSpec([-1, 1, 28, 28], 'float32', 'image')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(paddle.vision.models.LeNet(), input, label)
- >>> model.prepare(metrics=paddle.metric.Accuracy())
- >>> result = model.evaluate(val_dataset, batch_size=64)
- >>> print(result)
- {'acc': 0.0699}
- """
- if eval_data is not None and isinstance(eval_data, Dataset):
- eval_sampler = DistributedBatchSampler(
- eval_data, batch_size=batch_size
- )
- eval_loader = DataLoader(
- eval_data,
- batch_sampler=eval_sampler,
- places=self._place,
- num_workers=num_workers,
- return_list=True,
- )
- else:
- eval_loader = eval_data
- self._test_dataloader = eval_loader
- cbks = config_callbacks(
- callbacks,
- model=self,
- log_freq=log_freq,
- verbose=verbose,
- metrics=self._metrics_name(),
- )
- eval_steps = self._len_data_loader(eval_loader)
- self.num_iters = num_iters
- if (
- num_iters is not None
- and isinstance(num_iters, int)
- and isinstance(eval_steps, int)
- ):
- assert num_iters > 0, "num_iters must be greater than 0!"
- eval_steps = min(num_iters, eval_steps)
- self.num_iters = eval_steps
- cbks.on_begin(
- 'eval', {'steps': eval_steps, 'metrics': self._metrics_name()}
- )
- logs = self._run_one_epoch(eval_loader, cbks, 'eval')
- cbks.on_end('eval', logs)
- self._test_dataloader = None
- eval_result = {}
- for k in self._metrics_name():
- eval_result[k] = logs[k]
- return eval_result
- def predict(
- self,
- test_data,
- batch_size=1,
- num_workers=0,
- stack_outputs=False,
- verbose=1,
- callbacks=None,
- ):
- """
- Compute the output predictions on testing data.
- Args:
- test_data (Dataset|DataLoader): An iterable data loader is used for
- predict. An instance of paddle.io.Dataset or paddle.io.Dataloader
- is recommended.
- batch_size (int, optional): The batch size of test_data. When test_data is the
- instance of Dataloader, this argument will be ignored. Default: 1.
- num_workers (int, optional): The number of subprocess to load data, 0 for no subprocess
- used and loading data in main process. When test_data is the instance of Dataloader,
- this argument will be ignored. Default: 0.
- stack_outputs (bool, optional): Whether stack output field like a batch, as for an output
- field of a sample is in shape [X, Y], test_data contains N samples, predict
- output field will be in shape [N, X, Y] if stack_output is True, and will
- be a length N list in shape [[X, Y], [X, Y], ..., [X, Y]] if stack_outputs
- is False. stack_outputs as False is used for LoDTensor output situation,
- it is recommended set as True if outputs contains no LoDTensor. Default: False.
- verbose (int, optional): The verbosity mode, should be 0, 1, or 2. 0 = silent,
- 1 = progress bar, 2 = one line per batch. Default: 1.
- callbacks(Callback, optional): A Callback instance, Default: None.
- Returns:
- list: output of models.
- Examples:
- .. code-block:: python
- >>> import numpy as np
- >>> import paddle
- >>> from paddle.static import InputSpec
- >>> class MnistDataset(paddle.vision.datasets.MNIST):
- ... def __init__(self, mode, return_label=True):
- ... super().__init__(mode=mode)
- ... self.return_label = return_label
- ...
- ... def __getitem__(self, idx):
- ... img = np.reshape(self.images[idx], [1, 28, 28])
- ... if self.return_label:
- ... return img, np.array(self.labels[idx]).astype('int64')
- ... return img
- ...
- ... def __len__(self):
- ... return len(self.images)
- ...
- >>> test_dataset = MnistDataset(mode='test', return_label=False)
- >>> # imperative mode
- >>> input = InputSpec([-1, 1, 28, 28], 'float32', 'image')
- >>> model = paddle.Model(paddle.vision.models.LeNet(), input)
- >>> model.prepare()
- >>> result = model.predict(test_dataset, batch_size=64)
- >>> print(len(result[0]), result[0][0].shape)
- 157 (64, 10)
- >>> # declarative mode
- >>> device = paddle.set_device('cpu')
- >>> paddle.enable_static()
- >>> input = InputSpec([-1, 1, 28, 28], 'float32', 'image')
- >>> model = paddle.Model(paddle.vision.models.LeNet(), input)
- >>> model.prepare()
- >>> result = model.predict(test_dataset, batch_size=64)
- >>> print(len(result[0]), result[0][0].shape)
- 157 (64, 10)
- """
- if test_data is not None and isinstance(test_data, Dataset):
- test_sampler = DistributedBatchSampler(
- test_data, batch_size=batch_size
- )
- test_loader = DataLoader(
- test_data,
- batch_sampler=test_sampler,
- places=self._place,
- num_workers=num_workers,
- return_list=True,
- )
- else:
- test_loader = test_data
- self._test_dataloader = test_loader
- cbks = config_callbacks(callbacks, model=self, verbose=verbose)
- test_steps = self._len_data_loader(test_loader)
- logs = {'steps': test_steps}
- cbks.on_begin('predict', logs)
- outputs = []
- logs, outputs = self._run_one_epoch(test_loader, cbks, 'predict')
- outputs = list(zip(*outputs))
- # NOTE: for lod tensor output, we should not stack outputs
- # for stacking may lose its detail info
- if stack_outputs:
- outputs = [np.vstack(outs) for outs in outputs]
- self._test_dataloader = None
- cbks.on_end('predict', logs)
- return outputs
- def _save_inference_model(self, path):
- """
- Save inference model can be used in static or dynamic mode.
- Args:
- path (str): The path prefix to save model. The format is
- ``dirname/file_prefix`` or ``file_prefix``.
- Returns:
- None
- """
- if in_dynamic_mode():
- with base.framework._dygraph_guard(None):
- layer = self.network
- if self._input_info is None: # No provided or inferred
- raise RuntimeError(
- "Saving inference model needs 'inputs' or running before saving. Please specify 'inputs' in Model initialization or input training data and perform a training for shape derivation."
- )
- if self._is_shape_inferred:
- warnings.warn(
- "'inputs' was not specified when Model initialization, so the input shape to be saved will be the shape derived from the user's actual inputs. The input shape to be saved is %s. For saving correct input shapes, please provide 'inputs' for Model initialization."
- % self._input_info[0]
- )
- paddle.jit.save(layer, path, input_spec=self._inputs)
- else:
- # Path check
- file_prefix = os.path.basename(path)
- if file_prefix == "":
- raise ValueError(
- "The input path MUST be format of dirname/file_prefix "
- "[dirname\\file_prefix in Windows system], but received "
- "file_prefix is empty string."
- )
- dirname = os.path.dirname(path)
- if dirname and not os.path.exists(dirname):
- os.makedirs(dirname)
- model_path = dirname
- model_filename = file_prefix + INFER_MODEL_SUFFIX
- params_filename = file_prefix + INFER_PARAMS_SUFFIX
- prog = self._adapter._progs.get('test', None)
- assert (
- prog
- ), "Model is not ready, please call `model.prepare()` first"
- infer_prog = prog.clone(for_test=True)
- inputs = list(self._adapter._input_vars['test'])
- endpoints = self._adapter._endpoints['test']['output']
- paddle.static.save_inference_model(
- model_path,
- inputs,
- endpoints,
- self._adapter._executor,
- program=infer_prog,
- )
- def _run_one_epoch(
- self,
- data_loader,
- callbacks,
- mode,
- logs={},
- ):
- outputs = []
- for step, data in enumerate(data_loader):
- # Data might come from different types of data_loader and have
- # different format, as following:
- # 1. DataLoader in static graph:
- # [[input1, input2, ..., label1, label2, ...]]
- # 2. DataLoader in dygraph
- # [input1, input2, ..., label1, label2, ...]
- # 3. custumed iterator yield concated inputs and labels:
- # [input1, input2, ..., label1, label2, ...]
- # 4. custumed iterator yield separated inputs and labels:
- # ([input1, input2, ...], [label1, label2, ...])
- # To handle all of these, flatten (nested) list to list.
- data = paddle.utils.flatten(data)
- # LoDTensor.shape is callable, where LoDTensor comes from
- # DataLoader in static graph
- batch_size = (
- data[0].shape()[0]
- if callable(data[0].shape)
- else data[0].shape[0]
- )
- callbacks.on_batch_begin(mode, step, logs)
- if mode != 'predict':
- _inputs = [data[: len(self._inputs)], data[len(self._inputs) :]]
- if mode == 'train':
- _inputs.append(
- (step + 1) % self._accumulate == 0
- or step + 1 == len(data_loader)
- )
- outs = getattr(self, mode + '_batch')(*_inputs)
- if self._metrics and self._loss:
- metrics = [[float(l) for l in outs[0]]]
- elif self._loss:
- metrics = [[float(l) for l in outs]]
- else:
- metrics = []
- # metrics
- for metric in self._metrics:
- res = metric.accumulate()
- metrics.extend(to_list(res))
- assert len(self._metrics_name()) == len(metrics)
- for k, v in zip(self._metrics_name(), metrics):
- logs[k] = v
- else:
- if self._inputs is not None:
- outs = self.predict_batch(data[: len(self._inputs)])
- else:
- outs = self.predict_batch(data)
- outputs.append(outs)
- logs['step'] = step
- if (
- mode == 'train'
- or self._adapter._merge_count.get(mode + '_batch', 0) <= 0
- ):
- logs['batch_size'] = (
- batch_size * paddle.distributed.ParallelEnv().nranks
- )
- else:
- logs['batch_size'] = self._adapter._merge_count[mode + '_batch']
- callbacks.on_batch_end(mode, step, logs)
- if hasattr(self, 'num_iters') and self.num_iters is not None:
- self.num_iters -= 1
- if self.num_iters <= 0:
- self.stop_training = True
- del self.num_iters
- break
- self._reset_metrics()
- if mode == 'predict':
- return logs, outputs
- return logs
- def summary(self, input_size=None, dtype=None):
- """Prints a string summary of the network.
- Args:
- input_size (tuple|InputSpec|list[tuple|InputSpec], optional): Size of input tensor.
- if not set, input_size will get from ``self._inputs`` if network only have
- one input, input_size can be tuple or InputSpec. if model have multiple
- input, input_size must be a list which contain every input's shape. Default: None.
- dtype (str, optional): If dtype is None, 'float32' will be used, Default: None.
- Returns:
- Dict: A summary of the network including total params and total trainable params.
- Examples:
- .. code-block:: python
- >>> import paddle
- >>> from paddle.static import InputSpec
- >>> input = InputSpec([None, 1, 28, 28], 'float32', 'image')
- >>> label = InputSpec([None, 1], 'int64', 'label')
- >>> model = paddle.Model(paddle.vision.models.LeNet(), input, label)
- >>> optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters())
- >>> model.prepare(optim, paddle.nn.CrossEntropyLoss())
- >>> params_info = model.summary()
- >>> print(params_info)
- ---------------------------------------------------------------------------
- Layer (type) Input Shape Output Shape Param #
- ===========================================================================
- Conv2D-1 [[1, 1, 28, 28]] [1, 6, 28, 28] 60
- ReLU-1 [[1, 6, 28, 28]] [1, 6, 28, 28] 0
- MaxPool2D-1 [[1, 6, 28, 28]] [1, 6, 14, 14] 0
- Conv2D-2 [[1, 6, 14, 14]] [1, 16, 10, 10] 2,416
- ReLU-2 [[1, 16, 10, 10]] [1, 16, 10, 10] 0
- MaxPool2D-2 [[1, 16, 10, 10]] [1, 16, 5, 5] 0
- Linear-1 [[1, 400]] [1, 120] 48,120
- Linear-2 [[1, 120]] [1, 84] 10,164
- Linear-3 [[1, 84]] [1, 10] 850
- ===========================================================================
- Total params: 61,610
- Trainable params: 61,610
- Non-trainable params: 0
- ---------------------------------------------------------------------------
- Input size (MB): 0.00
- Forward/backward pass size (MB): 0.11
- Params size (MB): 0.24
- Estimated Total Size (MB): 0.35
- ---------------------------------------------------------------------------
- {'total_params': 61610, 'trainable_params': 61610}
- """
- assert (
- input_size is not None or self._inputs is not None
- ), "'input_size' or 'self._input' must be set"
- if input_size is not None:
- _input_size = input_size
- else:
- _input_size = self._inputs
- return summary(self.network, _input_size, dtypes=dtype)
- def _verify_spec(self, specs, shapes=None, dtypes=None, is_input=False):
- out_specs = []
- if specs is None:
- # Note(Aurelius84): If not specific specs of `Input`, using argument names of `forward` function
- # to generate `Input`. But how can we know the actual shape of each input tensor?
- if is_input:
- arg_names = extract_args(self.network.forward)[1:]
- # While Saving inference model in dygraph, and providing inputs only in running.
- if (
- shapes is not None
- and dtypes is not None
- and in_dynamic_mode()
- ):
- out_specs = [
- Input(name=n, dtype=dtypes[i], shape=shapes[i])
- for i, n in enumerate(arg_names)
- ]
- else:
- out_specs = [Input(name=n, shape=[None]) for n in arg_names]
- else:
- out_specs = to_list(specs)
- elif isinstance(specs, dict):
- assert is_input is False
- out_specs = [
- specs[n]
- for n in extract_args(self.network.forward)
- if n != 'self'
- ]
- else:
- out_specs = to_list(specs)
- # Note: checks each element has specified `name`.
- if out_specs is not None:
- for i, spec in enumerate(out_specs):
- assert isinstance(spec, Input)
- if spec.name is None:
- raise ValueError(
- f"Requires Input[{i}].name != None, but receive `None` with {spec}."
- )
- return out_specs
- def _reset_metrics(self):
- for metric in self._metrics:
- metric.reset()
- def _metrics_name(self):
- metrics_name = ['loss'] if self._loss else []
- for m in self._metrics:
- metrics_name.extend(to_list(m.name()))
- return metrics_name
- def _len_data_loader(self, data_loader):
- try:
- steps = len(data_loader)
- except Exception:
- steps = None
- return steps
- def _update_inputs(self):
- "Update self._inputs according to given inputs."
- self._input_info = self._adapter._input_info
- if self._input_info is not None and len(self._input_info) == 2:
- self._inputs = self._verify_spec(
- None, self._input_info[0], self._input_info[1], True
- )
- self._is_shape_inferred = True
|