HandX-demo / src /diffusion /utils /parser_util.py
xusirui's picture
HandX text-to-motion Gradio demo
2680bd5 verified
Raw
History Blame Contribute Delete
14.5 kB
import argparse
from argparse import ArgumentParser
import json
from pathlib import Path
def parse_and_load_from_json(parser):
'''
从 model_path 下的 args.json 文件中加载参数,并将其覆盖到用户指定的参数中。
'''
add_data_options(parser)
add_model_options(parser)
add_diffusion_options(parser)
args = parser.parse_args() # 部分参数由用户指定
args_to_overwrite = []
for group_name in ['dataset', 'model', 'diffusion']:
args_to_overwrite += get_args_per_group_name(parser, args, group_name)
model_path = get_model_path_from_args()
args_path = (Path(model_path).parent / 'args.json').as_posix()
assert Path(args_path).exists(), f'{args_path} does not exist.'
with open(args_path, 'r') as f:
model_args = json.load(f) # 从模型路径下的 args.json 文件中加载参数
for a in args_to_overwrite: # 将模型路径下的 args.json 覆盖到用户指定的参数上
if a in model_args.keys():
setattr(args, a, model_args[a])
elif a == 'cond_mode':
unconstrained = (model_args['cond_mode'] == 'no_cond')
setattr(args, 'unconstrained', unconstrained)
# else:
# raise Warning(f"was not able to load {a}, using default value {getattr(args, a)} instead.")
if args.cond_mask_prob == 0:
args.guidance_param = 1.0
return apply_rules(args)
def get_args_per_group_name(parser:ArgumentParser, args, group_name): # 根据指定的参数组名称 group_name,提取并返回该参数组内所有参数的`名称列表`
for group in parser._action_groups:
if group.title == group_name:
group_dict = {a.dest: getattr(args, a.dest, None) for a in group._group_actions}
return list(argparse.Namespace(**group_dict).__dict__.keys())
raise ValueError("group_name was not found")
def get_model_path_from_args():
try:
dummy_parser = ArgumentParser()
dummy_parser.add_argument("model_path")
dummy_args, _ = dummy_parser.parse_known_args() # 如果它遇到未在 dummy_parser 中定义的参数,它不会报错并退出程序,会返回一个包含(已识别参数,未识别参数)的元组
return dummy_args.model_path
except:
raise ValueError("model_path argument must be specified")
def add_base_options(parser:ArgumentParser):
group = parser.add_argument_group("base")
group.add_argument("--seed", default=10, type=int, help='For fixing random seed')
def add_diffusion_options(parser:ArgumentParser):
group = parser.add_argument_group("diffusion")
group.add_argument("--noise_schedule", default='cosine', choices=['linear', 'cosine'], type=str, help='Noise schedule type')
group.add_argument("--diffusion_steps", default=1000, type=int, help='Number of diffusion steps (denoted T in the paper)')
group.add_argument("--sigma_small", default=True, type=bool, help='Use smaller sigma values.')
def add_model_options(parser:ArgumentParser):
group = parser.add_argument_group('model')
group.add_argument("--arch", default='trans_enc', choices=['trans_enc', 'trans_dec', 'gru'], type=str, help='Architecture types as reported in the paper') # GRU: Gated Recurrent Unit
group.add_argument("--emb_trans_dec", default=False, type=bool, help='For trans_dec architecture only, if true, will inject condition as a class token (in addition to cross_attention).')
group.add_argument("--layers", default=8, type=int, help='Number of layers')
group.add_argument("--latent_dim", default=512, type=int, help='Transformer/GRU width')
group.add_argument("--text_model", default='openai/clip-vit-base-patch32', type=str, choices=['t5-small', 't5-large', 't5-base', 'openai/clip-vit-base-patch32'], help='the text encoder model to use.')
group.add_argument("--max_text_length", default=None, type=int, help='Maximum text length. If the text is longer, it will be truncated.')
group.add_argument("--num_actions", default=12, type=int, help='Number of actions in the dataset. If the dataset does not have actions, this parameter will be ignored.')
group.add_argument("--cond_mode", default='no_cond', type=str, choices=['no_cond', "text", 'action'], help='Conditioning mode.')
group.add_argument("--cond_mask_prob", default=0.1, type=float, help='The probability of masking the condition during training. For classifier-free guidance learning.')
group.add_argument("--lambda_rcxyz", default=0.0, type=float, help='Joint positions loss.')
group.add_argument("--lambda_vel", default=0.0, type=float, help='Joint velocity loss.')
group.add_argument("--lambda_fc", default=0.0, type=float, help="Foot contact loss.")
group.add_argument("--lambda_ig", default=0.0, type=float, help='interaction graph loss.')
group.add_argument("--lambda_w_ig", default=0.0, type=float, help='weighted interaction graph loss. (ReMos)')
# group.add_argument("--unconstrained", action='store_true', help='Model is trained unconditionally. That is, it is constrained by neither text nor action.')
group.add_argument("--num_heads", default=4, type=int, help='Number of attention heads')
group.add_argument("--ff_size", default=1024, type=int, help='Feed forward size')
group.add_argument("--dropout", default=0.1, type=float, help='Dropout rate')
group.add_argument("--activation", default='gelu', type=str, help='Activation function to use', choices=['relu', 'gelu', 'swish'])
def add_data_options(parser:ArgumentParser):
group = parser.add_argument_group('dataset')
group.add_argument("--dataset", default='interhand2-6m_bi_hand', type=str, choices=['interhand2-6m_bi_hand', 'gigahands_bi_hand_text', 'snaphands_bi_hand_text', 'snaphands_bi_hand_action'], help='Dataset name')
group.add_argument("--data_dir", default='data', type=str, help='Path to the dataset directory')
group.add_argument("--repr", default='joint_pos', type=str, choices=['joint_pos', 'joint_rot'], help='Representation of the motion data.')
group.add_argument("--difference", action='store_true', help='If True, will use difference representation of the motion data.')
# group.add_argument("--num_actions", default=12, type=int)
# group.add_argument("--augment", default=False, action='store_true', help='If True, will apply data augmentation.')
group.add_argument("--fixed_length", default=0, type=int, help='If > 0, will use fixed length for the motion.')
group.add_argument("--max_length", default=60, type=int, help='If fixed_length is set to 0, will use this value as the maximum length of the motion. If fixed_length is set to > 0, this value will be ignored.')
group.add_argument("--min_length", default=20, type=int, help='If fixed_length is set to 0, will use this value as the minimum length of the motion. If fixed_length is set to > 0, this value will be ignored.')
group.add_argument("--unit_length", default=4, type=int, help='If fixed_length is set to 0, will use this value as the unit length of the motion. If fixed_length is set to > 0, this value will be ignored.')
group.add_argument("--normalize", type=bool, default=True, help='If True, will normalize the motion data.')
group.add_argument("--random_shift", type=bool, default=True, help='If True, will apply random shift to the motion data.')
group.add_argument("--num_workers", default=8, type=int, help='Number of workers for data loading')
def add_training_options(parser:ArgumentParser):
group = parser.add_argument_group("training")
group.add_argument("--save_dir", required=True, type=str, help='Path to save checkpoints and results')
group.add_argument("--overwrite", action='store_true', help='If True, will enable to use an already existing save_dir.')
group.add_argument("--train_platform_type", default='NoPlatform', choices=['NoPlatform', 'WandbPlatform', 'TensorboardPlatform'], type=str, help='Choose platform to log results. NoPlatform means no logging.')
group.add_argument("--batch_size", default=64, type=int, help='Batch size during training')
group.add_argument("--lr", default=1e-4, type=float, help='Learning rate.')
group.add_argument("--weight_decay", default=0.0, type=float, help='Optimizer weight decay.')
group.add_argument("--eval_batch_size", default=32, type=int, help='Batch size during evaluation loop. Do not change this unless you know what you are doing. T2m Precision calculation is based on fixed batch size 32.')
group.add_argument("--eval_split", default='test', choices=['val', 'test'], type=str, help='Which split to evalutate on during training.')
group.add_argument("--eval_during_training", action='store_true', help='If true, will run evaluation during training.')
group.add_argument("--viz_during_training", action='store_true', help='If true, will run sample & visualization during training.')
group.add_argument("--val_during_training", action='store_true', help='If true, will calculate validation loss during training.')
group.add_argument("--eval_rep_times", default=3, type=int, help='Number of repetitions for evaluation loop during training.')
group.add_argument("--eval_num_samples", default=1_000, type=int, help='If -1, will use all samples in the specified split.')
group.add_argument("--log_interval", default=1_000, type=int, help='Log losses each N steps.')
group.add_argument("--save_interval", default=50_000, type=int, help='Save checkpoints and run evaluation each N steps.')
group.add_argument("--val_interval", default=10_000, type=int, help='Run validation each N steps. If val_during_training is True, will run validation each N steps.')
group.add_argument("--num_steps", default=2400_000, type=int, help='Training will stop after the specified number of steps.')
group.add_argument("--resume_checkpoint", default='', type=str, help='If not empty, will start from the specified checkpoint (path to model###.pt file).')
group.add_argument("--guidance_param", default=1, type=float, help='For classifier_free sampling - specified the s parameter, as defined in the paper.')
group.add_argument("--viz_denoising_steps", type=int, nargs='*', default=[])
group.add_argument("--viz_samples_count", type=int, default=1, help='How many samples each process visualizes every time?')
def add_sampling_options(parser:ArgumentParser):
group = parser.add_argument_group("sampling")
group.add_argument("--model_path", required=True, type=str, help='Path to model###.pt file to be sampled.')
group.add_argument("--output_dir", default='', type=str, help='Path to results dir (auto cereated by the script). If empty, will create dir in parallel to checkpoint.')
group.add_argument("--num_samples", default=10, type=int, help='Maximal number of prompts to sample, if loading dataset from file, this field will be ignored.')
group.add_argument("--guidance_param", default=1, type=float, help='For classifier_free sampling - specified the s parameter, as defined in the paper.')
# group.add_argument("--num_repetitions", default=3, type=int, help='Number of repetitions, per sample (text prompt/action)')
def add_generate_options(parser:ArgumentParser):
group = parser.add_argument_group('generate')
group.add_argument("--motion_length", default=60, type=int, help='The length of the sampled motion [in seconds].')
group.add_argument("--input_text", default='', type=str, help='Path to a text file lists text prompts to be synthesized. If empty, will take text prompts from dataset.')
group.add_argument("--text_prompt", default='', type=str, help='A text prompt to be generated. If empty, will take text prompts from dataset.')
group.add_argument("--action_name", default='', type=str, help='An action name to be generated. If empty, will take text prompts from dataset.')
def add_evaluation_options(parser:ArgumentParser):
group = parser.add_argument_group('eval')
group.add_argument("--model_path", type=str, default=None, help='Path to model###.pt file to be sampled.')
group.add_argument("--model_family_path", type=str, default=None, help='Path to model folder to be sampled.')
group.add_argument("--eval_mode", default='wo_mm', choices=['wo_mm', 'mm_short', 'debug', 'full'], type=str, help="wo_mm (t2m only) - 20 repetitions without multi-modality metric; "
"mm_short (t2m only) - 5 repetitions with multi-modality metric; "
"debug - short run, less accurate results."
"full (a2m only) - 20 repetitions.")
def train_args():
parser = ArgumentParser()
add_base_options(parser)
add_data_options(parser)
add_model_options(parser)
add_diffusion_options(parser)
add_training_options(parser)
return apply_rules(parser.parse_args())
def get_cond_mode(args):
return args.cond_mode
def generate_args():
parser = ArgumentParser()
# 只有 base、sampling、generate 三个参数组是用户指定的,其他参数组的参数会从模型路径下的 args.json 文件中加载并覆盖用户指定的参数
add_base_options(parser)
add_sampling_options(parser)
add_generate_options(parser)
args = parse_and_load_from_json(parser)
cond_mode = get_cond_mode(args)
if (args.input_text or args.text_prompt) and cond_mode != 'text':
raise ValueError("Specified text prompt or input text, but the cond_mode is not set to 'text")
return args
def evaluation_parser():
parser = ArgumentParser()
# 只有 base、evaluation 两个参数组是用户指定的,其他参数组的参数会从模型路径下的 args.json 文件中加载并覆盖用户指定的参数
add_base_options(parser)
add_evaluation_options(parser)
return parse_and_load_from_json(parser)
def apply_rules(args):
if args.fixed_length > 0:
del args.max_length
del args.min_length
del args.unit_length
if hasattr(args, 'val_during_training') and not args.val_during_training:
del args.val_interval
if args.cond_mode == 'no_cond':
del args.cond_mask_prob
del args.max_text_length
del args.text_model
del args.num_actions
elif args.cond_mode == 'text':
del args.num_actions
elif args.cond_mode == 'action':
del args.max_text_length
del args.text_model
return args