├── .gitignore ├── .idea ├── .gitignore ├── imu_classification.iml ├── misc.xml ├── modules.xml ├── other.xml └── vcs.xml ├── README.md ├── criteria.py ├── dataloaders ├── Scaler.py ├── datacontainer.py ├── dataloader.py ├── params.py ├── transforms.py └── uwb_dataloader.py ├── main.py ├── materials ├── 01-28.png ├── 02-22.png ├── 02-25.png ├── 02-32.png ├── 02-38.png ├── 02_11_00-28.png ├── 02_11_23-55.png ├── README.md ├── fast-02-27.png ├── id_names.png ├── test.gif └── validation.png ├── metrics.py ├── models ├── lstm.py ├── models.py ├── multi_scale_ori.py ├── rnn_model.py └── stacked_bidirectional.py ├── requirements.txt ├── scribbles.py ├── utils.py └── uwb_dataset ├── all ├── 01-28.csv ├── 02-22.csv ├── 02-25.csv ├── 02-32.csv ├── 02_11_00-28.csv └── 02_11_23-55.csv ├── test ├── 02-38.csv └── fast-02-27.csv ├── train ├── 01-28.csv └── 02_11_23-55.csv └── val ├── 02-22.csv ├── 02-25.csv ├── 02-32.csv └── 02_11_00-28.csv /.gitignore: -------------------------------------------------------------------------------- 1 | results 2 | data 3 | analysis/*.csv 4 | analysis/seq 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | *.sh 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/imu_classification.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pytorch UWB Localization 2 | 3 | Official page of [RONet](https://ieeexplore.ieee.org/abstract/document/8968551), which is published @IROS'19 4 | 5 | Since the original code is based on Tensorflow, now I have ported the original algorithm to PyTorch. 6 | 7 | ![before](/materials/test.gif) 8 | 9 | 10 | ## ToDo 11 | - [ ] Port RONet 12 | - [ ] Port Bi-LSTM 13 | - [ ] Run on test data 14 | - [x] Set training pipeline 15 | - [x] Visualize training procedure 16 | - [x] Autosave the best model 17 | 18 | ## Environments 19 | 20 | Please refer to `requirements.txt` 21 | 22 | ## Descriptions 23 | 24 | ### What is the UWB sensor? 25 | 26 | UWB is abbv. for *Ultra-wideband*, and the sensor outputs only 1D range data. 27 | 28 | ![validation](/materials/validation.png) 29 | 30 | More explanations are provided in [this paper](https://ieeexplore.ieee.org/abstract/document/8768568). 31 | 32 | In summary, UWB data are likely to be vulnerable to noise, multipath problems, and so forth. 33 | 34 | Thus, we leverage the nonlinearity of deep learning to tackle that issue. 35 | 36 | ### Data 37 | 38 | All data are contained in `uwb_dataset` and a total of eight sensors are deployed, whose positions are as follows: 39 | 40 | ![idnames](/materials/id_names.png) 41 | 42 | And each csv consists N (the num. of sequences) x 10 whose columns denotes: 43 | 44 | `range @id0, range @id1, range @id2, range @id3, range @id4, range @id5, range @id6, range @id7, x of GT, y of GT` 45 | 46 | Note that our experiment was conducted on **real-world** data by using [Pozyx UWB sensors](https://www.pozyx.io/?ppc_keyword=pozyx&gclid=CjwKCAiAm-2BBhANEiwAe7eyFHFbVb7B_eub3dTe9oIUqgN1XI6c9O4N8aOj6L24fZyAHMKQLRahQxoCqdgQAvD_BwE) and the motion capture system. 47 | 48 | (Please kindly keep in mind that Pozyx systems do not give precise range data :( ) 49 | 50 | 51 | ## Training 52 | 53 | The training scripts come with several options, which can be listed with the `--help` flag. 54 | ```bash 55 | python3 main.py --help 56 | ``` 57 | 58 | The point is that it only takes a few minutes because the data of UWB are lightweight and simple! :) 59 | 60 | Training results will be saved under the `results` folder. To resume a previous training, run 61 | ```bash 62 | python3 main.py --resume [path_to_previous_model] 63 | ``` 64 | 65 | ## Validation 66 | 67 | ```bash 68 | python3 main.py --evaluate [path_to_trained_model] 69 | ``` 70 | 71 | 72 | ## Benchmark 73 | 74 | On validation data 75 | 76 | | Methods | RMSE (cm) | 77 | |-----------|:----------:| 78 | | RNN | 4.050 | 79 | | GRU | 3.918 | 80 | | LSTM | 4.855 (what's wrong with you..?) | 81 | | Bi-LSTM | TBA | 82 | | RONet | TBA | 83 | 84 | 85 | ## Citation 86 | 87 | 88 | - If you use our code or method in your work, please consider citing the following:: 89 | 90 | ``` 91 | @INPROCEEDINGS {lim2019ronet, 92 | author = {Lim, Hyungtae and Park, Changgue and Myung, Hyun}, 93 | title = {Ronet: Real-time range-only indoor localization via stacked bidirectional lstm with residual attention}, 94 | booktitle = {Proceedings of the IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS)}, 95 | pages={3241--3247}, 96 | year = { 2019 }, 97 | organization={IEEE} 98 | } 99 | @INPROCEEDINGS {lim2018stackbilstm, 100 | author = {Lim, Hyungtae and Myung, Hyun}, 101 | title = {Effective Indoor Robot Localization by Stacked Bidirectional LSTM Using Beacon-Based Range Measurements}, 102 | booktitle = {International Conference on Robot Intelligence Technology and Applications}, 103 | pages={144--151}, 104 | year = { 2018 } 105 | organization={Springer} 106 | } 107 | 108 | ``` 109 | 110 | ## Contact 111 | 112 | Contact: Hyungtae Lim (shapelim@kaist.ac.kr) 113 | 114 | Please create a new issue for code-related questions. Pull requests are welcome. 115 | -------------------------------------------------------------------------------- /criteria.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | from torch.autograd import Variable 4 | 5 | class MaskedMSELoss(nn.Module): 6 | def __init__(self): 7 | super(MaskedMSELoss, self).__init__() 8 | 9 | def forward(self, pred, target): 10 | assert pred.dim() == target.dim(), "inconsistent dimensions" 11 | valid_mask = (target>0).detach() 12 | diff = target - pred 13 | diff = diff[valid_mask] 14 | self.loss = (diff ** 2).mean() 15 | return self.loss 16 | 17 | class MaskedL1Loss(nn.Module): 18 | def __init__(self): 19 | super(MaskedL1Loss, self).__init__() 20 | 21 | def forward(self, pred, target): 22 | assert pred.dim() == target.dim(), "inconsistent dimensions" 23 | valid_mask = (target>0).detach() 24 | diff = target - pred 25 | diff = diff[valid_mask] 26 | self.loss = diff.abs().mean() 27 | return self.loss -------------------------------------------------------------------------------- /dataloaders/Scaler.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import numpy as np 4 | from sklearn.preprocessing import MinMaxScaler 5 | 6 | class DataScaler: 7 | def __init__(self, root): 8 | self.X_scaler = MinMaxScaler() 9 | self.Y_scaler = MinMaxScaler() 10 | self.fit(root) 11 | 12 | def fit(self, root): 13 | for (path, _, files) in os.walk(root): 14 | for filename in files: 15 | try: 16 | abs_path = os.path.join(path, filename) 17 | if os.path.exists(abs_path): 18 | print("Scaling ", abs_path, "...") 19 | data = np.loadtxt(abs_path, delimiter=',') 20 | X = data[:, :-2] 21 | Y = data[:, -2:] 22 | # For debuging 23 | # print(X.shape, Y.shape) 24 | self.X_scaler.partial_fit(X) 25 | self.Y_scaler.partial_fit(Y) 26 | except KeyError: 27 | print("May be a file is open!") 28 | 29 | print("Fitting for preprocessing of X complete. min :", self.X_scaler.data_min_, "max : ", self.X_scaler.data_max_) 30 | print("Fitting for preprocessing of Y complete. min :", self.Y_scaler.data_min_, "max : ", self.Y_scaler.data_max_) 31 | 32 | def undo_scale(self, Y): 33 | Y_undo_scaled = self.Y_scaler.inverse_transform(Y) 34 | return Y_undo_scaled 35 | 36 | if __name__ == "__main__": 37 | scaler = DataScaler("/home/shapelim/ws/kari-lstm/uwb_dataset/all") 38 | -------------------------------------------------------------------------------- /dataloaders/datacontainer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import pandas as pd 4 | import numpy as np 5 | from sklearn.preprocessing import MinMaxScaler 6 | from dataloaders.params import INPUT_NAMES 7 | from metrics import AverageMeter, Result 8 | 9 | class ResultContainer: 10 | def __init__(self, y_target): 11 | self.trajectory_container = Trajectory(y_target) 12 | self.avg_meter = AverageMeter() 13 | self.result = Result() 14 | 15 | def accum(self, y_pred, y_gt): 16 | self.trajectory_container.accum(y_pred, y_gt) 17 | 18 | def get_result(self): 19 | return self.trajectory_container.get_results() 20 | 21 | 22 | class Trajectory: 23 | def __init__(self, y_target): 24 | self.Y_target = y_target 25 | self.y_gt_set = None 26 | self.y_pred_set = None 27 | self.is_initial = True 28 | 29 | def accum(self, y_pred, y_gt): 30 | if self.is_initial: 31 | self.y_gt_set = y_gt 32 | self.y_pred_set = y_pred 33 | self.is_initial = False 34 | else: 35 | self.y_gt_set = np.concatenate((self.y_gt_set, y_gt), axis=0) 36 | self.y_pred_set = np.concatenate((self.y_pred_set, y_pred), axis=0) 37 | 38 | def get_results(self): 39 | return self.y_gt_set, self.y_pred_set 40 | 41 | if __name__ == "__main__": 42 | pass 43 | -------------------------------------------------------------------------------- /dataloaders/dataloader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import os.path 4 | import numpy as np 5 | import torch.utils.data as data 6 | import dataloaders.transforms as transforms 7 | from sklearn.preprocessing import MinMaxScaler 8 | from dataloaders.params import INPUT_NAMES, X_KEY 9 | SAFETY_FACTOR = 10 10 | GT_LENGTH = 2 11 | def find_classes(dir): 12 | classes = os.listdir(dir) 13 | classes.sort() 14 | 15 | csvs = [np.loadtxt(os.path.join(dir, csvname), delimiter=',') for csvname in classes] 16 | class_to_idx = {classes[i]: i for i in range(len(classes))} 17 | return csvs, class_to_idx 18 | 19 | 20 | def make_dataset(csvs, seq_len, stride, interval): 21 | ''' 22 | This function is necessary since RNNs takes input whose shape is [seq_len, x_dim] 23 | :return: parsed train/val data 24 | ''' 25 | inputs = [] 26 | window_size = 1 + (seq_len - 1) * interval 27 | 28 | for order, csv in enumerate(csvs): 29 | total_length = len(csv) 30 | num_idxes = int((total_length - window_size + 1)//stride) 31 | 32 | assert (num_idxes - 1) * stride + window_size - 1 < total_length 33 | 34 | for i in range(num_idxes): 35 | start_idx = i * stride 36 | item = (order, start_idx) 37 | inputs.append(item) 38 | 39 | return inputs 40 | 41 | 42 | to_tensor = transforms.ToTensor() 43 | 44 | class MyDataloader(data.Dataset): 45 | def __init__(self, root, type, scaler, Y_target, seq_len=128, stride=1, interval=1): 46 | """ 47 | :param root: 48 | :param type: 49 | :param scaler: 50 | :param X_columns: 51 | :param Y_type: 52 | :param seq_len: 53 | :param stride: Interval btw time t-1 data and time t data 54 | :param interval: Interval in the input 55 | """ 56 | csvs, class_to_idx = find_classes(root) 57 | self.csvs_raw = csvs 58 | self.class_to_idx = class_to_idx 59 | self.scaler = scaler 60 | self.type = type # train or val 61 | 62 | self.Y_target = Y_target 63 | self.seq_len = seq_len 64 | self.stride = stride # Stride for window 65 | self.interval = interval # Interval size btw each data in a window 66 | self.csvs_scaled = self.scale_inputs() 67 | 68 | self.inputs = make_dataset(self.csvs_scaled, seq_len=seq_len, stride=stride, interval=interval) 69 | print("Total ", len(self.inputs), " data are generated") 70 | 71 | def scale_inputs(self): 72 | csvs_scaled = [] 73 | for csv_data in self.csvs_raw: 74 | X = csv_data[:, :-GT_LENGTH] 75 | Y = csv_data[:, -GT_LENGTH:] 76 | X_scaled = self.scaler.X_scaler.transform(X) 77 | Y_scaled = self.scaler.Y_scaler.transform(Y) 78 | data_scaled = np.concatenate((X_scaled, Y_scaled), axis=1) 79 | csvs_scaled.append(data_scaled) 80 | return csvs_scaled 81 | 82 | def get_input(self, id, idx): 83 | target_csv = self.csvs_scaled[id] 84 | # Note that stride is already considered in the function "make_dataset(~~)" 85 | target_idxes = [idx + self.interval * i for i in range(self.seq_len)] 86 | 87 | x = target_csv[target_idxes, :-GT_LENGTH] 88 | y = None 89 | if self.Y_target == "all": 90 | y = target_csv[target_idxes, -GT_LENGTH:] 91 | elif self.Y_target == "end": 92 | # maybe not in use 93 | y = target_csv[target_idxes[-1], -GT_LENGTH:] 94 | return x, y 95 | 96 | def __getraw__(self, index): 97 | """ 98 | Args: 99 | index (int): Index 100 | 101 | Returns: 102 | tuple: (x, y) the transformed data. 103 | """ 104 | csv_id, start_idx = self.inputs[index] 105 | x, y = self.get_input(csv_id, start_idx) 106 | return x, y, csv_id 107 | 108 | def __getitem__(self, index): 109 | 110 | x, y, csv_idx = self.__getraw__(index) 111 | 112 | tensor_x = to_tensor(x) 113 | tensor_y = to_tensor(y) 114 | 115 | if self.Y_target == "end": 116 | tensor_y = tensor_y.view(-1) 117 | 118 | return tensor_x, tensor_y, csv_idx 119 | 120 | def __len__(self): 121 | return len(self.inputs) 122 | 123 | 124 | -------------------------------------------------------------------------------- /dataloaders/params.py: -------------------------------------------------------------------------------- 1 | INPUT_NAMES = ["UWB_P", "UWB_Q", "UWB_R", 2 | "UWB_AX", "UWB_AY", "UWB_AZ", 3 | "V_air", "Elevator", "Aileron", "Rudder"] 4 | 5 | OUTPUT_NAMES = ["alpha", "beta"] 6 | 7 | INPUT_LEN = {"alpha": {"term": 2, "elements": 2, "all": 10}, 8 | "beta": {"term": 4, "elements": 5, "all": 10}} 9 | 10 | # T: Term 11 | A_T = ["V_square", "UWB_AZ"] 12 | A_E = ["V_air", "UWB_AZ"] 13 | 14 | B_T = ["Term0", "Term1", "Term2", "Rudder"] 15 | B_E = ["V_air", "UWB_AY", "UWB_P", "UWB_R", "Rudder"] 16 | 17 | X_KEY = {"alpha": {"term": A_T, "elements": A_E, "all": INPUT_NAMES}, 18 | "beta": {"term": B_T, "elements": B_E, "all": INPUT_NAMES}} -------------------------------------------------------------------------------- /dataloaders/transforms.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import torch 3 | import math 4 | import random 5 | 6 | from PIL import Image, ImageOps, ImageEnhance 7 | try: 8 | import accimage 9 | except ImportError: 10 | accimage = None 11 | 12 | import numpy as np 13 | import numbers 14 | import types 15 | import collections 16 | import warnings 17 | 18 | import scipy.ndimage.interpolation as itpl 19 | import scipy.misc as misc 20 | 21 | 22 | def _is_numpy_image(img): 23 | return isinstance(img, np.ndarray) and (img.ndim in {2, 3}) 24 | 25 | def _is_pil_image(img): 26 | if accimage is not None: 27 | return isinstance(img, (Image.Image, accimage.Image)) 28 | else: 29 | return isinstance(img, Image.Image) 30 | 31 | def _is_tensor_image(img): 32 | return torch.is_tensor(img) and img.ndimension() == 3 33 | 34 | def adjust_brightness(img, brightness_factor): 35 | """Adjust brightness of an Image. 36 | 37 | Args: 38 | img (PIL Image): PIL Image to be adjusted. 39 | brightness_factor (float): How much to adjust the brightness. Can be 40 | any non negative number. 0 gives a black image, 1 gives the 41 | original image while 2 increases the brightness by a factor of 2. 42 | 43 | Returns: 44 | PIL Image: Brightness adjusted image. 45 | """ 46 | if not _is_pil_image(img): 47 | raise TypeError('img should be PIL Image. Got {}'.format(type(img))) 48 | 49 | enhancer = ImageEnhance.Brightness(img) 50 | img = enhancer.enhance(brightness_factor) 51 | return img 52 | 53 | 54 | def adjust_contrast(img, contrast_factor): 55 | """Adjust contrast of an Image. 56 | 57 | Args: 58 | img (PIL Image): PIL Image to be adjusted. 59 | contrast_factor (float): How much to adjust the contrast. Can be any 60 | non negative number. 0 gives a solid gray image, 1 gives the 61 | original image while 2 increases the contrast by a factor of 2. 62 | 63 | Returns: 64 | PIL Image: Contrast adjusted image. 65 | """ 66 | if not _is_pil_image(img): 67 | raise TypeError('img should be PIL Image. Got {}'.format(type(img))) 68 | 69 | enhancer = ImageEnhance.Contrast(img) 70 | img = enhancer.enhance(contrast_factor) 71 | return img 72 | 73 | 74 | def adjust_saturation(img, saturation_factor): 75 | """Adjust color saturation of an image. 76 | 77 | Args: 78 | img (PIL Image): PIL Image to be adjusted. 79 | saturation_factor (float): How much to adjust the saturation. 0 will 80 | give a black and white image, 1 will give the original image while 81 | 2 will enhance the saturation by a factor of 2. 82 | 83 | Returns: 84 | PIL Image: Saturation adjusted image. 85 | """ 86 | if not _is_pil_image(img): 87 | raise TypeError('img should be PIL Image. Got {}'.format(type(img))) 88 | 89 | enhancer = ImageEnhance.Color(img) 90 | img = enhancer.enhance(saturation_factor) 91 | return img 92 | 93 | 94 | def adjust_hue(img, hue_factor): 95 | """Adjust hue of an image. 96 | 97 | The image hue is adjusted by converting the image to HSV and 98 | cyclically shifting the intensities in the hue channel (H). 99 | The image is then converted back to original image mode. 100 | 101 | `hue_factor` is the amount of shift in H channel and must be in the 102 | interval `[-0.5, 0.5]`. 103 | 104 | See https://en.wikipedia.org/wiki/Hue for more details on Hue. 105 | 106 | Args: 107 | img (PIL Image): PIL Image to be adjusted. 108 | hue_factor (float): How much to shift the hue channel. Should be in 109 | [-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel in 110 | HSV space in positive and negative direction respectively. 111 | 0 means no shift. Therefore, both -0.5 and 0.5 will give an image 112 | with complementary colors while 0 gives the original image. 113 | 114 | Returns: 115 | PIL Image: Hue adjusted image. 116 | """ 117 | if not(-0.5 <= hue_factor <= 0.5): 118 | raise ValueError('hue_factor is not in [-0.5, 0.5].'.format(hue_factor)) 119 | 120 | if not _is_pil_image(img): 121 | raise TypeError('img should be PIL Image. Got {}'.format(type(img))) 122 | 123 | input_mode = img.mode 124 | if input_mode in {'L', '1', 'I', 'F'}: 125 | return img 126 | 127 | h, s, v = img.convert('HSV').split() 128 | 129 | np_h = np.array(h, dtype=np.uint8) 130 | # uint8 addition take cares of rotation across boundaries 131 | with np.errstate(over='ignore'): 132 | np_h += np.uint8(hue_factor * 255) 133 | h = Image.fromarray(np_h, 'L') 134 | 135 | img = Image.merge('HSV', (h, s, v)).convert(input_mode) 136 | return img 137 | 138 | 139 | def adjust_gamma(img, gamma, gain=1): 140 | """Perform gamma correction on an image. 141 | 142 | Also known as Power Law Transform. Intensities in RGB mode are adjusted 143 | based on the following equation: 144 | 145 | I_out = 255 * gain * ((I_in / 255) ** gamma) 146 | 147 | See https://en.wikipedia.org/wiki/Gamma_correction for more details. 148 | 149 | Args: 150 | img (PIL Image): PIL Image to be adjusted. 151 | gamma (float): Non negative real number. gamma larger than 1 make the 152 | shadows darker, while gamma smaller than 1 make dark regions 153 | lighter. 154 | gain (float): The constant multiplier. 155 | """ 156 | if not _is_pil_image(img): 157 | raise TypeError('img should be PIL Image. Got {}'.format(type(img))) 158 | 159 | if gamma < 0: 160 | raise ValueError('Gamma should be a non-negative real number') 161 | 162 | input_mode = img.mode 163 | img = img.convert('RGB') 164 | 165 | np_img = np.array(img, dtype=np.float32) 166 | np_img = 255 * gain * ((np_img / 255) ** gamma) 167 | np_img = np.uint8(np.clip(np_img, 0, 255)) 168 | 169 | img = Image.fromarray(np_img, 'RGB').convert(input_mode) 170 | return img 171 | 172 | 173 | class Compose(object): 174 | """Composes several transforms together. 175 | 176 | Args: 177 | transforms (list of ``Transform`` objects): list of transforms to compose. 178 | 179 | Example: 180 | >>> transforms.Compose([ 181 | >>> transforms.CenterCrop(10), 182 | >>> transforms.ToTensor(), 183 | >>> ]) 184 | """ 185 | 186 | def __init__(self, transforms): 187 | self.transforms = transforms 188 | 189 | def __call__(self, img): 190 | for t in self.transforms: 191 | img = t(img) 192 | return img 193 | 194 | 195 | class ToTensor(object): 196 | """Convert a ``numpy.ndarray`` to tensor. 197 | 198 | Converts a numpy.ndarray (H x W x C) to a torch.FloatTensor of shape (C x H x W). 199 | """ 200 | 201 | def __call__(self, img): 202 | """Convert a ``numpy.ndarray`` to tensor. 203 | 204 | Args: 205 | img (numpy.ndarray): Image to be converted to tensor. 206 | 207 | Returns: 208 | Tensor: Converted image. 209 | """ 210 | # if not(_is_numpy_image(img)): 211 | # raise TypeError('img should be ndarray. Got {}'.format(type(img))) 212 | 213 | if isinstance(img, np.ndarray): 214 | # handle numpy array 215 | if img.ndim == 3: 216 | img = torch.from_numpy(img.transpose((2, 0, 1)).copy()) 217 | elif img.ndim == 2: 218 | img = torch.from_numpy(img.copy()) 219 | else: 220 | raise RuntimeError('img should be ndarray with 2 or 3 dimensions. Got {}'.format(img.ndim)) 221 | 222 | # backward compatibility 223 | # return img.float().div(255) 224 | return img.float() 225 | 226 | 227 | class NormalizeNumpyArray(object): 228 | """Normalize a ``numpy.ndarray`` with mean and standard deviation. 229 | Given mean: ``(M1,...,Mn)`` and std: ``(M1,..,Mn)`` for ``n`` channels, this transform 230 | will normalize each channel of the input ``numpy.ndarray`` i.e. 231 | ``input[channel] = (input[channel] - mean[channel]) / std[channel]`` 232 | 233 | Args: 234 | mean (sequence): Sequence of means for each channel. 235 | std (sequence): Sequence of standard deviations for each channel. 236 | """ 237 | 238 | def __init__(self, mean, std): 239 | self.mean = mean 240 | self.std = std 241 | 242 | def __call__(self, img): 243 | """ 244 | Args: 245 | img (numpy.ndarray): Image of size (H, W, C) to be normalized. 246 | 247 | Returns: 248 | Tensor: Normalized image. 249 | """ 250 | if not(_is_numpy_image(img)): 251 | raise TypeError('img should be ndarray. Got {}'.format(type(img))) 252 | # TODO: make efficient 253 | print(img.shape) 254 | for i in range(3): 255 | img[:,:,i] = (img[:,:,i] - self.mean[i]) / self.std[i] 256 | return img 257 | 258 | class NormalizeTensor(object): 259 | """Normalize an tensor image with mean and standard deviation. 260 | Given mean: ``(M1,...,Mn)`` and std: ``(M1,..,Mn)`` for ``n`` channels, this transform 261 | will normalize each channel of the input ``torch.*Tensor`` i.e. 262 | ``input[channel] = (input[channel] - mean[channel]) / std[channel]`` 263 | 264 | Args: 265 | mean (sequence): Sequence of means for each channel. 266 | std (sequence): Sequence of standard deviations for each channel. 267 | """ 268 | 269 | def __init__(self, mean, std): 270 | self.mean = mean 271 | self.std = std 272 | 273 | def __call__(self, tensor): 274 | """ 275 | Args: 276 | tensor (Tensor): Tensor image of size (C, H, W) to be normalized. 277 | 278 | Returns: 279 | Tensor: Normalized Tensor image. 280 | """ 281 | if not _is_tensor_image(tensor): 282 | raise TypeError('tensor is not a torch image.') 283 | # TODO: make efficient 284 | for t, m, s in zip(tensor, self.mean, self.std): 285 | t.sub_(m).div_(s) 286 | return tensor 287 | 288 | class Rotate(object): 289 | """Rotates the given ``numpy.ndarray``. 290 | 291 | Args: 292 | angle (float): The rotation angle in degrees. 293 | """ 294 | 295 | def __init__(self, angle): 296 | self.angle = angle 297 | 298 | def __call__(self, img): 299 | """ 300 | Args: 301 | img (numpy.ndarray (C x H x W)): Image to be rotated. 302 | 303 | Returns: 304 | img (numpy.ndarray (C x H x W)): Rotated image. 305 | """ 306 | 307 | # order=0 means nearest-neighbor type interpolation 308 | return itpl.rotate(img, self.angle, reshape=False, prefilter=False, order=0) 309 | 310 | 311 | class Resize(object): 312 | """Resize the the given ``numpy.ndarray`` to the given size. 313 | Args: 314 | size (sequence or int): Desired output size. If size is a sequence like 315 | (h, w), output size will be matched to this. If size is an int, 316 | smaller edge of the image will be matched to this number. 317 | i.e, if height > width, then image will be rescaled to 318 | (size * height / width, size) 319 | interpolation (int, optional): Desired interpolation. Default is 320 | ``PIL.Image.BILINEAR`` 321 | """ 322 | 323 | def __init__(self, size, interpolation='nearest'): 324 | assert isinstance(size, int) or isinstance(size, float) or \ 325 | (isinstance(size, collections.Iterable) and len(size) == 2) 326 | self.size = size 327 | self.interpolation = interpolation 328 | 329 | def __call__(self, img): 330 | """ 331 | Args: 332 | img (PIL Image): Image to be scaled. 333 | Returns: 334 | PIL Image: Rescaled image. 335 | """ 336 | if img.ndim == 3: 337 | return misc.imresize(img, self.size, self.interpolation) 338 | elif img.ndim == 2: 339 | return misc.imresize(img, self.size, self.interpolation, 'F') 340 | else: 341 | RuntimeError('img should be ndarray with 2 or 3 dimensions. Got {}'.format(img.ndim)) 342 | 343 | 344 | class CenterCrop(object): 345 | """Crops the given ``numpy.ndarray`` at the center. 346 | 347 | Args: 348 | size (sequence or int): Desired output size of the crop. If size is an 349 | int instead of sequence like (h, w), a square crop (size, size) is 350 | made. 351 | """ 352 | 353 | def __init__(self, size): 354 | if isinstance(size, numbers.Number): 355 | self.size = (int(size), int(size)) 356 | else: 357 | self.size = size 358 | 359 | @staticmethod 360 | def get_params(img, output_size): 361 | """Get parameters for ``crop`` for center crop. 362 | 363 | Args: 364 | img (numpy.ndarray (C x H x W)): Image to be cropped. 365 | output_size (tuple): Expected output size of the crop. 366 | 367 | Returns: 368 | tuple: params (i, j, h, w) to be passed to ``crop`` for center crop. 369 | """ 370 | h = img.shape[0] 371 | w = img.shape[1] 372 | th, tw = output_size 373 | i = int(round((h - th) / 2.)) 374 | j = int(round((w - tw) / 2.)) 375 | 376 | # # randomized cropping 377 | # i = np.random.randint(i-3, i+4) 378 | # j = np.random.randint(j-3, j+4) 379 | 380 | return i, j, th, tw 381 | 382 | def __call__(self, img): 383 | """ 384 | Args: 385 | img (numpy.ndarray (C x H x W)): Image to be cropped. 386 | 387 | Returns: 388 | img (numpy.ndarray (C x H x W)): Cropped image. 389 | """ 390 | i, j, h, w = self.get_params(img, self.size) 391 | 392 | """ 393 | i: Upper pixel coordinate. 394 | j: Left pixel coordinate. 395 | h: Height of the cropped image. 396 | w: Width of the cropped image. 397 | """ 398 | if not(_is_numpy_image(img)): 399 | raise TypeError('img should be ndarray. Got {}'.format(type(img))) 400 | if img.ndim == 3: 401 | return img[i:i+h, j:j+w, :] 402 | elif img.ndim == 2: 403 | return img[i:i + h, j:j + w] 404 | else: 405 | raise RuntimeError('img should be ndarray with 2 or 3 dimensions. Got {}'.format(img.ndim)) 406 | 407 | 408 | class Lambda(object): 409 | """Apply a user-defined lambda as a transform. 410 | 411 | Args: 412 | lambd (function): Lambda/function to be used for transform. 413 | """ 414 | 415 | def __init__(self, lambd): 416 | assert isinstance(lambd, types.LambdaType) 417 | self.lambd = lambd 418 | 419 | def __call__(self, img): 420 | return self.lambd(img) 421 | 422 | 423 | class HorizontalFlip(object): 424 | """Horizontally flip the given ``numpy.ndarray``. 425 | 426 | Args: 427 | do_flip (boolean): whether or not do horizontal flip. 428 | 429 | """ 430 | 431 | def __init__(self, do_flip): 432 | self.do_flip = do_flip 433 | 434 | def __call__(self, img): 435 | """ 436 | Args: 437 | img (numpy.ndarray (C x H x W)): Image to be flipped. 438 | 439 | Returns: 440 | img (numpy.ndarray (C x H x W)): flipped image. 441 | """ 442 | if not(_is_numpy_image(img)): 443 | raise TypeError('img should be ndarray. Got {}'.format(type(img))) 444 | 445 | if self.do_flip: 446 | return np.fliplr(img) 447 | else: 448 | return img 449 | 450 | 451 | class ColorJitter(object): 452 | """Randomly change the brightness, contrast and saturation of an image. 453 | 454 | Args: 455 | brightness (float): How much to jitter brightness. brightness_factor 456 | is chosen uniformly from [max(0, 1 - brightness), 1 + brightness]. 457 | contrast (float): How much to jitter contrast. contrast_factor 458 | is chosen uniformly from [max(0, 1 - contrast), 1 + contrast]. 459 | saturation (float): How much to jitter saturation. saturation_factor 460 | is chosen uniformly from [max(0, 1 - saturation), 1 + saturation]. 461 | hue(float): How much to jitter hue. hue_factor is chosen uniformly from 462 | [-hue, hue]. Should be >=0 and <= 0.5. 463 | """ 464 | def __init__(self, brightness=0, contrast=0, saturation=0, hue=0): 465 | self.brightness = brightness 466 | self.contrast = contrast 467 | self.saturation = saturation 468 | self.hue = hue 469 | 470 | @staticmethod 471 | def get_params(brightness, contrast, saturation, hue): 472 | """Get a randomized transform to be applied on image. 473 | 474 | Arguments are same as that of __init__. 475 | 476 | Returns: 477 | Transform which randomly adjusts brightness, contrast and 478 | saturation in a random order. 479 | """ 480 | transforms = [] 481 | if brightness > 0: 482 | brightness_factor = np.random.uniform(max(0, 1 - brightness), 1 + brightness) 483 | transforms.append(Lambda(lambda img: adjust_brightness(img, brightness_factor))) 484 | 485 | if contrast > 0: 486 | contrast_factor = np.random.uniform(max(0, 1 - contrast), 1 + contrast) 487 | transforms.append(Lambda(lambda img: adjust_contrast(img, contrast_factor))) 488 | 489 | if saturation > 0: 490 | saturation_factor = np.random.uniform(max(0, 1 - saturation), 1 + saturation) 491 | transforms.append(Lambda(lambda img: adjust_saturation(img, saturation_factor))) 492 | 493 | if hue > 0: 494 | hue_factor = np.random.uniform(-hue, hue) 495 | transforms.append(Lambda(lambda img: adjust_hue(img, hue_factor))) 496 | 497 | np.random.shuffle(transforms) 498 | transform = Compose(transforms) 499 | 500 | return transform 501 | 502 | def __call__(self, img): 503 | """ 504 | Args: 505 | img (numpy.ndarray (C x H x W)): Input image. 506 | 507 | Returns: 508 | img (numpy.ndarray (C x H x W)): Color jittered image. 509 | """ 510 | if not(_is_numpy_image(img)): 511 | raise TypeError('img should be ndarray. Got {}'.format(type(img))) 512 | 513 | pil = Image.fromarray(img) 514 | transform = self.get_params(self.brightness, self.contrast, 515 | self.saturation, self.hue) 516 | return np.array(transform(pil)) 517 | 518 | class Crop(object): 519 | """Crops the given PIL Image to a rectangular region based on a given 520 | 4-tuple defining the left, upper pixel coordinated, hight and width size. 521 | 522 | Args: 523 | a tuple: (upper pixel coordinate, left pixel coordinate, hight, width)-tuple 524 | """ 525 | 526 | def __init__(self, i, j, h, w): 527 | """ 528 | i: Upper pixel coordinate. 529 | j: Left pixel coordinate. 530 | h: Height of the cropped image. 531 | w: Width of the cropped image. 532 | """ 533 | self.i = i 534 | self.j = j 535 | self.h = h 536 | self.w = w 537 | 538 | def __call__(self, img): 539 | """ 540 | Args: 541 | img (numpy.ndarray (C x H x W)): Image to be cropped. 542 | Returns: 543 | img (numpy.ndarray (C x H x W)): Cropped image. 544 | """ 545 | 546 | i, j, h, w = self.i, self.j, self.h, self.w 547 | 548 | if not(_is_numpy_image(img)): 549 | raise TypeError('img should be ndarray. Got {}'.format(type(img))) 550 | if img.ndim == 3: 551 | return img[i:i + h, j:j + w, :] 552 | elif img.ndim == 2: 553 | return img[i:i + h, j:j + w] 554 | else: 555 | raise RuntimeError( 556 | 'img should be ndarray with 2 or 3 dimensions. Got {}'.format(img.ndim)) 557 | 558 | def __repr__(self): 559 | return self.__class__.__name__ + '(i={0},j={1},h={2},w={3})'.format( 560 | self.i, self.j, self.h, self.w) 561 | -------------------------------------------------------------------------------- /dataloaders/uwb_dataloader.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import dataloaders.transforms as transforms 4 | from dataloaders.dataloader import MyDataloader 5 | 6 | 7 | class UWBDataloader(MyDataloader): 8 | def __init__(self, root, type, scaler, Y_target, seq_len=128, stride=1, interval=1): 9 | super(UWBDataloader, self).__init__(root, type, scaler, Y_target, seq_len, stride, interval) 10 | 11 | if __name__ == "__main__": 12 | 13 | import criteria 14 | import utils 15 | import os 16 | 17 | from dataloaders.params import INPUT_NAMES, OUTPUT_NAMES 18 | from models.multi_scale_ori import * 19 | from dataloaders.Scaler import DataScaler 20 | 21 | args = utils.parse_command() 22 | print(args) 23 | X_columns = None 24 | if args.x_columns == "all": 25 | X_columns = INPUT_NAMES 26 | else: 27 | raise RuntimeError("X_columns is wrong!!") 28 | 29 | torch.cuda.empty_cache() 30 | 31 | traindir = os.path.join('data', args.data, 'train') 32 | valdir = os.path.join('data', args.data, 'val') 33 | 34 | 35 | def create_data_loaders(args, scaler): 36 | # Data loading code 37 | print("=> creating data loaders ...") 38 | 39 | train_loader = None 40 | val_loader = None 41 | 42 | # sparsifier is a class for generating random sparse depth input from the ground truth 43 | 44 | if args.data == 'EAV': 45 | from dataloaders.uwb_dataloader import UWBDataloader 46 | # from dataloaders.dataloader import MyDataloader as UWBDataloader 47 | if not args.evaluate: 48 | train_dataset = UWBDataloader(traindir, 'train', scaler, X_columns, args.y_type, args.y_target, 49 | seq_len=args.seq_len, stride=args.stride, interval=args.interval) 50 | val_dataset = UWBDataloader(valdir, 'val', scaler, X_columns, args.y_type, args.y_target, 51 | seq_len=args.seq_len, stride=args.stride, interval=args.interval) 52 | else: 53 | raise RuntimeError('Dataset not found.' + 54 | 'The dataset must be included in data_names declared at parse_command() in utils.py') 55 | 56 | # set batch size to be 1 for validation 57 | val_loader = torch.utils.data.DataLoader(val_dataset, 58 | batch_size=1, shuffle=False, num_workers=args.workers, pin_memory=True) 59 | 60 | # put construction of train loader here, for those who are interested in testing only 61 | if not args.evaluate: 62 | train_loader = torch.utils.data.DataLoader( 63 | train_dataset, batch_size=args.batch_size, shuffle=True, 64 | num_workers=args.workers, pin_memory=True, sampler=None, 65 | worker_init_fn=lambda work_id: np.random.seed(work_id)) 66 | # worker_init_fn ensures different sampling patterns for each data loading thread 67 | 68 | print("=> data loaders created.") 69 | return train_loader, val_loader 70 | 71 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import csv 4 | import numpy as np 5 | 6 | import torch 7 | import torch.backends.cudnn as cudnn 8 | import torch.optim 9 | cudnn.benchmark = True 10 | 11 | import criteria 12 | import utils 13 | from dataloaders.params import INPUT_NAMES, OUTPUT_NAMES, INPUT_LEN 14 | from models.multi_scale_ori import * 15 | from dataloaders.Scaler import DataScaler 16 | from dataloaders.datacontainer import ResultContainer 17 | from metrics import * 18 | 19 | args = utils.parse_command() 20 | print(args) 21 | 22 | # torch.cuda.empty_cache() 23 | 24 | os.environ['CUDA_DEVICE_ORDER']='PCI_BUS_ID' 25 | 26 | fieldnames = ['rmse', 'mean', 'median', 'var', 'max'] 27 | 28 | scaledir = os.path.join(args.data, 'all') 29 | traindir = os.path.join(args.data, 'train') 30 | valdir = os.path.join(args.data, 'val') 31 | 32 | NUM_VAL_CSVS = len(os.listdir(valdir)) 33 | mm_scaler = DataScaler(scaledir) 34 | 35 | def create_data_loaders(args, scaler): 36 | # Data loading code 37 | print("=> creating data loaders ...") 38 | 39 | train_loader = None 40 | val_loader = None 41 | 42 | if args.data in ["uwb_dataset"]: 43 | from dataloaders.uwb_dataloader import UWBDataloader 44 | # from dataloaders.dataloader import MyDataloader as UWBDataloader 45 | if not args.evaluate: 46 | train_dataset = UWBDataloader(traindir, 'train', scaler, args.y_target, seq_len=args.seq_len, 47 | stride=args.x_stride, interval=args.x_interval) 48 | val_dataset = UWBDataloader(valdir, 'val', scaler, args.y_target, seq_len=args.seq_len, 49 | stride=args.x_stride, interval=args.x_interval) 50 | else: 51 | raise RuntimeError('Dataset not found.' + 52 | 'The dataset must be included in data_names declared at parse_command() in utils.py') 53 | 54 | # set batch size to be 1 for validation 55 | val_loader = torch.utils.data.DataLoader(val_dataset, 56 | batch_size=1, shuffle=False, num_workers=args.workers, pin_memory=True) 57 | 58 | # put construction of train loader here, for those who are interested in testing only 59 | if not args.evaluate: 60 | train_loader = torch.utils.data.DataLoader( 61 | train_dataset, batch_size=args.batch_size, shuffle=True, 62 | num_workers=args.workers, pin_memory=True, sampler=None, 63 | worker_init_fn=lambda work_id:np.random.seed(work_id)) 64 | # worker_init_fn ensures different sampling patterns for each data loading thread 65 | 66 | print("=> data loaders created.") 67 | return train_loader, val_loader 68 | 69 | def main(): 70 | global args, output_directory, train_csv, test_csvs, mm_scaler 71 | # MinMax-Scaler! 72 | os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu 73 | # evaluation mode 74 | start_epoch = 0 75 | if args.evaluate: 76 | assert os.path.isfile(args.evaluate), \ 77 | "=> no best model found at '{}'".format(args.evaluate) 78 | print("=> loading best model '{}'".format(args.evaluate)) 79 | checkpoint = torch.load(args.evaluate) 80 | output_directory = os.path.dirname(args.evaluate) 81 | args = checkpoint['args'] 82 | start_epoch = checkpoint['epoch'] + 1 83 | model = checkpoint['model'] 84 | print("=> loaded best model (epoch {})".format(checkpoint['epoch'])) 85 | _, val_loader = create_data_loaders(args, mm_scaler) 86 | args.evaluate = True 87 | validate(val_loader, model, checkpoint['epoch'], write_to_file=False) 88 | return 89 | 90 | # optionally resume from a checkpoint 91 | elif args.resume: 92 | chkpt_path = args.resume 93 | assert os.path.isfile(chkpt_path), \ 94 | "=> no checkpoint found at '{}'".format(chkpt_path) 95 | print("=> loading checkpoint '{}'".format(chkpt_path)) 96 | checkpoint = torch.load(chkpt_path) 97 | args = checkpoint['args'] 98 | start_epoch = checkpoint['epoch'] + 1 99 | best_result = checkpoint['best_result'] 100 | model = checkpoint['model'] 101 | optimizer = checkpoint['optimizer'] 102 | output_directory = os.path.dirname(os.path.abspath(chkpt_path)) 103 | print("=> loaded checkpoint (epoch {})".format(checkpoint['epoch'])) 104 | train_loader, val_loader = create_data_loaders(args, mm_scaler) 105 | args.resume = True 106 | 107 | # create new model 108 | else: 109 | train_loader, val_loader = create_data_loaders(args, mm_scaler) 110 | print("=> creating Model ({}) ...".format(args.arch)) 111 | from models.rnn_model import Model 112 | if args.arch == 'LSTM': 113 | model = Model(input_dim=args.x_dim, hidden_dim=args.hidden_size, Y_target=args.y_target, model_type="lstm") 114 | elif args.arch == 'GRU': 115 | model = Model(input_dim=args.x_dim, hidden_dim=args.hidden_size, Y_target=args.y_target, model_type="gru") 116 | if args.arch == 'RNN': 117 | model = Model(input_dim=args.x_dim, hidden_dim=args.hidden_size, Y_target=args.y_target, model_type="rnn") 118 | print("=> model created.") 119 | 120 | model_parameters = list(model.parameters()) 121 | params = sum([np.prod(p.size()) for p in model_parameters]) 122 | print("Num. of parameters: ", params) 123 | 124 | optimizer = torch.optim.Adam(model.parameters(), args.lr, weight_decay=args.weight_decay) 125 | 126 | # model = torch.nn.DataParallel(model).cuda() # for multi-gpu training 127 | model = model.cuda() 128 | 129 | 130 | criterion = nn.MSELoss().cuda() 131 | # create results folder, if not already exists 132 | output_directory = utils.get_output_directory(args) 133 | if not os.path.exists(output_directory): 134 | os.makedirs(output_directory) 135 | train_csv = os.path.join(output_directory, 'train.csv') 136 | test_csvs = [] 137 | for i in range(NUM_VAL_CSVS): 138 | test_csv_name = 'test_' + str(i) + '.csv' 139 | test_csv_each = os.path.join(output_directory, test_csv_name) 140 | test_csvs.append(test_csv_each) 141 | test_csv_total = os.path.join(output_directory, 'test.csv') 142 | test_csvs.append(test_csv_total) 143 | 144 | # 1 indicates total 145 | assert NUM_VAL_CSVS + 1 == len(test_csvs), "Something's wrong!" 146 | 147 | # create new csv files with only header 148 | if not args.resume: 149 | with open(train_csv, 'w') as csvfile: 150 | writer = csv.DictWriter(csvfile, fieldnames=[]) 151 | writer.writeheader() 152 | for test_csv in test_csvs: 153 | with open(test_csv, 'w') as csvfile: 154 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 155 | writer.writeheader() 156 | 157 | best_rmse = 1000000000 158 | 159 | print("=> Learning start.") 160 | for epoch in range(start_epoch, args.epochs): 161 | utils.adjust_learning_rate(optimizer, epoch, args.lr, args.decay_rate, args.decay_step) 162 | print("=> On training...") 163 | train(train_loader, model, criterion, optimizer, epoch) # train for one epoch 164 | if epoch % args.validation_interval == 0: 165 | print("=> On validating...") 166 | result_rmse, results_list = validate(val_loader, model, epoch) # evaluate on validation set 167 | # Save validation results 168 | print("=> On drawing results...") 169 | pngname = os.path.join(output_directory, str(epoch).zfill(2) + "_" 170 | + str(round(result_rmse, 5)) + ".png") 171 | utils.plot_trajectory(pngname, results_list[:-1]) 172 | is_best = best_rmse > result_rmse 173 | if is_best: 174 | best_rmse = result_rmse 175 | best_name = os.path.join(output_directory, "best.csv") 176 | with open(best_name, 'w', newline='') as csvfile: 177 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 178 | writer.writeheader() 179 | for result_container in results_list: 180 | avg = result_container.result 181 | writer.writerow({'rmse': avg.rmse, 'mean': avg.mean, 182 | 'median': avg.median, 'var': avg.var, 'max': avg.error_max}) 183 | 184 | writer.writerow({'rmse': epoch, 'mean': 0, 185 | 'median': 0, 'var': 0, 'max': 0}) 186 | 187 | utils.save_output(results_list, epoch, output_directory) 188 | utils.save_checkpoint({ 189 | 'args': args, 190 | 'epoch': epoch, 191 | 'arch': args.arch, 192 | 'model': model, 193 | 'optimizer': optimizer, 194 | 'scaler': mm_scaler 195 | }, is_best, epoch, output_directory) 196 | 197 | def train(train_loader, model, criterion, optimizer, epoch): 198 | model.train() # switch to train mode 199 | end = time.time() 200 | train_loss = 0 201 | 202 | average_meter = AverageMeter() 203 | 204 | for batch_idx, (x, y_gt, _) in enumerate(train_loader): 205 | 206 | x, y_gt = x.cuda(), y_gt.cuda() 207 | 208 | torch.cuda.synchronize() 209 | data_time = time.time() - end 210 | 211 | # compute pred 212 | end = time.time() 213 | y_pred = model(x) 214 | 215 | loss = criterion(y_pred, y_gt) 216 | optimizer.zero_grad() 217 | loss.backward() # compute gradient and do SGD step 218 | 219 | optimizer.step() 220 | torch.cuda.synchronize() 221 | 222 | gpu_time = time.time() - end 223 | # measure accuracy and record loss 224 | 225 | train_loss += loss.item() 226 | 227 | if (batch_idx + 1) % args.print_freq == 0: 228 | print('Epoch: %d | %d / %d | lr: %.8f' 229 | %(epoch, batch_idx + 1, len(train_loader), optimizer.param_groups[0]['lr'])) 230 | 231 | # avg = average_meter.average() 232 | # with open(train_csv, 'a') as csvfile: 233 | # writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 234 | # writer.writerow({'rmse': avg.rmse, 'rel': avg.absrel, 235 | # 'd1': avg.delta1, 'd2': avg.delta2, 'd3': avg.delta3}) 236 | 237 | def validate(val_loader, model, epoch, write_to_file=True): 238 | 239 | model.eval() 240 | 241 | end = time.time() 242 | 243 | # 0 ~ N-1: 0 ~ N-1th csv 244 | # Nth: total 245 | results_list = [] 246 | for _ in range(NUM_VAL_CSVS+1): 247 | result = ResultContainer(args.y_target) 248 | results_list.append(result) 249 | 250 | count = 0 251 | squares = 0 252 | is_initial = True 253 | 254 | for i, (x, y_gt, csv_id) in enumerate(val_loader): 255 | x, y_gt = x.cuda(), y_gt.cuda() 256 | torch.cuda.synchronize() 257 | data_time = time.time() - end 258 | 259 | end = time.time() 260 | with torch.no_grad(): 261 | y_pred = model(x) 262 | 263 | torch.cuda.synchronize() 264 | gpu_time = time.time() - end 265 | 266 | end = time.time() 267 | 268 | # Unscale output 269 | if args.y_target == "all": 270 | y_pred = y_pred[:, -1, :] 271 | y_gt = y_gt[:, -1, :] 272 | y_pred_unscaled = mm_scaler.undo_scale(y_pred.data.cpu()) 273 | y_gt_unscaled = mm_scaler.undo_scale(y_gt.data.cpu()) 274 | 275 | # Set result 276 | result = Result() 277 | result.evaluate(y_pred_unscaled, y_gt_unscaled) 278 | 279 | # Accumulate its trajectory 280 | results_list[csv_id].accum(y_pred_unscaled, y_gt_unscaled) 281 | 282 | results_list[csv_id].avg_meter.update(result, gpu_time, data_time, x.size(0)) 283 | results_list[-1].avg_meter.update(result, gpu_time, data_time, x.size(0)) 284 | 285 | if (i + 1) % args.print_freq == 0: 286 | avg = results_list[-1].avg_meter.average() 287 | print('%d / %d | RMSE: %.6f MEAN: %.6f MEDIAN: %.4f' 288 | % (i, len(val_loader), avg.rmse, avg.mean, avg.median)) 289 | 290 | rmse_final = None 291 | if write_to_file: 292 | for i_th_idx, test_csv in enumerate(test_csvs): 293 | metric = results_list[i_th_idx].result 294 | if i_th_idx < NUM_VAL_CSVS: 295 | gt_np, pred_np = results_list[i_th_idx].get_result() 296 | elif i_th_idx == NUM_VAL_CSVS: ## For total evaluation 297 | gt_np, pred_np = results_list[0].get_result() 298 | for k in range(1, NUM_VAL_CSVS): 299 | gt_np_tmp, pred_np_tmp = results_list[k].get_result() 300 | gt_np = np.concatenate((gt_np, gt_np_tmp), axis=0) 301 | pred_np = np.concatenate((pred_np, pred_np_tmp), axis=0) 302 | else: 303 | raise RuntimeError("Not implemented!!!") 304 | 305 | metric.evaluate(pred_np, gt_np) 306 | with open(test_csv, 'a') as csvfile: 307 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 308 | writer.writerow({'rmse': metric.rmse, 'mean': metric.mean, 309 | 'median': metric.median, 'var': metric.var, 'max': metric.error_max}) 310 | if i_th_idx == NUM_VAL_CSVS: 311 | rmse_final = metric.rmse 312 | print("Final RMSE is ", rmse_final) 313 | 314 | return rmse_final, results_list 315 | 316 | if __name__ == '__main__': 317 | main() 318 | -------------------------------------------------------------------------------- /materials/01-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/01-28.png -------------------------------------------------------------------------------- /materials/02-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/02-22.png -------------------------------------------------------------------------------- /materials/02-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/02-25.png -------------------------------------------------------------------------------- /materials/02-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/02-32.png -------------------------------------------------------------------------------- /materials/02-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/02-38.png -------------------------------------------------------------------------------- /materials/02_11_00-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/02_11_00-28.png -------------------------------------------------------------------------------- /materials/02_11_23-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/02_11_23-55.png -------------------------------------------------------------------------------- /materials/README.md: -------------------------------------------------------------------------------- 1 | # Trajectories of data 2 | 3 | ## 01-28.csv 4 | 5 | ![01-28](01-28.png) 6 | 7 | -------------------------------------------------------------------------------- /materials/fast-02-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/fast-02-27.png -------------------------------------------------------------------------------- /materials/id_names.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/id_names.png -------------------------------------------------------------------------------- /materials/test.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/test.gif -------------------------------------------------------------------------------- /materials/validation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LimHyungTae/pytorch.uwb.localization/921c51f80c464c7627afe37c1b054c6082000032/materials/validation.png -------------------------------------------------------------------------------- /metrics.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import math 3 | import numpy as np 4 | 5 | EPSILON = 0.000000000000001 6 | def log10(x): 7 | """Convert a new tensor with the base-10 logarithm of the elements of x. """ 8 | return np.log(x) / math.log(10) 9 | 10 | class Result(object): 11 | def __init__(self): 12 | self.rmse = 0.0 13 | self.mean = 0.0 14 | self.median = 0.0 15 | self.var = 0.0 16 | self.error_max = 0.0 17 | self.abs_diff = None 18 | 19 | def update(self, rmse, mean, median, var, error_max): 20 | self.rmse = rmse 21 | self.mean = mean 22 | self.median = median 23 | self.var = var 24 | self.error_max = error_max 25 | 26 | def evaluate(self, output, target): 27 | diff = output - target 28 | self.abs_diff = np.abs(diff) 29 | self.rmse = math.sqrt(np.mean(np.power(diff, 2))) 30 | self.var = np.var(self.abs_diff) 31 | self.mean = np.mean(self.abs_diff) 32 | self.median = np.median(self.abs_diff) 33 | self.error_max = np.amax(self.abs_diff) 34 | 35 | class AverageMeter(object): 36 | def __init__(self): 37 | self.reset() 38 | self.is_initial = True 39 | self.abs_diff = None 40 | 41 | def reset(self): 42 | self.count = 0.0 43 | self.sum_rmse = 0.0 44 | 45 | def update(self, result, gpu_time, data_time, n=1): 46 | self.count += n 47 | if self.is_initial: 48 | self.abs_diff = result.abs_diff 49 | self.is_initial = False 50 | else: 51 | self.abs_diff = np.concatenate((self.abs_diff, result.abs_diff), axis=0) 52 | self.sum_rmse += n*result.rmse 53 | 54 | def average(self): 55 | avg = Result() 56 | var = np.var(self.abs_diff) 57 | mean = np.mean(self.abs_diff) 58 | median = np.median(self.abs_diff) 59 | error_max = np.amax(self.abs_diff) 60 | avg.update(self.sum_rmse / self.count, mean, median, var, error_max) 61 | return avg -------------------------------------------------------------------------------- /models/lstm.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import torch.optim as optim 5 | from torch.autograd import Variable 6 | 7 | 8 | class LSTM(torch.nn.Module): 9 | def __init__(self, input_dim, hidden_dim, output_dim, Y_target="end"): 10 | super(LSTM, self).__init__() 11 | # Input of LSTM: [bs, seq_len, Input_size] 12 | # Output of LSTM: [bs, seq_len, hidden_size] 13 | # Output of Hidden size LSTM: [num_layers, bs, hidden size] 14 | self.Y_target = Y_target 15 | 16 | self.lstm = torch.nn.LSTM(input_dim, hidden_dim, num_layers=1, batch_first=True) 17 | # self.bn = nn.BatchNorm2d(hidden_dim) 18 | # self.fc1 = torch.nn.Linear(hidden_dim, 128, bias=True) 19 | # self.fc2 = torch.nn.Linear(128, output_dim, bias=True) 20 | 21 | def forward(self, x): 22 | print("what?") 23 | x, _status = self.lstm(x) 24 | print("whehe?", x.size()) 25 | # x = self.bn(x) 26 | # x = self.fc1(x[:, -1]) 27 | # x = self.fc2(x[:, -1]) 28 | if self.Y_target == "end": 29 | x = x[:, -1] 30 | return x 31 | 32 | if __name__ == "__main__": 33 | bs = 8 34 | seq_len = 7 35 | input_size = 8 36 | hs = 128 37 | lstm = LSTM(input_size, hs, 2, 'all') 38 | inputs = torch.randn(bs, seq_len, input_size) # make a sequence of length 5 39 | # 40 | out = lstm(inputs) 41 | print(inputs.size()) 42 | print(out.size()) 43 | 44 | -------------------------------------------------------------------------------- /models/models.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import torchvision.models 5 | import collections 6 | import math 7 | 8 | class Unpool(nn.Module): 9 | # Unpool: 2*2 unpooling with zero padding 10 | def __init__(self, num_channels, stride=2): 11 | super(Unpool, self).__init__() 12 | 13 | self.num_channels = num_channels 14 | self.stride = stride 15 | 16 | # create kernel [1, 0; 0, 0] 17 | self.weights = torch.autograd.Variable(torch.zeros(num_channels, 1, stride, stride).cuda()) # currently not compatible with running on CPU 18 | self.weights[:,:,0,0] = 1 19 | 20 | def forward(self, x): 21 | return F.conv_transpose2d(x, self.weights, stride=self.stride, groups=self.num_channels) 22 | 23 | def weights_init(m): 24 | # Initialize filters with Gaussian random weights 25 | if isinstance(m, nn.Conv2d): 26 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 27 | m.weight.data.normal_(0, math.sqrt(2. / n)) 28 | if m.bias is not None: 29 | m.bias.data.zero_() 30 | elif isinstance(m, nn.ConvTranspose2d): 31 | n = m.kernel_size[0] * m.kernel_size[1] * m.in_channels 32 | m.weight.data.normal_(0, math.sqrt(2. / n)) 33 | if m.bias is not None: 34 | m.bias.data.zero_() 35 | elif isinstance(m, nn.BatchNorm2d): 36 | m.weight.data.fill_(1) 37 | m.bias.data.zero_() 38 | 39 | class Decoder(nn.Module): 40 | # Decoder is the base class for all decoders 41 | 42 | names = ['deconv2', 'deconv3', 'upconv', 'upproj'] 43 | 44 | def __init__(self): 45 | super(Decoder, self).__init__() 46 | 47 | self.layer1 = None 48 | self.layer2 = None 49 | self.layer3 = None 50 | self.layer4 = None 51 | 52 | def forward(self, x): 53 | x = self.layer1(x) 54 | x = self.layer2(x) 55 | x = self.layer3(x) 56 | x = self.layer4(x) 57 | return x 58 | 59 | class DeConv(Decoder): 60 | def __init__(self, in_channels, kernel_size): 61 | assert kernel_size>=2, "kernel_size out of range: {}".format(kernel_size) 62 | super(DeConv, self).__init__() 63 | 64 | def convt(in_channels): 65 | stride = 2 66 | padding = (kernel_size - 1) // 2 67 | output_padding = kernel_size % 2 68 | assert -2 - 2*padding + kernel_size + output_padding == 0, "deconv parameters incorrect" 69 | 70 | module_name = "deconv{}".format(kernel_size) 71 | return nn.Sequential(collections.OrderedDict([ 72 | (module_name, nn.ConvTranspose2d(in_channels,in_channels//2,kernel_size, 73 | stride,padding,output_padding,bias=False)), 74 | ('batchnorm', nn.BatchNorm2d(in_channels//2)), 75 | ('relu', nn.ReLU(inplace=True)), 76 | ])) 77 | 78 | self.layer1 = convt(in_channels) 79 | self.layer2 = convt(in_channels // 2) 80 | self.layer3 = convt(in_channels // (2 ** 2)) 81 | self.layer4 = convt(in_channels // (2 ** 3)) 82 | 83 | class UpConv(Decoder): 84 | # UpConv decoder consists of 4 upconv modules with decreasing number of channels and increasing feature map size 85 | def upconv_module(self, in_channels): 86 | # UpConv module: unpool -> 5*5 conv -> batchnorm -> ReLU 87 | upconv = nn.Sequential(collections.OrderedDict([ 88 | ('unpool', Unpool(in_channels)), 89 | ('conv', nn.Conv2d(in_channels,in_channels//2,kernel_size=5,stride=1,padding=2,bias=False)), 90 | ('batchnorm', nn.BatchNorm2d(in_channels//2)), 91 | ('relu', nn.ReLU()), 92 | ])) 93 | return upconv 94 | 95 | def __init__(self, in_channels): 96 | super(UpConv, self).__init__() 97 | self.layer1 = self.upconv_module(in_channels) 98 | self.layer2 = self.upconv_module(in_channels//2) 99 | self.layer3 = self.upconv_module(in_channels//4) 100 | self.layer4 = self.upconv_module(in_channels//8) 101 | 102 | class UpProj(Decoder): 103 | # UpProj decoder consists of 4 upproj modules with decreasing number of channels and increasing feature map size 104 | 105 | class UpProjModule(nn.Module): 106 | # UpProj module has two branches, with a Unpool at the start and a ReLu at the end 107 | # upper branch: 5*5 conv -> batchnorm -> ReLU -> 3*3 conv -> batchnorm 108 | # bottom branch: 5*5 conv -> batchnorm 109 | 110 | def __init__(self, in_channels): 111 | super(UpProj.UpProjModule, self).__init__() 112 | out_channels = in_channels//2 113 | self.unpool = Unpool(in_channels) 114 | self.upper_branch = nn.Sequential(collections.OrderedDict([ 115 | ('conv1', nn.Conv2d(in_channels,out_channels,kernel_size=5,stride=1,padding=2,bias=False)), 116 | ('batchnorm1', nn.BatchNorm2d(out_channels)), 117 | ('relu', nn.ReLU()), 118 | ('conv2', nn.Conv2d(out_channels,out_channels,kernel_size=3,stride=1,padding=1,bias=False)), 119 | ('batchnorm2', nn.BatchNorm2d(out_channels)), 120 | ])) 121 | self.bottom_branch = nn.Sequential(collections.OrderedDict([ 122 | ('conv', nn.Conv2d(in_channels,out_channels,kernel_size=5,stride=1,padding=2,bias=False)), 123 | ('batchnorm', nn.BatchNorm2d(out_channels)), 124 | ])) 125 | self.relu = nn.ReLU() 126 | 127 | def forward(self, x): 128 | x = self.unpool(x) 129 | x1 = self.upper_branch(x) 130 | x2 = self.bottom_branch(x) 131 | x = x1 + x2 132 | x = self.relu(x) 133 | return x 134 | 135 | def __init__(self, in_channels): 136 | super(UpProj, self).__init__() 137 | self.layer1 = self.UpProjModule(in_channels) 138 | self.layer2 = self.UpProjModule(in_channels//2) 139 | self.layer3 = self.UpProjModule(in_channels//4) 140 | self.layer4 = self.UpProjModule(in_channels//8) 141 | 142 | def choose_decoder(decoder, in_channels): 143 | # iheight, iwidth = 10, 8 144 | if decoder[:6] == 'deconv': 145 | assert len(decoder)==7 146 | kernel_size = int(decoder[6]) 147 | return DeConv(in_channels, kernel_size) 148 | elif decoder == "upproj": 149 | return UpProj(in_channels) 150 | elif decoder == "upconv": 151 | return UpConv(in_channels) 152 | else: 153 | assert False, "invalid option for decoder: {}".format(decoder) 154 | 155 | 156 | class ResNet(nn.Module): 157 | def __init__(self, layers, decoder, output_size, in_channels=3, pretrained=True): 158 | 159 | if layers not in [18, 34, 50, 101, 152]: 160 | raise RuntimeError('Only 18, 34, 50, 101, and 152 layer model are defined for ResNet. Got {}'.format(layers)) 161 | 162 | super(ResNet, self).__init__() 163 | pretrained_model = torchvision.models.__dict__['resnet{}'.format(layers)](pretrained=pretrained) 164 | 165 | if in_channels == 3: 166 | self.conv1 = pretrained_model._modules['conv1'] 167 | self.bn1 = pretrained_model._modules['bn1'] 168 | else: 169 | self.conv1 = nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False) 170 | self.bn1 = nn.BatchNorm2d(64) 171 | weights_init(self.conv1) 172 | weights_init(self.bn1) 173 | 174 | self.output_size = output_size 175 | 176 | self.relu = pretrained_model._modules['relu'] 177 | self.maxpool = pretrained_model._modules['maxpool'] 178 | self.layer1 = pretrained_model._modules['layer1'] 179 | self.layer2 = pretrained_model._modules['layer2'] 180 | self.layer3 = pretrained_model._modules['layer3'] 181 | self.layer4 = pretrained_model._modules['layer4'] 182 | 183 | # clear memory 184 | del pretrained_model 185 | 186 | # define number of intermediate channels 187 | if layers <= 34: 188 | num_channels = 512 189 | elif layers >= 50: 190 | num_channels = 2048 191 | 192 | self.conv2 = nn.Conv2d(num_channels,num_channels//2,kernel_size=1,bias=False) 193 | self.bn2 = nn.BatchNorm2d(num_channels//2) 194 | self.decoder = choose_decoder(decoder, num_channels//2) 195 | 196 | # setting bias=true doesn't improve accuracy 197 | self.conv3 = nn.Conv2d(num_channels//32,1,kernel_size=3,stride=1,padding=1,bias=False) 198 | self.bilinear = nn.Upsample(size=self.output_size, mode='bilinear', align_corners=True) 199 | 200 | # weight init 201 | self.conv2.apply(weights_init) 202 | self.bn2.apply(weights_init) 203 | self.decoder.apply(weights_init) 204 | self.conv3.apply(weights_init) 205 | 206 | def forward(self, x): 207 | # resnet 208 | x = self.conv1(x) 209 | x = self.bn1(x) 210 | x = self.relu(x) 211 | x = self.maxpool(x) 212 | x = self.layer1(x) 213 | x = self.layer2(x) 214 | x = self.layer3(x) 215 | x = self.layer4(x) 216 | 217 | x = self.conv2(x) 218 | x = self.bn2(x) 219 | 220 | # decoder 221 | x = self.decoder(x) 222 | x = self.conv3(x) 223 | x = self.bilinear(x) 224 | 225 | return x 226 | -------------------------------------------------------------------------------- /models/multi_scale_ori.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch.nn.functional as F 3 | import math 4 | import torch.utils.model_zoo as model_zoo 5 | 6 | import torch 7 | 8 | 9 | def conv3x3(in_planes, out_planes, stride=1): 10 | """3x3 convolution with padding""" 11 | return nn.Conv1d(in_planes, out_planes, kernel_size=3, stride=stride, 12 | padding=1, bias=False) 13 | 14 | def conv5x5(in_planes, out_planes, stride=1): 15 | return nn.Conv1d(in_planes, out_planes, kernel_size=5, stride=stride, 16 | padding=1, bias=False) 17 | 18 | def conv7x7(in_planes, out_planes, stride=1): 19 | return nn.Conv1d(in_planes, out_planes, kernel_size=7, stride=stride, 20 | padding=1, bias=False) 21 | 22 | 23 | 24 | class BasicBlock3x3(nn.Module): 25 | expansion = 1 26 | 27 | def __init__(self, inplanes3, planes, stride=1, downsample=None): 28 | super(BasicBlock3x3, self).__init__() 29 | self.conv1 = conv3x3(inplanes3, planes, stride) 30 | self.bn1 = nn.BatchNorm1d(planes) 31 | self.relu = nn.ReLU(inplace=True) 32 | self.conv2 = conv3x3(planes, planes) 33 | self.bn2 = nn.BatchNorm1d(planes) 34 | self.downsample = downsample 35 | self.stride = stride 36 | 37 | def forward(self, x): 38 | residual = x 39 | 40 | out = self.conv1(x) 41 | out = self.bn1(out) 42 | out = self.relu(out) 43 | 44 | out = self.conv2(out) 45 | out = self.bn2(out) 46 | 47 | if self.downsample is not None: 48 | residual = self.downsample(x) 49 | 50 | out += residual 51 | out = self.relu(out) 52 | 53 | return out 54 | 55 | 56 | class BasicBlock5x5(nn.Module): 57 | expansion = 1 58 | 59 | def __init__(self, inplanes5, planes, stride=1, downsample=None): 60 | super(BasicBlock5x5, self).__init__() 61 | self.conv1 = conv5x5(inplanes5, planes, stride) 62 | self.bn1 = nn.BatchNorm1d(planes) 63 | self.relu = nn.ReLU(inplace=True) 64 | self.conv2 = conv5x5(planes, planes) 65 | self.bn2 = nn.BatchNorm1d(planes) 66 | self.downsample = downsample 67 | self.stride = stride 68 | 69 | def forward(self, x): 70 | residual = x 71 | 72 | out = self.conv1(x) 73 | out = self.bn1(out) 74 | out = self.relu(out) 75 | 76 | out = self.conv2(out) 77 | out = self.bn2(out) 78 | 79 | if self.downsample is not None: 80 | residual = self.downsample(x) 81 | 82 | d = residual.shape[2] - out.shape[2] 83 | out1 = residual[:,:,0:-d] + out 84 | out1 = self.relu(out1) 85 | # out += residual 86 | 87 | return out1 88 | 89 | 90 | 91 | class BasicBlock7x7(nn.Module): 92 | expansion = 1 93 | 94 | def __init__(self, inplanes7, planes, stride=1, downsample=None): 95 | super(BasicBlock7x7, self).__init__() 96 | self.conv1 = conv7x7(inplanes7, planes, stride) 97 | self.bn1 = nn.BatchNorm1d(planes) 98 | self.relu = nn.ReLU(inplace=True) 99 | self.conv2 = conv7x7(planes, planes) 100 | self.bn2 = nn.BatchNorm1d(planes) 101 | self.downsample = downsample 102 | self.stride = stride 103 | 104 | def forward(self, x): 105 | residual = x 106 | 107 | out = self.conv1(x) 108 | out = self.bn1(out) 109 | out = self.relu(out) 110 | 111 | out = self.conv2(out) 112 | out = self.bn2(out) 113 | 114 | if self.downsample is not None: 115 | residual = self.downsample(x) 116 | 117 | d = residual.shape[2] - out.shape[2] 118 | out1 = residual[:, :, 0:-d] + out 119 | out1 = self.relu(out1) 120 | # out += residual 121 | 122 | return out1 123 | 124 | 125 | class MSResNet_Partial(nn.Module): 126 | def __init__(self, input_channel, window_size, batch_size, layers=[1, 1, 1, 1], num_classes=10): 127 | self.inplanes3 = 64 128 | self.inplanes5 = 64 129 | self.inplanes7 = 64 130 | 131 | if window_size == 128: 132 | maxpool3_kernel_size = 4 133 | maxpool5_kernel_size = 16 134 | maxpool7_kernel_size = 16 135 | 136 | super(MSResNet_Partial, self).__init__() 137 | 138 | self.conv1 = nn.Conv1d(input_channel, 64, kernel_size=7, stride=2, padding=3, 139 | bias=False) 140 | self.bn1 = nn.BatchNorm1d(64) 141 | self.relu = nn.ReLU(inplace=True) 142 | self.maxpool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1) 143 | 144 | self.layer3x3_1 = self._make_layer3(BasicBlock3x3, 64, layers[0], stride=2) 145 | self.layer3x3_2 = self._make_layer3(BasicBlock3x3, 128, layers[1], stride=2) 146 | self.layer3x3_3 = self._make_layer3(BasicBlock3x3, 256, layers[2], stride=2) 147 | self.layer3x3_4 = self._make_layer3(BasicBlock3x3, 512, layers[3], stride=2) 148 | 149 | self.fc = nn.Linear(1024, num_classes) 150 | 151 | # todo: modify the initialization 152 | # for m in self.modules(): 153 | # if isinstance(m, nn.Conv1d): 154 | # n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 155 | # m.weight.data.normal_(0, math.sqrt(2. / n)) 156 | # elif isinstance(m, nn.BatchNorm1d): 157 | # m.weight.data.fill_(1) 158 | # m.bias.data.zero_() 159 | 160 | def _make_layer3(self, block, planes, blocks, stride=2): 161 | downsample = None 162 | if stride != 1 or self.inplanes3 != planes * block.expansion: 163 | downsample = nn.Sequential( 164 | nn.Conv1d(self.inplanes3, planes * block.expansion, 165 | kernel_size=1, stride=stride, bias=False), 166 | nn.BatchNorm1d(planes * block.expansion), 167 | ) 168 | 169 | layers = [] 170 | layers.append(block(self.inplanes3, planes, stride, downsample)) 171 | self.inplanes3 = planes * block.expansion 172 | for i in range(1, blocks): 173 | layers.append(block(self.inplanes3, planes)) 174 | 175 | return nn.Sequential(*layers) 176 | 177 | def forward(self, x0): 178 | x0 = self.conv1(x0) 179 | x0 = self.bn1(x0) 180 | x0 = self.relu(x0) 181 | x0 = self.maxpool(x0) 182 | 183 | x = self.layer3x3_1(x0) 184 | x = self.layer3x3_2(x) 185 | x = self.layer3x3_3(x) 186 | x = self.layer3x3_4(x) 187 | # x = self.maxpool3(x) 188 | 189 | x = torch.flatten(x, 1) 190 | x = self.fc(x) 191 | 192 | x = F.softmax(x, dim=1) 193 | return x 194 | 195 | 196 | 197 | class MSResNet(nn.Module): 198 | def __init__(self, input_channel, window_size, layers=[1, 1, 1, 1], num_classes=10): 199 | self.inplanes3 = 64 200 | self.inplanes5 = 64 201 | self.inplanes7 = 64 202 | 203 | if window_size == 128: 204 | maxpool3_kernel_size = 4 205 | maxpool5_kernel_size = 16 206 | maxpool7_kernel_size = 16 207 | 208 | super(MSResNet, self).__init__() 209 | 210 | self.conv1 = nn.Conv1d(input_channel, 64, kernel_size=7, stride=2, padding=3, 211 | bias=False) 212 | self.bn1 = nn.BatchNorm1d(64) 213 | self.relu = nn.ReLU(inplace=True) 214 | self.maxpool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1) 215 | 216 | self.layer3x3_1 = self._make_layer3(BasicBlock3x3, 64, layers[0], stride=2) 217 | self.layer3x3_2 = self._make_layer3(BasicBlock3x3, 128, layers[1], stride=2) 218 | self.layer3x3_3 = self._make_layer3(BasicBlock3x3, 256, layers[2], stride=2) 219 | # self.layer3x3_4 = self._make_layer3(BasicBlock3x3, 512, layers[3], stride=2) 220 | 221 | # maxplooing kernel size: 16, 11, 6 222 | self.maxpool3 = nn.AvgPool1d(kernel_size=16, stride=1, padding=0) 223 | # 224 | # self.layer5x5_1 = self._make_layer5(BasicBlock5x5, 64, layers[0], stride=2) 225 | # self.layer5x5_2 = self._make_layer5(BasicBlock5x5, 128, layers[1], stride=2) 226 | # self.layer5x5_3 = self._make_layer5(BasicBlock5x5, 256, layers[2], stride=2) 227 | # # self.layer5x5_4 = self._make_layer5(BasicBlock5x5, 512, layers[3], stride=2) 228 | # self.maxpool5 = nn.AvgPool1d(kernel_size=11, stride=1, padding=0) 229 | # 230 | # self.layer7x7_1 = self._make_layer7(BasicBlock7x7, 64, layers[0], stride=2) 231 | # self.layer7x7_2 = self._make_layer7(BasicBlock7x7, 128, layers[1], stride=2) 232 | # self.layer7x7_3 = self._make_layer7(BasicBlock7x7, 256, layers[2], stride=2) 233 | # # self.layer7x7_4 = self._make_layer7(BasicBlock7x7, 512, layers[3], stride=2) 234 | # self.maxpool7 = nn.AvgPool1d(kernel_size=6, stride=1, padding=0) 235 | 236 | # self.drop = nn.Dropout(p=0.2) 237 | self.fc = nn.Linear(256*3, num_classes) 238 | 239 | # todo: modify the initialization 240 | # for m in self.modules(): 241 | # if isinstance(m, nn.Conv1d): 242 | # n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 243 | # m.weight.data.normal_(0, math.sqrt(2. / n)) 244 | # elif isinstance(m, nn.BatchNorm1d): 245 | # m.weight.data.fill_(1) 246 | # m.bias.data.zero_() 247 | 248 | def _make_layer3(self, block, planes, blocks, stride=2): 249 | downsample = None 250 | if stride != 1 or self.inplanes3 != planes * block.expansion: 251 | downsample = nn.Sequential( 252 | nn.Conv1d(self.inplanes3, planes * block.expansion, 253 | kernel_size=1, stride=stride, bias=False), 254 | nn.BatchNorm1d(planes * block.expansion), 255 | ) 256 | 257 | layers = [] 258 | layers.append(block(self.inplanes3, planes, stride, downsample)) 259 | self.inplanes3 = planes * block.expansion 260 | for i in range(1, blocks): 261 | layers.append(block(self.inplanes3, planes)) 262 | 263 | return nn.Sequential(*layers) 264 | 265 | def _make_layer5(self, block, planes, blocks, stride=2): 266 | downsample = None 267 | if stride != 1 or self.inplanes5 != planes * block.expansion: 268 | downsample = nn.Sequential( 269 | nn.Conv1d(self.inplanes5, planes * block.expansion, 270 | kernel_size=1, stride=stride, bias=False), 271 | nn.BatchNorm1d(planes * block.expansion), 272 | ) 273 | 274 | layers = [] 275 | layers.append(block(self.inplanes5, planes, stride, downsample)) 276 | self.inplanes5 = planes * block.expansion 277 | for i in range(1, blocks): 278 | layers.append(block(self.inplanes5, planes)) 279 | 280 | return nn.Sequential(*layers) 281 | 282 | 283 | def _make_layer7(self, block, planes, blocks, stride=2): 284 | downsample = None 285 | if stride != 1 or self.inplanes7 != planes * block.expansion: 286 | downsample = nn.Sequential( 287 | nn.Conv1d(self.inplanes7, planes * block.expansion, 288 | kernel_size=1, stride=stride, bias=False), 289 | nn.BatchNorm1d(planes * block.expansion), 290 | ) 291 | 292 | layers = [] 293 | layers.append(block(self.inplanes7, planes, stride, downsample)) 294 | self.inplanes7 = planes * block.expansion 295 | for i in range(1, blocks): 296 | layers.append(block(self.inplanes7, planes)) 297 | 298 | return nn.Sequential(*layers) 299 | 300 | def forward(self, x0): 301 | x0 = self.conv1(x0) 302 | x0 = self.bn1(x0) 303 | x0 = self.relu(x0) 304 | x0 = self.maxpool(x0) 305 | 306 | x = self.layer3x3_1(x0) 307 | x = self.layer3x3_2(x) 308 | x = self.layer3x3_3(x) 309 | # x = self.layer3x3_4(x) 310 | print(x.size()) 311 | x = self.maxpool3(x) 312 | 313 | y = self.layer5x5_1(x0) 314 | y = self.layer5x5_2(y) 315 | y = self.layer5x5_3(y) 316 | # y = self.layer5x5_4(y) 317 | 318 | print(y.size()) 319 | y = self.maxpool5(y) 320 | 321 | z = self.layer7x7_1(x0) 322 | z = self.layer7x7_2(z) 323 | z = self.layer7x7_3(z) 324 | # z = self.layer7x7_4(z) 325 | print(z.size()) 326 | z = self.maxpool7(z) 327 | 328 | out = torch.cat([x, y, z], dim=1) 329 | 330 | out = out.squeeze() 331 | # out = self.drop(out) 332 | out1 = self.fc(out) 333 | 334 | return out1, out 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /models/rnn_model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import torch.optim as optim 5 | from torch.autograd import Variable 6 | 7 | OUTPUT_LENGTH = 2 # x and y 8 | 9 | class Model(torch.nn.Module): 10 | def __init__(self, input_dim, hidden_dim, Y_target="end", model_type="lstm"): 11 | super(Model, self).__init__() 12 | self.Y_target = Y_target 13 | 14 | if model_type == "lstm": 15 | self.rnn = torch.nn.LSTM(input_dim, hidden_dim, num_layers=1, batch_first=True) 16 | elif model_type == "rnn": 17 | self.rnn = torch.nn.RNN(input_dim, hidden_dim, num_layers=1, batch_first=True) 18 | elif model_type == "gru": 19 | self.rnn = torch.nn.GRU(input_dim, hidden_dim, num_layers=1, batch_first=True) 20 | 21 | self.fc1 = torch.nn.Linear(hidden_dim, hidden_dim * 2, bias=True) 22 | self.bn = torch.nn.BatchNorm1d(hidden_dim * 2) 23 | self.relu = torch.nn.ReLU(inplace=False) 24 | self.fc2 = torch.nn.Linear(hidden_dim * 2, OUTPUT_LENGTH, bias=True) 25 | 26 | def forward(self, x): 27 | x, _status = self.rnn(x) 28 | if self.Y_target == "end": 29 | x = x[:, -1] 30 | x = self.relu(x) 31 | x = self.fc1(x) 32 | x = self.bn(x) 33 | x = self.relu(x) 34 | x = self.fc2(x) 35 | elif self.Y_target == "all": 36 | x = self.relu(x) 37 | bs, seq, hs = x.size() 38 | x = x.reshape(bs * seq, hs) 39 | x = self.fc1(x) 40 | x = self.bn(x) 41 | x = self.relu(x) 42 | x = self.fc2(x) 43 | x = x.view(bs, seq, OUTPUT_LENGTH) 44 | else: 45 | raise RuntimeError("Not implemented!!") 46 | 47 | return x 48 | 49 | if __name__ == "__main__": 50 | bs = 256 51 | seq_len = 12 52 | input_size = 8 53 | hs = 128 54 | lstm = Model(input_size, hs, "all") 55 | inputs = torch.randn(bs, seq_len, input_size) # make a sequence of length 5 56 | # 57 | print(inputs.size()) 58 | out = lstm(inputs) 59 | print(out.size()) 60 | 61 | -------------------------------------------------------------------------------- /models/stacked_bidirectional.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import torch.optim as optim 5 | from torch.autograd import Variable 6 | 7 | 8 | class StackedBiLSTM(torch.nn.Module): 9 | def __init__(self, input_dim, hidden_dim, output_dim, Y_target="end"): 10 | super(StackedBiLSTM, self).__init__() 11 | # Input of LSTM: [bs, seq_len, Input_size] 12 | # Output of LSTM: [bs, seq_len, hidden_size] 13 | # Output of Hidden size LSTM: [num_layers, bs, hidden size] 14 | self.Y_target = Y_target 15 | 16 | self.lstm = torch.nn.LSTM(input_dim, hidden_dim, num_layers=2, batch_first=True, bidirectional=True) 17 | # self.bn = nn.BatchNorm2d(hidden_dim) 18 | # self.fc1 = torch.nn.Linear(hidden_dim, 128, bias=True) 19 | # self.fc2 = torch.nn.Linear(128, output_dim, bias=True) 20 | 21 | def forward(self, x): 22 | # print(x.size()) 23 | x, _status = self.lstm(x) 24 | # x = self.bn(x) 25 | # x = self.fc1(x[:, -1]) 26 | # x = self.fc2(x[:, -1]) 27 | print(x.size()) 28 | if self.Y_target == "end": 29 | x = x[:, -1] 30 | return x 31 | 32 | if __name__ == "__main__": 33 | bs = 8 34 | seq_len = 7 35 | input_size = 3 36 | hs = 128 37 | lstm = StackedBiLSTM(input_size, hs, 1) 38 | inputs = torch.randn(bs, seq_len, input_size) # make a sequence of length 5 39 | # 40 | print(inputs.size()) 41 | out = lstm(inputs) 42 | print(out.size()) 43 | print(out[:,-1].size()) 44 | print(out.size()) 45 | # lstm = Net(input_size, hs, 1) 46 | # out, _state = lstm(inputs) 47 | 48 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: linux-64 4 | _libgcc_mutex=0.1=main 5 | blas=1.0=mkl 6 | ca-certificates=2020.4.5.2=hecda079_0 7 | certifi=2020.4.5.2=py36h9f0ad1d_0 8 | cudatoolkit=10.1.243=h6bb024c_0 9 | cycler=0.10.0=py_2 10 | dbus=1.13.6=he372182_0 11 | expat=2.2.9=he1b5a44_2 12 | faiss-cpu=1.6.3=py36h6bb024c_0 13 | fontconfig=2.13.1=he4413a7_1000 14 | freetype=2.9.1=h8a8886c_1 15 | fvcore=0.1.1.post20200610=py_0 16 | glib=2.63.1=h3eb4bd4_1 17 | gst-plugins-base=1.14.0=hbbd80ab_1 18 | gstreamer=1.14.0=hb31296c_0 19 | h5py=2.10.0=nompi_py36h513d04c_102 20 | hdf5=1.10.5=nompi_h3c11f04_1104 21 | icu=58.2=hf484d3e_1000 22 | intel-openmp=2020.1=217 23 | joblib=0.15.1=py_0 24 | jpeg=9b=h024ee3a_2 25 | kiwisolver=1.2.0=py36hdb11119_0 26 | ld_impl_linux-64=2.33.1=h53a641e_7 27 | libedit=3.1.20181209=hc058e9b_0 28 | libffi=3.3=he6710b0_1 29 | libgcc-ng=9.1.0=hdf63c60_0 30 | libgfortran-ng=7.3.0=hdf63c60_0 31 | libpng=1.6.37=hbc83047_0 32 | libstdcxx-ng=9.1.0=hdf63c60_0 33 | libtiff=4.1.0=h2733197_1 34 | libuuid=2.32.1=h14c3975_1000 35 | libxcb=1.13=h14c3975_1002 36 | libxml2=2.9.9=hea5a465_1 37 | lz4-c=1.9.2=he6710b0_0 38 | matplotlib=3.1.3=py36_0 39 | matplotlib-base=3.1.3=py36hef1b27d_0 40 | mkl=2020.1=217 41 | mkl-service=2.3.0=py36he904b0f_0 42 | mkl_fft=1.0.15=py36ha843d7b_0 43 | mkl_random=1.1.1=py36h0573a6f_0 44 | ncurses=6.2=he6710b0_1 45 | ninja=1.9.0=py36hfd86e86_0 46 | numpy=1.18.1=py36h4f9e942_0 47 | numpy-base=1.18.1=py36hde5b4d6_1 48 | olefile=0.46=py36_0 49 | openssl=1.1.1g=h516909a_0 50 | pandas=1.0.3=py36h0573a6f_0 51 | pcre=8.44=he1b5a44_0 52 | pillow=7.1.2=py36hb39fc2d_0 53 | pip=20.0.2=py36_3 54 | portalocker=1.7.0=py36h9f0ad1d_0 55 | pthread-stubs=0.4=h14c3975_1001 56 | pyparsing=2.4.7=pyh9f0ad1d_0 57 | pyqt=5.9.2=py36hcca6a23_4 58 | python=3.6.10=h7579374_2 59 | python-dateutil=2.8.1=py_0 60 | python_abi=3.6=1_cp36m 61 | pytorch=1.4.0=py3.6_cuda10.1.243_cudnn7.6.3_0 62 | pytz=2020.1=py_0 63 | pyyaml=5.3.1=py36h8c4c3a4_0 64 | qt=5.9.7=h5867ecd_1 65 | readline=8.0=h7b6447c_0 66 | scikit-learn=0.22.1=py36hd81dba3_0 67 | scipy=1.1.0=py36hfa4b5c9_1 68 | setuptools=47.1.1=py36_0 69 | sip=4.19.8=py36hf484d3e_0 70 | six=1.15.0=py_0 71 | sqlite=3.31.1=h62c20be_1 72 | tabulate=0.8.7=pyh9f0ad1d_0 73 | termcolor=1.1.0=py_2 74 | tk=8.6.8=hbc83047_0 75 | torchvision=0.5.0=py36_cu101 76 | tornado=6.0.4=py36h8c4c3a4_1 77 | tqdm=4.46.1=pyh9f0ad1d_0 78 | wheel=0.34.2=py36_0 79 | xorg-libxau=1.0.9=h14c3975_0 80 | xorg-libxdmcp=1.1.3=h516909a_0 81 | xz=5.2.5=h7b6447c_0 82 | yacs=0.1.6=py_0 83 | yaml=0.2.5=h516909a_0 84 | zlib=1.2.11=h7b6447c_3 85 | zstd=1.4.4=h0b5b093_3 86 | -------------------------------------------------------------------------------- /scribbles.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | loss = nn.CrossEntropyLoss() 4 | input = torch.randn(3, 5, requires_grad=True) 5 | m = nn.Softmax(dim=1) 6 | input = m(input) 7 | target = torch.empty(3, dtype=torch.long).random_(5) 8 | print(input) 9 | print(target) 10 | print(input.size(), target.size()) 11 | output = loss(input, target) 12 | output.backward() -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import shutil 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from math import ceil 7 | from dataloaders.params import INPUT_NAMES, OUTPUT_NAMES 8 | 9 | def parse_command(): 10 | model_names = ['RNN', 'GRU', 'LSTM', "Bi-LSTM"] 11 | loss_names = ['l1', 'l2'] 12 | data_names = ["uwb_dataset"] 13 | 14 | import argparse 15 | parser = argparse.ArgumentParser(description='UWB_LSTM') 16 | parser.add_argument('--arch', '-a', metavar='ARCH', default='LSTM', choices=model_names, 17 | help='model architecture: ' + ' | '.join(model_names) + ' (default: LSTM)') 18 | parser.add_argument('--memo', default='0301', type=str) 19 | parser.add_argument('--data', metavar='DATA', default='uwb_dataset', choices=data_names, 20 | help='dataset: ' + ' | '.join(data_names) + ' (default: original one, which I acquired)') 21 | ########## RNN Params ########## 22 | parser.add_argument('-h_s', '--hidden-size', default=256, type=int, help='Hidden size') 23 | parser.add_argument('--seq-len', '-sl', default=12, type=int, metavar='SEQLENGTH', 24 | help='Sequence length for input') 25 | parser.add_argument('--x-stride', default=1, type=int, metavar='ST', 26 | help='Stride between each UWB sample in UWB samples(default: 1)') 27 | parser.add_argument('--x-dim', default=8, type=int, metavar='DIM', 28 | help='Input dimension (default: 8 since the number of UWB is 8)') 29 | parser.add_argument('--x-interval', default=1, type=int, metavar='I', 30 | help='Interval of each datum in UWB samples (default: 1)') 31 | parser.add_argument('--y-target', default="all", type=str, metavar='OUTPUT_target', choices=["end", "all"], 32 | help='When calculating loss function!') 33 | ########## Learning Params ########## 34 | parser.add_argument('--gpu', default="1") 35 | parser.add_argument('-v', '--validation-interval', default=1, type=int, metavar='VI', 36 | help='Validation interval. The number of data loading workers (default: 10)') 37 | parser.add_argument('-j', '--workers', default=0, type=int, metavar='N', 38 | help='number of data loading workers (default: 10)') 39 | parser.add_argument('--epochs', default=30, type=int, metavar='N', 40 | help='number of total epochs to run (default: 15)') 41 | parser.add_argument('-c', '--criterion', metavar='LOSS', default='l1', choices=loss_names, 42 | help='loss function: ' + ' | '.join(loss_names) + ' (default: l1)') 43 | parser.add_argument('-b', '--batch-size', default=3000, type=int, help='mini-batch size') 44 | parser.add_argument('--decay-rate', default=0.7, type=float, metavar='dr', 45 | help='number of decay_step (default: 0.2)') 46 | parser.add_argument('--decay-step', default=5, type=int, metavar='ds', 47 | help='number of decay_step (default: 5)') 48 | parser.add_argument('--lr', '--learning-rate', default=0.001, type=float, 49 | metavar='LR', help='initial learning rate (default 0.001)') 50 | parser.add_argument('--momentum', default=0.9, type=float, metavar='M', 51 | help='momentum') 52 | parser.add_argument('--weight-decay', '--wd', default=1e-4, type=float, 53 | metavar='W', help='weight decay (default: 1e-4)') 54 | parser.add_argument('--print-freq', '-p', default=300, type=int, 55 | metavar='N', help='print frequency (default: 10)') 56 | parser.add_argument('--resume', default='', type=str, metavar='PATH', 57 | help='path to latest checkpoint (default: none)') 58 | parser.add_argument('-e', '--evaluate', dest='evaluate', type=str, default='', 59 | help='evaluate model on validation set') 60 | parser.add_argument('--no-pretrain', dest='pretrained', action='store_false', 61 | help='not to use ImageNet pre-trained weights') 62 | 63 | parser.set_defaults(pretrained=True) 64 | args = parser.parse_args() 65 | 66 | return args 67 | 68 | def save_checkpoint(state, is_best, epoch, output_directory): 69 | checkpoint_filename = os.path.join(output_directory, 'checkpoint-' + str(epoch) + '.pth.tar') 70 | torch.save(state, checkpoint_filename) 71 | if is_best: 72 | best_filename = os.path.join(output_directory, 'model_best.pth.tar') 73 | shutil.copyfile(checkpoint_filename, best_filename) 74 | if epoch > 0: 75 | prev_checkpoint_filename = os.path.join(output_directory, 'checkpoint-' + str(epoch-1) + '.pth.tar') 76 | if os.path.exists(prev_checkpoint_filename): 77 | os.remove(prev_checkpoint_filename) 78 | 79 | 80 | def save_output(results_list, epoch, output_directory): 81 | output_filename = os.path.join(output_directory, 'output-' + str(epoch) + '.pth.tar') 82 | torch.save({"output": results_list}, output_filename) 83 | 84 | 85 | def adjust_learning_rate(optimizer, epoch, lr_init, decay_rate, decay_step): 86 | """Sets the learning rate to the initial LR decayed by 10 every 5 epochs""" 87 | lr = lr_init * (decay_rate ** (epoch // decay_step)) 88 | for param_group in optimizer.param_groups: 89 | param_group['lr'] = lr 90 | 91 | 92 | def get_output_directory(args): 93 | output_directory = os.path.join('results', 94 | '{}_{}.y_target={}.seqlen={}.interal={}.stride={}.arch={}..criterion={}.lr={}.dr={}.ds={}.bs={}'. 95 | format(args.memo, args.data, args.y_target, args.seq_len, args.x_interval, args.x_stride, 96 | args.arch, args.criterion, args.lr, args.decay_rate, args.decay_step, args.batch_size)) 97 | 98 | if output_directory.split("/")[1] in os.listdir('results'): 99 | from random import random 100 | output_directory = output_directory + "_" + str(int(random() * 10000)) 101 | return output_directory 102 | 103 | 104 | def calc_RMSE(scaler, y_gt, y_pred): 105 | y_gt_cpu = y_gt.cpu().detach().numpy() 106 | y_pred_cpu = y_pred.cpu().detach().numpy() 107 | 108 | y_gt_unscaled = scaler.undo_scale(y_gt_cpu) 109 | y_pred_unscaled = scaler.undo_scale(y_pred_cpu) 110 | 111 | return np.sqrt(np.mean((y_gt_unscaled - y_pred_unscaled) ** 2)) 112 | 113 | def plot_trajectory(png_name, result_containers): 114 | # For alpha 115 | MIN_VALUES = [-2.7, -2.7, -2.7, -2.7] 116 | MAX_VALUES = [2.7, 2.7, 2.7, 2.7] 117 | 118 | 119 | len_col = 4 120 | len_row = ceil(len(result_containers) / len_col) 121 | plt.figure(figsize=(22, 5)) 122 | for i, result_container in enumerate(result_containers): 123 | 124 | plt.subplot(len_row, len_col, i + 1) 125 | y_gt_set, y_pred_set = result_container.trajectory_container.get_results() 126 | len_x = y_gt_set.shape[0] 127 | plt.plot(y_gt_set[:, 0], y_gt_set[:, 1], 'g', linestyle='-', label='gt') 128 | plt.plot(y_pred_set[:, 0], y_pred_set[:, 1], 'b', linestyle='--', label='pred') 129 | 130 | plt.grid() 131 | plt.xlim(MIN_VALUES[i], MAX_VALUES[i]) 132 | plt.ylim(MIN_VALUES[i], MAX_VALUES[i]) 133 | plt.legend() 134 | plt.rcParams["legend.loc"] = 'lower left' 135 | 136 | fig = plt.gcf() 137 | fig.savefig(png_name) 138 | plt.close('all') 139 | 140 | 141 | if __name__ == "__main__": 142 | abs_dir = "/home/shapelim/ws/kari-lstm/uwb_dataset/val" 143 | csv_names = sorted(os.listdir("/home/shapelim/ws/kari-lstm/uwb_dataset/val")) 144 | plt.figure(figsize=(20, 5)) 145 | for i, csvname in enumerate(csv_names): 146 | fname = os.path.join(abs_dir, csvname) 147 | y_gt_set = np.loadtxt(fname, delimiter=',')[:, -2:] 148 | plt.subplot(1, 4, i + 1) 149 | plt.plot(y_gt_set[:, 0], y_gt_set[:, 1], 'g', linestyle='-', label='gt') 150 | plt.grid() 151 | plt.legend() 152 | 153 | fig = plt.gcf() 154 | fig.savefig("debug.png") 155 | --------------------------------------------------------------------------------