167 lines
6.7 KiB
Python
167 lines
6.7 KiB
Python
|
import torch
|
|||
|
import torch.nn as nn
|
|||
|
from easymocap.config import Config, load_object
|
|||
|
from easymocap.mytools.debug_utils import log
|
|||
|
|
|||
|
def dict_of_numpy_to_tensor(body_params, device):
|
|||
|
params_ = {}
|
|||
|
for key, val in body_params.items():
|
|||
|
if isinstance(val, dict):
|
|||
|
params_[key] = dict_of_numpy_to_tensor(val, device)
|
|||
|
else:
|
|||
|
params_[key] = torch.Tensor(val).to(device)
|
|||
|
return params_
|
|||
|
|
|||
|
def dict_of_tensor_to_numpy(body_params):
|
|||
|
params_ = {}
|
|||
|
for key, val in body_params.items():
|
|||
|
if isinstance(val, dict):
|
|||
|
params_[key] = dict_of_tensor_to_numpy(val)
|
|||
|
else:
|
|||
|
params_[key] = val.cpu().numpy()
|
|||
|
return params_
|
|||
|
|
|||
|
def make_optimizer(opt_params, optim_type='lbfgs', max_iter=20,
|
|||
|
lr=1e-3, betas=(0.9, 0.999), weight_decay=0.0, **kwargs):
|
|||
|
if isinstance(opt_params, dict):
|
|||
|
# LBFGS 不支持参数字典
|
|||
|
opt_params = list(opt_params.values())
|
|||
|
if optim_type == 'lbfgs':
|
|||
|
# optimizer = torch.optim.LBFGS(
|
|||
|
# opt_params, max_iter=max_iter, lr=lr, line_search_fn='strong_wolfe',
|
|||
|
# tolerance_grad= 0.0000001, # float32的有效位数是7位
|
|||
|
# tolerance_change=0.0000001,
|
|||
|
# )
|
|||
|
from easymocap.pyfitting.lbfgs import LBFGS
|
|||
|
optimizer = LBFGS(opt_params, line_search_fn='strong_wolfe', max_iter=max_iter,
|
|||
|
tolerance_grad= 0.0000001, # float32的有效位数是7位
|
|||
|
tolerance_change=0.0000001,
|
|||
|
**kwargs)
|
|||
|
elif optim_type == 'adam':
|
|||
|
optimizer = torch.optim.Adam(opt_params, lr=lr, betas=betas, weight_decay=weight_decay)
|
|||
|
else:
|
|||
|
raise NotImplementedError
|
|||
|
return optimizer
|
|||
|
|
|||
|
def grad_require(params, flag=False):
|
|||
|
if isinstance(params, list):
|
|||
|
for par in params:
|
|||
|
par.requires_grad = flag
|
|||
|
elif isinstance(params, dict):
|
|||
|
for key, par in params.items():
|
|||
|
par.requires_grad = flag
|
|||
|
|
|||
|
def make_closure(optimizer, model, params, infos, loss, device):
|
|||
|
loss_func = {}
|
|||
|
for key, val in loss.items():
|
|||
|
loss_func[key] = load_object(val['module'], val['args'])
|
|||
|
if isinstance(loss_func[key], nn.Module):
|
|||
|
loss_func[key].to(device)
|
|||
|
|
|||
|
def closure(debug=False):
|
|||
|
optimizer.zero_grad()
|
|||
|
new_params = params.copy()
|
|||
|
output = model(new_params)
|
|||
|
loss_dict = {}
|
|||
|
loss_weight = {key:loss[key].weight for key in loss_func.keys()}
|
|||
|
for key, func in loss_func.items():
|
|||
|
output_ = {k: output[k] for k in loss[key].key_from_output}
|
|||
|
infos_ = {k: infos[k] for k in loss[key].key_from_infos}
|
|||
|
loss_now = func(output_, infos_)
|
|||
|
if isinstance(loss_now, dict):
|
|||
|
for k, _loss in loss_now.items():
|
|||
|
loss_dict[key+'_'+k] = _loss
|
|||
|
loss_weight[key+'_'+k] = loss_weight[key]
|
|||
|
loss_weight.pop(key)
|
|||
|
else:
|
|||
|
loss_dict[key] = loss_now
|
|||
|
loss_sum = sum([loss_dict[key]*loss_weight[key]
|
|||
|
for key in loss_dict.keys()])
|
|||
|
# for key in loss_dict.keys():
|
|||
|
# print(key, loss_dict[key] * loss_weight[key])
|
|||
|
# print(loss_sum)
|
|||
|
if debug:
|
|||
|
return loss_dict, loss_weight
|
|||
|
loss_sum.backward()
|
|||
|
return loss_sum
|
|||
|
return closure
|
|||
|
|
|||
|
def rel_change(prev_val, curr_val):
|
|||
|
return (prev_val - curr_val) / max([1e-5, abs(prev_val), abs(curr_val)])
|
|||
|
|
|||
|
class Optimizer:
|
|||
|
def __init__(self, optimize_keys, optimizer_args, loss) -> None:
|
|||
|
self.optimize_keys = optimize_keys
|
|||
|
self.optimizer_args = optimizer_args
|
|||
|
self.loss = loss
|
|||
|
self.used_infos = []
|
|||
|
for key, val in loss.items():
|
|||
|
self.used_infos.extend(val.key_from_infos)
|
|||
|
self.used_infos = list(set(self.used_infos))
|
|||
|
|
|||
|
def log_loss(self, iter_, closure, print_loss=False):
|
|||
|
if iter_ % 10 == 0 or print_loss:
|
|||
|
with torch.no_grad():
|
|||
|
loss_dict, loss_weight = closure(debug=True)
|
|||
|
print('{:-6d}: '.format(iter_) + ' '.join([key + ' %7.4f'%(loss_dict[key].item()*loss_weight[key]) for key in loss_dict.keys()]))
|
|||
|
|
|||
|
def optimizer_step(self, optimizer, closure):
|
|||
|
prev_loss = None
|
|||
|
self.log_loss(0, closure, True)
|
|||
|
for iter_ in range(1, 1000):
|
|||
|
loss = optimizer.step(closure)
|
|||
|
# check the loss
|
|||
|
if torch.isnan(loss).sum() > 0:
|
|||
|
print('[optimize] NaN loss value, stopping!')
|
|||
|
break
|
|||
|
if torch.isinf(loss).sum() > 0:
|
|||
|
print('[optimize] Infinite loss value, stopping!')
|
|||
|
break
|
|||
|
# check the delta
|
|||
|
if iter_ > 0 and prev_loss is not None:
|
|||
|
loss_rel_change = rel_change(prev_loss, loss.item())
|
|||
|
if loss_rel_change <= 0.0000001:
|
|||
|
break
|
|||
|
self.log_loss(iter_, closure)
|
|||
|
prev_loss = loss.item()
|
|||
|
self.log_loss(iter_, closure, True)
|
|||
|
return True
|
|||
|
|
|||
|
def __call__(self, params, model, **infos):
|
|||
|
"""
|
|||
|
待优化变量一定要在params中,但params中不一定会被优化
|
|||
|
infos中的变量不一定会被优化
|
|||
|
"""
|
|||
|
# TODO: 应该使用model的device,但考虑到model可能是一个函数,所以暂时当场计算
|
|||
|
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
|
|||
|
params = dict_of_numpy_to_tensor(params, device=device)
|
|||
|
infos_used = {key: infos[key] for key in self.used_infos if key in infos.keys()}
|
|||
|
infos_used = dict_of_numpy_to_tensor(infos_used, device=device)
|
|||
|
|
|||
|
log('[{}] Optimize {}'.format(self.__class__.__name__, self.optimize_keys))
|
|||
|
log('[{}] Loading {}'.format(self.__class__.__name__, self.used_infos))
|
|||
|
opt_params = {}
|
|||
|
for key in self.optimize_keys:
|
|||
|
if key in infos.keys(): # 优化的参数
|
|||
|
opt_params[key] = infos_used[key]
|
|||
|
elif key in params.keys():
|
|||
|
opt_params[key] = params[key]
|
|||
|
else:
|
|||
|
raise ValueError('{} is not in infos or body_params'.format(key))
|
|||
|
for key, val in opt_params.items():
|
|||
|
infos_used['init_'+key] = val.clone()
|
|||
|
optimizer = make_optimizer(opt_params, **self.optimizer_args)
|
|||
|
closure = make_closure(optimizer, model, params, infos_used, self.loss, device)
|
|||
|
# 准备开始优化
|
|||
|
grad_require(opt_params, True)
|
|||
|
self.optimizer_step(optimizer, closure)
|
|||
|
grad_require(opt_params, False)
|
|||
|
# 直接返回
|
|||
|
ret = {
|
|||
|
'params': params
|
|||
|
}
|
|||
|
for key in self.optimize_keys:
|
|||
|
if key in infos.keys():
|
|||
|
ret[key] = opt_params[key]
|
|||
|
ret = dict_of_tensor_to_numpy(ret)
|
|||
|
return ret
|