| |
| """NeRF2 runner for training and testing |
| """ |
|
|
| import os |
|
|
| os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" |
|
|
| import argparse |
| from shutil import copyfile |
|
|
| import numpy as np |
| import torch |
| import torch.optim as optim |
| import yaml |
| from skimage.metrics import structural_similarity as ssim |
| from tensorboardX import SummaryWriter |
| from torch.utils.data import DataLoader |
| from tqdm import tqdm, trange |
| import scipy.io as scio |
|
|
| from dataloader import * |
| from model import * |
| from renderer import renderer_dict |
| from utils.data_painter import paint_spectrum_compare |
| from utils.logger import logger_config |
|
|
|
|
| class NeRF2_Runner(): |
|
|
| def __init__(self, mode, dataset_type, **kwargs) -> None: |
|
|
| kwargs_path = kwargs['path'] |
| kwargs_render = kwargs['render'] |
| kwargs_network = kwargs['networks'] |
| kwargs_train = kwargs['train'] |
| self.dataset_type = dataset_type |
|
|
| |
| self.expname = kwargs_path['expname'] |
| self.datadir = kwargs_path['datadir'] |
| self.logdir = kwargs_path['logdir'] |
| self.devices = torch.device('cuda') |
|
|
| |
| log_filename = "logger.log" |
| log_savepath = os.path.join(self.logdir, self.expname, log_filename) |
| self.logger = logger_config(log_savepath=log_savepath, logging_name='nerf2') |
| self.logger.info("expname:%s, datadir:%s, logdir:%s", self.expname, self.datadir, self.logdir) |
| self.writer = SummaryWriter(os.path.join(self.logdir, self.expname, 'tensorboard')) |
|
|
|
|
| |
| self.nerf2_network = NeRF2(**kwargs_network).to(self.devices) |
| params = list(self.nerf2_network.parameters()) |
| self.optimizer = torch.optim.Adam(params, lr=float(kwargs_train['lr']), |
| weight_decay=float(kwargs_train['weight_decay']), |
| betas=(0.9, 0.999)) |
| self.cosine_scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer=self.optimizer, |
| T_max=float(kwargs_train['T_max']), eta_min=float(kwargs_train['eta_min']), |
| last_epoch=-1) |
|
|
| |
| renderer = renderer_dict[kwargs_render['mode']] |
| self.renderer = renderer(networks_fn=self.nerf2_network, **kwargs_render) |
| self.scale_worldsize = kwargs_render['scale_worldsize'] |
|
|
| |
| total_params = sum(p.numel() for p in params if p.requires_grad) |
| self.logger.info("Total number of parameters: %s", total_params) |
|
|
| |
| self.current_iteration = 1 |
| if kwargs_train['load_ckpt'] or mode == 'test': |
| self.load_checkpoints() |
| self.batch_size = kwargs_train['batch_size'] |
| self.total_iterations = kwargs_train['total_iterations'] |
| self.save_freq = kwargs_train['save_freq'] |
|
|
| |
| dataset = dataset_dict[dataset_type] |
| train_index = os.path.join(self.datadir, "train_index.txt") |
| test_index = os.path.join(self.datadir, "test_index.txt") |
| if not os.path.exists(train_index) or not os.path.exists(test_index): |
| split_dataset(self.datadir, ratio=0.8, dataset_type=dataset_type) |
| self.logger.info("Loading training set...") |
| self.train_set = dataset(self.datadir, train_index, self.scale_worldsize) |
| self.logger.info("Loading test set...") |
| self.test_set = dataset(self.datadir, test_index, self.scale_worldsize) |
|
|
| self.train_iter = DataLoader(self.train_set, batch_size=self.batch_size, shuffle=True, num_workers=0) |
| self.test_iter = DataLoader(self.test_set, batch_size=self.batch_size, shuffle=False, num_workers=0) |
| self.logger.info("Train set size:%d, Test set size:%d", len(self.train_set), len(self.test_set)) |
|
|
|
|
| def load_checkpoints(self): |
| ckptsdir = os.path.join(self.logdir, self.expname, 'ckpts') |
| if not os.path.exists(ckptsdir): |
| os.makedirs(ckptsdir) |
| ckpts = [os.path.join(ckptsdir, f) for f in sorted(os.listdir(ckptsdir)) if 'tar' in f] |
| self.logger.info('Found ckpts %s', ckpts) |
|
|
| if len(ckpts) > 0: |
| ckpt_path = ckpts[-1] |
| self.logger.info('Loading ckpt %s', ckpt_path) |
| ckpt = torch.load(ckpt_path, map_location=self.devices) |
|
|
| self.nerf2_network.load_state_dict(ckpt['nerf2_network_state_dict']) |
| self.optimizer.load_state_dict(ckpt['optimizer_state_dict']) |
| self.cosine_scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer=self.optimizer,T_max=20,eta_min=1e-5) |
| self.cosine_scheduler.load_state_dict(ckpt['scheduler_state_dict']) |
| self.current_iteration = ckpt['current_iteration'] |
|
|
|
|
|
|
| def save_checkpoint(self): |
| ckptsdir = os.path.join(self.logdir, self.expname, 'ckpts') |
| os.makedirs(ckptsdir, exist_ok=True) |
| model_lst = [x for x in sorted(os.listdir(ckptsdir)) if x.endswith('.tar')] |
| if len(model_lst) > 2: |
| os.remove(ckptsdir + '/%s' % model_lst[0]) |
|
|
| ckptname = os.path.join(ckptsdir, '{:06d}.tar'.format(self.current_iteration)) |
| torch.save({ |
| 'current_iteration': self.current_iteration, |
| 'nerf2_network_state_dict': self.nerf2_network.state_dict(), |
| 'optimizer_state_dict': self.optimizer.state_dict(), |
| 'scheduler_state_dict': self.cosine_scheduler.state_dict() |
| }, ckptname) |
| return ckptname |
|
|
|
|
|
|
| def train(self): |
| """train the model |
| """ |
| self.logger.info("Start training. Current Iteration:%d", self.current_iteration) |
| while self.current_iteration <= self.total_iterations: |
| with tqdm(total=len(self.train_iter), desc=f"Iteration {self.current_iteration}/{self.total_iterations}") as pbar: |
| for train_input, train_label in self.train_iter: |
| if self.current_iteration > self.total_iterations: |
| break |
|
|
| train_input, train_label = train_input.to(self.devices), train_label.to(self.devices) |
| if self.dataset_type == "rfid": |
| rays_o, rays_d, tx_o = train_input[:, :3], train_input[:, 3:6], train_input[:, 6:9] |
| predict_spectrum = self.renderer.render_ss(tx_o, rays_o, rays_d) |
| loss = sig2mse(predict_spectrum, train_label.view(-1)) |
| elif self.dataset_type == 'ble': |
| tx_o, rays_o, rays_d = train_input[:, :3], train_input[:, 3:6], train_input[:, 6:] |
| predict_rssi = self.renderer.render_rssi(tx_o, rays_o, rays_d) |
| loss = sig2mse(predict_rssi, train_label.view(-1)) |
| elif self.dataset_type == 'mimo': |
| uplink, rays_o, rays_d = train_input[:, :52], train_input[:, 52:55], train_input[:, 55:] |
| predict_downlink = self.renderer.render_csi(uplink, rays_o, rays_d) |
| predict_downlink = torch.concat((predict_downlink.real, predict_downlink.imag), dim=-1) |
| loss = sig2mse(predict_downlink, train_label) |
|
|
|
|
| self.optimizer.zero_grad() |
| loss.backward() |
| self.optimizer.step() |
| self.cosine_scheduler.step() |
| self.current_iteration += 1 |
|
|
| self.writer.add_scalar('Loss/loss', loss, self.current_iteration) |
| pbar.update(1) |
| pbar.set_description(f"Iteration {self.current_iteration}/{self.total_iterations}") |
| pbar.set_postfix_str('loss = {:.6f}, lr = {:.6f}'.format(loss.item(), self.optimizer.param_groups[0]['lr'])) |
|
|
| if self.current_iteration % self.save_freq == 0: |
| ckptname = self.save_checkpoint() |
| pbar.write('Saved checkpoints at {}'.format(ckptname)) |
|
|
|
|
|
|
| def eval_network_spectrum(self): |
| """test the model |
| """ |
| self.logger.info("Start evaluation") |
| self.nerf2_network.eval() |
|
|
| os.makedirs(os.path.join(self.logdir, self.expname, 'pred_spectrum'), exist_ok=True) |
| pred2next, gt2next = torch.zeros((0)), torch.zeros((0)) |
| save_img_idx = 0 |
| all_ssim = [] |
| with torch.no_grad(): |
| for test_input, test_label in self.test_iter: |
| test_input, test_label = test_input.to(self.devices), test_label.to(self.devices) |
| rays_o, rays_d, tx_o = test_input[:, :3], test_input[:, 3:6], test_input[:, 6:9] |
| pred_spectrum = self.renderer.render_ss(tx_o, rays_o, rays_d) |
|
|
|
|
| |
| pred_spectrum = pred_spectrum.detach().cpu() |
| gt_spectrum = test_label.detach().cpu() |
| pred_spectrum = torch.concatenate((pred2next, pred_spectrum), dim=0) |
| gt_spectrum = torch.concatenate((gt2next, gt_spectrum), dim=0) |
| num_spectrum = len(pred_spectrum) // (360 * 90) |
| pred2next = pred_spectrum[num_spectrum*360*90:] |
| gt2next = gt_spectrum[num_spectrum*360*90:] |
|
|
| for i in range(num_spectrum): |
| pred_sepctrum_i = pred_spectrum[i*360*90:(i+1)*360*90].numpy().reshape(90, 360) |
| gt_spectrum_i = gt_spectrum[i*360*90:(i+1)*360*90].numpy().reshape(90, 360) |
| pixel_error = np.mean(abs(pred_sepctrum_i - gt_spectrum_i)) |
| ssim_i = ssim(pred_sepctrum_i, gt_spectrum_i, data_range=1, multichannel=False) |
| self.logger.info("Spectrum {:d}, Mean pixel error = {:.6f}; SSIM = {:.6f}".format(save_img_idx, pixel_error, ssim_i)) |
| paint_spectrum_compare(pred_sepctrum_i, gt_spectrum_i,save_path=os.path.join(self.logdir, self.expname,'pred_spectrum', f'{save_img_idx}.png')) |
| all_ssim.append(ssim_i) |
| self.logger.info("Median SSIM is {:.6f}".format(np.median(all_ssim))) |
| save_img_idx += 1 |
| np.savetxt(os.path.join(self.logdir, self.expname, 'all_ssim.txt'), all_ssim, fmt='%.4f') |
|
|
|
|
|
|
|
|
| def eval_network_rssi(self): |
| """test the model and save predicted RSSI values to a file |
| """ |
| self.logger.info("Start evaluation") |
| self.nerf2_network.eval() |
|
|
| with torch.no_grad(): |
| with open(os.path.join(self.logdir, self.expname, "result.txt"), 'w') as f: |
| for test_input, test_label in self.test_iter: |
| test_input, test_label = test_input.to(self.devices), test_label.to(self.devices) |
| tx_o, rays_o, rays_d = test_input[:, :3], test_input[:, 3:6], test_input[:, 6:] |
| predict_rssi = self.renderer.render_rssi(tx_o, rays_o, rays_d) |
|
|
| |
| predict_rssi = amplitude2rssi(predict_rssi.detach().cpu()) |
| gt_rssi = amplitude2rssi(test_label.detach().cpu()) |
|
|
| error = abs(predict_rssi - gt_rssi.reshape(-1)) |
| self.logger.info("Median error:%.2f", torch.median(error)) |
|
|
| |
| for i, rssi in enumerate(predict_rssi): |
| f.write("{:.2f}, {:.2f}".format(gt_rssi[i].item(), rssi.item()) + '\n') |
|
|
| result = np.loadtxt(os.path.join(self.logdir,self.expname, "result.txt"), delimiter=",") |
| self.logger.info("Total Median error:%.2f", np.median(abs(result[:,0] - result[:,1]))) |
|
|
|
|
|
|
| def eval_network_csi(self): |
| """test the model and save predicted csi values to a file |
| """ |
| self.logger.info("Start evaluation") |
| self.nerf2_network.eval() |
|
|
| n_bs = self.test_set.n_bs |
| n_data = len(self.test_set) |
|
|
| all_pred_csi = torch.zeros((n_data, 26), dtype=torch.complex64) |
| all_gt_csi = torch.zeros((n_data, 26), dtype=torch.complex64) |
| with torch.no_grad(): |
| for idx, (test_input, test_label) in enumerate(self.test_iter): |
| test_input, test_label = test_input.to(self.devices), test_label.to(self.devices) |
| uplink, rays_o, rays_d = test_input[:, :52], test_input[:, 52:55], test_input[:, 55:] |
| predict_downlink = self.renderer.render_csi(uplink, rays_o, rays_d) |
| gt_downlink = test_label[:, :26] + 1j * test_label[:, 26:] |
| predict_downlink = self.test_set.denormalize_csi(predict_downlink) |
| gt_downlink = self.test_set.denormalize_csi(gt_downlink) |
|
|
| all_pred_csi[idx*self.batch_size:(idx+1)*self.batch_size] = predict_downlink |
| all_gt_csi[idx*self.batch_size:(idx+1)*self.batch_size] = gt_downlink |
|
|
| all_pred_csi = rearrange(all_pred_csi, '(n_data n_bs) channel -> n_data n_bs channel', n_bs=n_bs) |
| all_gt_csi = rearrange(all_gt_csi, '(n_data n_bs) channel -> n_data n_bs channel', n_bs=n_bs) |
| snr = csi2snr(all_pred_csi, all_gt_csi) |
| self.logger.info("Median SNR:%.2f", torch.median(snr)) |
|
|
| scio.savemat(os.path.join(self.logdir, self.expname, "result.mat"), {'pred_csi': all_pred_csi.cpu().numpy(), |
| 'gt_csi': all_gt_csi.cpu().numpy(), |
| 'snr': snr.cpu().numpy()}) |
|
|
|
|
|
|
|
|
| if __name__ == '__main__': |
|
|
| parser = argparse.ArgumentParser() |
| parser.add_argument('--config', type=str, default='configs/mimo-csi.yml', help='config file path') |
| parser.add_argument('--gpu', type=int, default=0) |
| parser.add_argument('--mode', type=str, default='train') |
| parser.add_argument('--dataset_type', type=str, default='mimo') |
| args = parser.parse_args() |
| torch.cuda.set_device(args.gpu) |
|
|
| with open(args.config) as f: |
| kwargs = yaml.safe_load(f) |
| f.close() |
|
|
| |
| if args.mode == 'train': |
| logdir = os.path.join(kwargs['path']['logdir'], kwargs['path']['expname']) |
| os.makedirs(logdir, exist_ok=True) |
| copyfile(args.config, os.path.join(logdir,'config.yml')) |
|
|
| worker = NeRF2_Runner(mode=args.mode, dataset_type=args.dataset_type, **kwargs) |
| if args.mode == 'train': |
| worker.train() |
| elif args.mode == 'test': |
| if args.dataset_type == 'rfid': |
| worker.eval_network_spectrum() |
| elif args.dataset_type == 'ble': |
| worker.eval_network_rssi() |
| elif args.dataset_type == 'mimo': |
| worker.eval_network_csi() |
|
|