| import os, torch, numpy as np, pickle, argparse |
| from copy import deepcopy |
| from itertools import repeat |
| from tqdm import tqdm |
| from einops import rearrange |
| import matplotlib |
| matplotlib.use('Agg') |
| import matplotlib.pyplot as plt |
| import time |
| from torchvision import transforms |
| from ModelTrain.module.utils import load_data |
| from ModelTrain.module.utils import compute_dict_mean, set_seed, detach_dict, calibrate_linear_vel, postprocess_base_action |
| from ModelTrain.module.policy import ACTPolicy, CNNMLPPolicy, DiffusionPolicy |
| from ModelTrain.module.policy_with_hsa import ACTPolicyWithHSA, create_default_hsa_config |
| from ModelTrain.module.policy_jepa_adapter_with_hsa import ACTJEPAHsa, create_default_hsa_config as create_hsa_config_jepa |
| import IPython |
| e = IPython.embed |
|
|
| def get_auto_index(dataset_dir): |
| max_idx = 1000 |
| for i in range(max_idx + 1): |
| if not os.path.isfile(os.path.join(dataset_dir, f"qpos_{i}.npy")): |
| return i |
| else: |
| raise Exception(f"Error getting auto index, or more than {max_idx} episodes") |
|
|
|
|
| def train(args): |
| set_seed(1) |
| ckpt_dir = args["ckpt_dir"] |
| policy_class = args.get("policy_class", "ACT") |
| task_name = args["task_name"] |
| batch_size_train = args["batch_size"] |
| batch_size_val = args["batch_size"] |
| num_steps = args["num_steps"] |
| eval_every = args["eval_every"] |
| validate_every = args["validate_every"] |
| save_every = args["save_every"] |
| resume_ckpt_path = args["resume_ckpt_path"] |
| from ModelTrain.constants import TASK_CONFIGS |
| task_config = TASK_CONFIGS[task_name] |
| dataset_dir = task_config["dataset_dir"] |
| episode_len = task_config["episode_len"] |
| camera_names = task_config["camera_names"] |
| tactile_camera_names = task_config.get("tactile_camera_names", []) |
| stats_dir = task_config.get("stats_dir", None) |
| sample_weights = task_config.get("sample_weights", None) |
| train_ratio = task_config.get("train_ratio", 0.99) |
| name_filter = task_config.get("name_filter", lambda n: True) |
| state_dim = task_config.get('state_dim', 14) |
| action_dim = task_config.get('action_dim', 16) |
| lr_backbone = 1e-05 |
| backbone = "resnet18" |
| if policy_class == "ACT": |
| enc_layers = 4 |
| dec_layers = 7 |
| nheads = 8 |
| policy_config = {'lr':args["lr"], 'num_queries':args["chunk_size"], |
| 'kl_weight':args["kl_weight"], |
| 'hidden_dim':args["hidden_dim"], |
| 'dim_feedforward':args["dim_feedforward"], |
| 'lr_backbone':lr_backbone, |
| 'backbone':backbone, |
| 'enc_layers':enc_layers, |
| 'dec_layers':dec_layers, |
| 'nheads':nheads, |
| 'camera_names':camera_names, |
| 'vq':False, |
| 'vq_class':None, |
| 'vq_dim':None, |
| 'action_dim':action_dim, |
| 'no_encoder':args["no_encoder"]} |
| elif policy_class == "ACTJEPA": |
| enc_layers = 4 |
| dec_layers = 7 |
| nheads = 8 |
| |
| vit_ckpt = args.get("vit_ckpt_path") or args.get("vitg_ckpt_path") |
| policy_config = {'lr':args["lr"], 'num_queries':args["chunk_size"], |
| 'kl_weight':args["kl_weight"], |
| 'hidden_dim':args["hidden_dim"], |
| 'dim_feedforward':args["dim_feedforward"], |
| 'lr_backbone':lr_backbone, |
| 'backbone':backbone, |
| 'enc_layers':enc_layers, |
| 'dec_layers':dec_layers, |
| 'nheads':nheads, |
| 'camera_names':camera_names, |
| 'tactile_camera_names':tactile_camera_names, |
| 'vq':False, |
| 'vq_class':None, |
| 'vq_dim':None, |
| 'action_dim':action_dim, |
| 'no_encoder':args["no_encoder"], |
| 'use_vitg':True, |
| 'vitg_ckpt_path':vit_ckpt, |
| 'vit_model':args.get("vit_model", "vitg")} |
| elif policy_class == "ACTJEPAAdapter": |
| enc_layers = 4 |
| dec_layers = 7 |
| nheads = 8 |
| |
| vit_ckpt = args.get("vit_ckpt_path") or args.get("vitg_ckpt_path") |
| policy_config = {'lr':args["lr"], 'num_queries':args["chunk_size"], |
| 'kl_weight':args["kl_weight"], |
| 'hidden_dim':args["hidden_dim"], |
| 'dim_feedforward':args["dim_feedforward"], |
| 'lr_backbone':lr_backbone, |
| 'backbone':backbone, |
| 'enc_layers':enc_layers, |
| 'dec_layers':dec_layers, |
| 'nheads':nheads, |
| 'camera_names':camera_names, |
| 'tactile_camera_names':tactile_camera_names, |
| 'vq':False, |
| 'vq_class':None, |
| 'vq_dim':None, |
| 'action_dim':action_dim, |
| 'no_encoder':args["no_encoder"], |
| 'use_vitg':True, |
| 'vitg_ckpt_path':vit_ckpt, |
| 'vit_model':args.get("vit_model", "vitg"), |
| 'adapter_hidden_dim':args.get("adapter_hidden_dim", 512), |
| 'adapter_depth':args.get("adapter_depth", 3), |
| 'adapter_dropout':args.get("adapter_dropout", 0.1), |
| 'adapter_scale_init':args.get("adapter_scale_init", 0.1), |
| 'adapter_pooling':args.get("adapter_pooling", "attention")} |
| else: |
| if policy_class == "Diffusion": |
| policy_config = {'lr':args["lr"], 'camera_names':camera_names, |
| 'action_dim':action_dim, |
| 'observation_horizon':1, |
| 'action_horizon':8, |
| 'prediction_horizon':args["chunk_size"], |
| 'num_queries':args["chunk_size"], |
| 'num_inference_timesteps':10, |
| 'ema_power':0.75, |
| 'vq':False} |
| else: |
| if policy_class == "CNNMLP": |
| policy_config = {'lr':args["lr"], |
| 'lr_backbone':lr_backbone, 'backbone':backbone, 'num_queries':1, 'camera_names':camera_names} |
| else: |
| raise NotImplementedError |
| config = {'num_steps':num_steps, 'eval_every':eval_every, |
| 'validate_every':validate_every, |
| 'save_every':save_every, |
| 'ckpt_dir':ckpt_dir, |
| 'resume_ckpt_path':resume_ckpt_path, |
| 'episode_len':episode_len, |
| 'state_dim':state_dim, |
| 'lr':args["lr"], |
| 'policy_class':policy_class, |
| 'policy_config':policy_config, |
| 'task_name':task_name, |
| 'seed':args["seed"], |
| 'temporal_agg':args["temporal_agg"], |
| 'camera_names':camera_names, |
| 'load_pretrain':args["load_pretrain"], |
| 'enable_hsa':args.get("enable_hsa", False), |
| 'hsa_weight':args.get("hsa_weight", 1.0), |
| 'hsa_temperature':args.get("hsa_temperature", 0.07), |
| 'hsa_img_size':args.get("hsa_img_size", 224), |
| 'hsa_feature_dim':args.get("hsa_feature_dim", 768), |
| 'hsa_num_heads':args.get("hsa_num_heads", 12), |
| 'robot_type':args.get("robot_type", "Nova 2"), |
| 'wrist_camera':args.get("wrist_camera", "left_wrist"), |
| 'camera_params':args.get("camera_params", None)} |
| if not os.path.isdir(ckpt_dir): |
| os.makedirs(ckpt_dir) |
| config_path = os.path.join(ckpt_dir, "config.pkl") |
| expr_name = ckpt_dir.split("/")[-1] |
| with open(config_path, "wb") as f: |
| pickle.dump(config, f) |
| print(f"Loading data from: {dataset_dir}") |
| use_vitg = args.get("use_vitg", False) |
| |
| all_camera_names = camera_names + tactile_camera_names |
| train_dataloader, val_dataloader, stats, _ = load_data(dataset_dir, name_filter, all_camera_names, batch_size_train, batch_size_val, (args["chunk_size"]), (args["skip_mirrored_data"]), (config["load_pretrain"]), policy_class, stats_dir_l=stats_dir, sample_weights=sample_weights, train_ratio=train_ratio, use_vitg=use_vitg, tactile_camera_names=tactile_camera_names) |
| stats_path = os.path.join(ckpt_dir, "dataset_stats.pkl") |
| with open(stats_path, "wb") as f: |
| pickle.dump(stats, f) |
| best_ckpt_info = train_bc(train_dataloader, val_dataloader, config) |
| best_step, min_val_loss, best_state_dict = best_ckpt_info |
| ckpt_path = os.path.join(ckpt_dir, "policy_best.ckpt") |
| torch.save(best_state_dict, ckpt_path) |
| print(f"Best ckpt, val loss {min_val_loss:.6f} @ step{best_step}") |
|
|
|
|
| def make_policy(policy_class, policy_config, hsa_config=None): |
| """Create policy with optional HSA loss support.""" |
| if policy_class == "ACT": |
| if hsa_config is not None and hsa_config.get('enable_hsa', False): |
| policy = ACTPolicyWithHSA(policy_config, hsa_config) |
| else: |
| policy = ACTPolicy(policy_config) |
| elif policy_class == "ACTJEPA": |
| from ModelTrain.module.policy_jepa import ACTJEPAPolicy |
| policy = ACTJEPAPolicy(policy_config) |
| elif policy_class == "ACTJEPAAdapter": |
| from ModelTrain.module.policy_jepa_adapter import ACTJEPAAdapterPolicy |
| if hsa_config is not None and hsa_config.get('enable_hsa', False): |
| policy = ACTJEPAHsa(policy_config, hsa_config) |
| else: |
| policy = ACTJEPAAdapterPolicy(policy_config) |
| elif policy_class == "CNNMLP": |
| policy = CNNMLPPolicy(policy_config) |
| elif policy_class == "Diffusion": |
| policy = DiffusionPolicy(policy_config) |
| else: |
| raise NotImplementedError |
| return policy |
|
|
|
|
| def make_optimizer(policy_class, policy): |
| if policy_class == "ACT": |
| optimizer = policy.configure_optimizers() |
| elif policy_class == "ACTJEPA": |
| optimizer = policy.configure_optimizers() |
| elif policy_class == "ACTJEPAAdapter": |
| optimizer = policy.configure_optimizers() |
| elif policy_class == "CNNMLP": |
| optimizer = policy.configure_optimizers() |
| elif policy_class == "Diffusion": |
| optimizer = policy.configure_optimizers() |
| else: |
| raise NotImplementedError |
| return optimizer |
|
|
|
|
| def get_image(ts, camera_names, rand_crop_resize=False): |
| print("get_image") |
| curr_images = [] |
| for cam_name in camera_names: |
| curr_image = rearrange(ts.observation["images"][cam_name], "h w c -> c h w") |
| curr_images.append(curr_image) |
| else: |
| curr_image = np.stack(curr_images, axis=0) |
| curr_image = torch.from_numpy(curr_image / 255.0).float().cuda().unsqueeze(0) |
| if rand_crop_resize: |
| print("rand crop resize is used!") |
| original_size = curr_image.shape[-2:] |
| ratio = 0.95 |
| curr_image = curr_image[..., int(original_size[0] * (1 - ratio) / 2):int(original_size[0] * (1 + ratio) / 2), |
| int(original_size[1] * (1 - ratio) / 2):int(original_size[1] * (1 + ratio) / 2)] |
| curr_image = curr_image.squeeze(0) |
| resize_transform = transforms.Resize(original_size, antialias=True) |
| curr_image = resize_transform(curr_image) |
| curr_image = curr_image.unsqueeze(0) |
| return curr_image |
|
|
|
|
| def forward_pass(data, policy, enable_hsa=False): |
| """ |
| Forward pass through policy, handling both standard and HSA modes. |
| |
| Args: |
| data: Batch data (5 items with tactile) or (4 items without tactile) |
| policy: Policy model |
| enable_hsa: Whether to compute HSA loss (requires tactile data) |
| |
| Returns: |
| Forward dictionary with losses |
| """ |
| |
| if len(data) == 5: |
| |
| rgb_data, tactile_data, qpos_data, action_data, is_pad = data |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if isinstance(tactile_data, torch.Tensor): |
| |
| if tactile_data.dim() == 4: |
| |
| tactile_data = tactile_data.unsqueeze(1) |
| |
| image_data = torch.cat([rgb_data, tactile_data], dim=1) |
| elif tactile_data and len(tactile_data) > 0: |
| |
| |
| |
| |
| if isinstance(tactile_data[0], torch.Tensor) and tactile_data[0].dim() == 4: |
| |
| |
| tactile_stacked = torch.stack(tactile_data, dim=1) |
| |
| elif isinstance(tactile_data[0], list): |
| |
| tactile_stacked = torch.stack([torch.stack(batch_tactile) for batch_tactile in tactile_data]) |
| |
| else: |
| |
| tactile_stacked = torch.stack(tactile_data) |
| |
| tactile_stacked = tactile_stacked.unsqueeze(1) |
| |
| |
| |
| |
| image_data = [rgb_data, tactile_stacked] |
| |
| else: |
| |
| image_data = rgb_data |
| else: |
| |
| image_data, qpos_data, action_data, is_pad = data |
| |
| |
| if isinstance(image_data, list): |
| |
| image_data = [img.cuda() for img in image_data] |
| else: |
| image_data = image_data.cuda() |
| |
| qpos_data, action_data, is_pad = (qpos_data.cuda(), action_data.cuda(), is_pad.cuda()) |
| return policy(qpos_data, image_data, action_data, is_pad) |
|
|
|
|
| def train_bc(train_dataloader, val_dataloader, config): |
| num_steps = config["num_steps"] |
| ckpt_dir = config["ckpt_dir"] |
| seed = config["seed"] |
| policy_class = config["policy_class"] |
| policy_config = config["policy_config"] |
| eval_every = config["eval_every"] |
| validate_every = config["validate_every"] |
| save_every = config["save_every"] |
| |
| |
| enable_hsa = config.get("enable_hsa", False) |
| hsa_config = None |
| if enable_hsa: |
| |
| wrist_camera_name = config.get("wrist_camera", "left_wrist") |
| camera_names = config['camera_names'] |
| try: |
| wrist_camera_idx = camera_names.index(wrist_camera_name) |
| except ValueError: |
| print(f"Warning: Wrist camera '{wrist_camera_name}' not found in camera_names {camera_names}, using index 1 (left_wrist)") |
| wrist_camera_idx = 1 |
| |
| hsa_config = { |
| 'enable_hsa': True, |
| 'hsa_weight': config.get("hsa_weight", 1.0), |
| 'temperature': config.get("hsa_temperature", 0.07), |
| 'img_size': config.get("hsa_img_size", 224), |
| 'feature_dim': config.get("hsa_feature_dim", 768), |
| 'num_heads': config.get("hsa_num_heads", 12), |
| 'robot_type': config.get("robot_type", "Nova 2"), |
| 'wrist_camera': wrist_camera_name, |
| 'wrist_camera_idx': wrist_camera_idx, |
| 'tactile_camera_idx': 0, |
| 'camera_params': config.get("camera_params", None) |
| } |
| |
| set_seed(seed) |
| policy = make_policy(policy_class, policy_config, hsa_config) |
| |
| |
| if enable_hsa: |
| print("\n" + "="*60) |
| print("HSA Loss ENABLED") |
| print(f" Policy Class: {policy_class}") |
| print(f" HSA Weight: {hsa_config['hsa_weight']}") |
| print(f" Temperature: {hsa_config['temperature']}") |
| print(f" Robot Type: {hsa_config['robot_type']}") |
| print(f" Wrist Camera: {hsa_config['wrist_camera']}") |
| print("="*60 + "\n") |
| |
| if config["load_pretrain"]: |
| loading_status = policy.deserialize(torch.load(os.path.join("./ckpt/pretrain_all", "policy_step_50000_seed_0.ckpt"))) |
| print(f"loaded! {loading_status}") |
| if config["resume_ckpt_path"] is not None: |
| loading_status = policy.deserialize(torch.load(config["resume_ckpt_path"])) |
| print(f'Resume policy from: {config["resume_ckpt_path"]}, Status: {loading_status}') |
| optimizer = make_optimizer(policy_class, policy) |
| policy.cuda() |
| min_val_loss = np.inf |
| best_ckpt_info = None |
| train_dataloader = repeater(train_dataloader) |
| train_loss = [] |
| val_loss = [] |
| train_hsa = [] |
| val_hsa = [] |
| last_time = time.time() |
| start_time = last_time |
| for step in tqdm(range(num_steps + 1)): |
| if step % validate_every == 0: |
| print("validating") |
| with torch.inference_mode(): |
| policy.eval() |
| validation_dicts = [] |
| for batch_idx, data in enumerate(val_dataloader): |
| forward_dict = forward_pass(data, policy, enable_hsa=enable_hsa) |
| validation_dicts.append(forward_dict) |
| if batch_idx > 50: |
| break |
| validation_summary = compute_dict_mean(validation_dicts) |
| epoch_val_loss = validation_summary["loss"].mean() |
| if epoch_val_loss < min_val_loss: |
| min_val_loss = epoch_val_loss |
| best_ckpt_info = (step, min_val_loss, deepcopy(policy.serialize())) |
|
|
| for k in list(validation_summary.keys()): |
| validation_summary[f"val_{k}"] = validation_summary.pop(k) |
| else: |
| print(f"Val loss: {epoch_val_loss:.5f}") |
| val_loss.append(float(epoch_val_loss.item())) |
| |
| |
| if enable_hsa and 'val_hsa_total' in validation_summary: |
| val_hsa.append(float(validation_summary['val_hsa_total'].mean().item())) |
| |
| |
| if enable_hsa: |
| hsa_keys = [k for k in validation_summary.keys() if 'hsa' in k.lower()] |
| if hsa_keys: |
| print(" HSA Losses:", end=" ") |
| for k in hsa_keys: |
| print(f"{k}: {validation_summary[k].mean().item():.3f}", end=" ") |
| print() |
| |
| |
| summary_string = "" |
| for k, v in validation_summary.items(): |
| summary_string += f"{k}: {v.mean().item():.3f} " |
| else: |
| print(summary_string) |
|
|
| if step > 0: |
| if step % eval_every == 0: |
| ckpt_name = f"policy_step_{step}_seed_{seed}.ckpt" |
| ckpt_path = os.path.join(ckpt_dir, ckpt_name) |
| policy.train() |
| optimizer.zero_grad() |
| data = next(train_dataloader) |
| forward_dict = forward_pass(data, policy, enable_hsa=enable_hsa) |
| loss = forward_dict["loss"] |
| loss.mean().backward() |
| optimizer.step() |
| train_loss.append(float(loss.mean().item())) |
| |
| |
| if enable_hsa and 'hsa_total' in forward_dict: |
| train_hsa.append(float(forward_dict['hsa_total'].mean().item())) |
| |
| |
| if step % 100 == 0 and step > 0: |
| train_summary = f"Step {step} - Train loss: {loss.mean().item():.5f}" |
| if enable_hsa and 'hsa_wrist' in forward_dict: |
| train_summary += f" | L1: {forward_dict['l1'].mean().item():.3f}" |
| train_summary += f" | KL: {forward_dict['kl'].mean().item():.3f}" |
| train_summary += f" | HSA_wrist: {forward_dict['hsa_wrist'].mean().item():.3f}" |
| if 'hsa_total' in forward_dict: |
| train_summary += f" | HSA_total: {forward_dict['hsa_total'].mean().item():.3f}" |
| print(train_summary) |
| |
| if step % save_every == 0: |
| ckpt_path = os.path.join(ckpt_dir, f"policy_step_{step}_seed_{seed}.ckpt") |
| torch.save(policy.serialize(), ckpt_path) |
| cur_time = time.time() |
| last_time = cur_time |
| else: |
| print("train all time:", cur_time - start_time) |
| ckpt_path = os.path.join(ckpt_dir, "policy_last.ckpt") |
| torch.save(policy.serialize(), ckpt_path) |
| best_step, min_val_loss, best_state_dict = best_ckpt_info |
| ckpt_path = os.path.join(ckpt_dir, f"policy_step_{best_step}_seed_{seed}.ckpt") |
| torch.save(best_state_dict, ckpt_path) |
| print(f"Training finished:\nSeed {seed}, val loss {min_val_loss:.6f} at step {best_step}") |
| |
| |
| plt.figure(figsize=(12, 6)) |
| plt.plot(train_loss, label="Training Loss (raw)", color='blue', alpha=0.2, linewidth=0.5) |
| |
| |
| if len(train_loss) > 100: |
| window = min(100, len(train_loss) // 10) |
| smoothed = np.convolve(train_loss, np.ones(window)/window, mode='valid') |
| smooth_steps = list(range(window//2, len(train_loss) - window//2)) |
| plt.plot(smooth_steps, smoothed, label=f"Training Loss (smoothed)", |
| color='darkblue', linewidth=2) |
| |
| plt.title("Training Loss Over Steps") |
| plt.xlabel("Step") |
| plt.ylabel("Loss") |
| plt.legend() |
| plt.grid(True, alpha=0.3) |
| plt.savefig(ckpt_dir + "/train_loss.png", dpi=150) |
| plt.close() |
| |
| |
| plt.figure(figsize=(10, 6)) |
| plt.plot(val_loss, label="Validation Loss", color='green', linewidth=2, marker='o', markersize=4) |
| plt.title("Validation Loss Over Steps") |
| plt.xlabel("Validation Step") |
| plt.ylabel("Loss") |
| plt.legend() |
| plt.grid(True, alpha=0.3) |
| plt.savefig(ckpt_dir + "/val_loss.png", dpi=150) |
| plt.close() |
| |
| |
| if enable_hsa and len(train_hsa) > 0: |
| |
| plt.figure(figsize=(12, 6)) |
| |
| |
| plt.plot(train_hsa, label="HSA Loss (raw)", color='orange', alpha=0.2, linewidth=0.5) |
| |
| |
| window_size = min(100, len(train_hsa) // 10) |
| if len(train_hsa) > window_size: |
| smoothed = np.convolve(train_hsa, np.ones(window_size)/window_size, mode='valid') |
| smoothed_steps = list(range(window_size//2, len(train_hsa) - window_size//2)) |
| plt.plot(smoothed_steps, smoothed, label=f"HSA Loss (smoothed, window={window_size})", |
| color='darkorange', linewidth=2) |
| |
| plt.title("HSA Training Loss Over Steps") |
| plt.xlabel("Step") |
| plt.ylabel("HSA Loss") |
| plt.legend() |
| plt.grid(True, alpha=0.3) |
| plt.savefig(ckpt_dir + "/train_hsa_loss.png", dpi=150) |
| plt.close() |
| |
| |
| if len(val_hsa) > 0: |
| plt.figure(figsize=(10, 6)) |
| plt.plot(val_hsa, label="Validation HSA Loss", color='red') |
| plt.title("HSA Validation Loss Over Steps") |
| plt.xlabel("Validation Step") |
| plt.ylabel("HSA Loss") |
| plt.legend() |
| plt.grid(True) |
| plt.savefig(ckpt_dir + "/val_hsa_loss.png") |
| plt.close() |
| |
| |
| if len(val_hsa) > 0: |
| plt.figure(figsize=(12, 6)) |
| |
| |
| plt.plot(train_hsa, color='orange', alpha=0.15, linewidth=0.5, label='Training (raw)') |
| |
| |
| window_size = min(100, len(train_hsa) // 10) |
| if len(train_hsa) > window_size: |
| smoothed = np.convolve(train_hsa, np.ones(window_size)/window_size, mode='valid') |
| smoothed_steps = list(range(window_size//2, len(train_hsa) - window_size//2)) |
| plt.plot(smoothed_steps, smoothed, color='darkorange', linewidth=2, |
| label=f'Training (smoothed)') |
| |
| |
| val_steps = [i * validate_every for i in range(len(val_hsa))] |
| plt.plot(val_steps, val_hsa, color='red', linewidth=2.5, |
| marker='o', markersize=4, label='Validation') |
| |
| plt.title("HSA Loss: Training vs Validation") |
| plt.xlabel("Step") |
| plt.ylabel("HSA Loss") |
| plt.legend() |
| plt.grid(True, alpha=0.3) |
| plt.savefig(ckpt_dir + "/hsa_loss_combined.png", dpi=150) |
| plt.close() |
| |
| print(f"HSA loss plots saved to {ckpt_dir}/") |
| if len(train_hsa) > 100: |
| print(f" Initial HSA: {train_hsa[0]:.3f} → Final HSA: {train_hsa[-1]:.3f}") |
| |
| return best_ckpt_info |
|
|
|
|
| def repeater(data_loader): |
| epoch = 0 |
| for loader in repeat(data_loader): |
| for data in loader: |
| yield data |
| else: |
| print(f"Epoch {epoch} done") |
| epoch += 1 |
|
|
| |
|
|