├── 4day_prediction.png ├── 4day_result_plot.png ├── LICENSE ├── README.md ├── _config.yml ├── code ├── model.py ├── process.py ├── readme.md ├── train.py └── utils.py ├── data └── readme.md ├── deepRnn.png ├── demo.png ├── plotResult.png └── table1.png /4day_prediction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psu1/DeepRNN/65ac0c84f5ca85d498e3028902265e48845024e0/4day_prediction.png -------------------------------------------------------------------------------- /4day_result_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psu1/DeepRNN/65ac0c84f5ca85d498e3028902265e48845024e0/4day_result_plot.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Peng Su 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [arXiv](https://arxiv.org/abs/1705.04524)    2 | [Paper](https://ieeexplore.ieee.org/abstract/document/8333434/)    3 | [Poster](http://www.ee.cuhk.edu.hk/~psu/img/BHI2018.pdf)    4 | [Project](https://psu1.github.io/DeepRNN/) 5 | 6 | 7 | ## Demo (real-time BP prediction) 8 | In nutshell, we build a novel Recurrent Neural Networks to predict arterial blood pressure (BP) from ECG and PPG signals which can be easily collected from wearable devices. The top two channels in the demo below are input ECG and PPG signals, the bottom channel plots the real-time BP prediction and BP ground truth. 9 | [![](demo.png)](https://www.youtube.com/watch?v=4fLw79l3Dh4&feature=youtu.be) 10 | 11 | ## Project at a glance 12 | 13 | Existing methods for arterial blood pressure (BP) estimation directly map the input physiological signals to output BP values without explicitly modeling the underlying temporal dependencies in BP dynamics. As a result, these models suffer from accuracy decay over a long time and thus require frequent calibration. In this work, we address this issue by formulating BP estimation as a sequence prediction problem in which both the input and target are temporal sequences. We propose a novel deep recurrent neural network (RNN) consisting of multilayered Long Short-Term Memory (LSTM) networks, which are incorporated with (1) a bidirectional structure to access larger-scale context information of input sequence, and (2) residual connections to allow gradients in deep RNN to propagate more effectively.The proposed deep RNN model was tested on a static BP dataset, and it achieved root mean square error (RMSE) of 3.90 and 2.66 mmHg for systolic BP (SBP) and diastolic BP (DBP) prediction respectively, surpassing the accuracy of traditional BP prediction models. On a multi-day BP dataset, the deep RNN achieved RMSE of 3.84, 5.25, 5.80 and 5.81 mmHg for the 1st day, 2nd day, 4th day and 6th month after the 1st day SBP prediction, and 1.80, 4.78, 5.0, 5.21 mmHg for corresponding DBP prediction, respectively, which outperforms all previous models with notable improvement. The experimental results suggest that modeling the temporal dependencies in BP dynamics significantly improves the long-term BP prediction accuracy. 14 | 15 | ## Network Architecture 16 | 17 | Simple but proven to work very well. Detailed mathematical explanation behind such design is provided in paper Section 3. 18 | 19 | ![](deepRnn.png) 20 | DeepRNN architecture. Each rectangular box is an LSTM cell. The green dashed box at bottom is a bidirectional 21 | LSTM layer consisting of forward (orange) and backward (green) LSTM. The orange dashed box depicts the LSTM 22 | layer with residual connections. 23 | 24 | 25 | ## Results 26 | 27 | ### 1.Generalization capability across different subjects. 28 | Detailed analysis of our Deep RNN models with comparison with different reference models. DeepRNN-xL 29 | represents a x layer RNN model. All the models are validated on the static continuous BP dataset. (unit: mmHg) 30 |

31 | 32 |

33 | 34 | 35 | Bland-Altman plots of the overall SBP and DBP predictions by a DeepRNN-4L model on the static continuous BP dataset. 36 | ![](plotResult.png) 37 | 38 | 39 | ### 2.Generalization capability over a long period of time. 40 | 41 | Overall RMSE comparison of different models on the multi-day continuous BP dataset. 42 | ![](4day_result_plot.png) 43 | 44 | Qualitative example to show the 45 | capability of DeepRNN to track long-term BP variation 46 | Figure (a), (b), (c) and (d) represent the results of 1st day, 2nd day, 4th day and 6th month after the 1st day, respectively. 47 | ![](4day_prediction.png) 48 | 49 | 50 | 51 | 52 | ## Citation 53 | @inproceedings{su2018long, 54 | title={Long-term Blood Pressure Prediction with Deep Recurrent Neural Networks}, 55 | author={Su, Peng and Ding, Xiao-Rong and Zhang, Yuan-Ting and Liu, Jing and Miao, Fen and Zhao, Ni}, 56 | booktitle={Biomedical \& Health Informatics (BHI), 2018 IEEE EMBS International Conference on}, 57 | pages={323--328}, 58 | year={2018}, 59 | organization={IEEE} 60 | } 61 | 62 | @article{su2017predicting, 63 | title={Predicting Blood Pressure with Deep Bidirectional LSTM Network}, 64 | author={Su, Peng and Ding, Xiaorong and Zhang, Yuanting and Li, Ye and Zhao, Ni}, 65 | journal={arXiv preprint arXiv:1705.04524}, 66 | year={2017} 67 | } 68 | 69 | ## Acknowledgment 70 | The Chinese University of Hong Kong 71 | Intelligent Sensing Limited 72 | Key Laboratory for Health Informatics of the Chinese Academy of Sciences 73 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: Long-term Blood Pressure Prediction with Deep Recurrent Neural Networks 3 | description: Peng Su, Xiao-Rong Ding, Yuan-Ting Zhang, Jing Liu, Fen Miao, and Ni Zhao 4 | -------------------------------------------------------------------------------- /code/model.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import collections 4 | 5 | import tensorflow as tf 6 | import numpy as np 7 | 8 | import process 9 | import utils 10 | 11 | TrainForwardOutputTuple = collections.namedtuple("TrainForwardOutputTuplee", 12 | ("train_summary", "train_loss", # need to change to train_forward_loss 13 | "predict_beat_count", 14 | "global_step", "batch_size", "grad_norm", 15 | "learning_rate", "train_src_ref", "train_tgt_ref", 16 | "train_src2tgt_output", "train_tgt2src_output", 17 | "src_seq_len", "tgt_seq_len")) 18 | 19 | TrainBackwardOutputTuple = collections.namedtuple("TrainBackwardOutputTuplee", 20 | ("train_summary", "train_backward_loss", 21 | "global_step", "batch_size", "grad_norm", 22 | "learning_rate", "train_src_ref", "train_tgt_ref", 23 | "train_src2tgt2src_output", "train_tgt2src2tgt_output")) 24 | 25 | EvalOutputTuple = collections.namedtuple("EvalOutputTuple", ("eval_loss", "predict_beat_count", "batch_size")) 26 | 27 | InferOutputTuple = collections.namedtuple("InferOutputTuple", 28 | ("infer_tgt_output", "infer_tgt_ref", "infer_loss", "predict_beat_count", 29 | "batch_size")) 30 | 31 | class Model(object): 32 | """ 33 | This class implements a multi-layer recurrent neural network as "lstm" 34 | """ 35 | 36 | def __init__(self, hparams, iterator, mode): 37 | # Set params 38 | self.set_params(hparams, iterator, mode) 39 | 40 | # build graph 41 | self.build_graph(hparams) 42 | 43 | def set_params(self, hparams, iterator, mode): 44 | """Set various params for self and initialize.""" 45 | self.architecture = hparams.architecture 46 | assert hparams.src_len == hparams.tgt_len 47 | self.length = hparams.src_len 48 | self.iterator = iterator 49 | self.mode = mode 50 | self.dtype = tf.float64 51 | self.unit_type = hparams.unit_type 52 | self.num_units = hparams.num_units ## this is the size in each cell 53 | self.forget_bias = hparams.forget_bias 54 | self.dropout = hparams.dropout 55 | self.src_feature_size = hparams.src_feature_size 56 | self.tgt_feature_size = hparams.tgt_feature_size 57 | if self.architecture == "deepRNN": 58 | self.num_bi_layers = hparams.num_bi_layers 59 | self.num_uni_layers = hparams.num_uni_layers 60 | 61 | # Set num layers 62 | self.num_layers=hparams.num_layers 63 | # Set num residual layers 64 | self.num_residual_layers = hparams.num_residual_layers 65 | 66 | # Batch size 67 | self.batch_size = hparams.batch_size 68 | self.batch_size_tensor = tf.constant(self.batch_size) 69 | # train directions 70 | self.forward_directions = hparams.forward_directions 71 | self.backward_directions = hparams.backward_directions 72 | 73 | # Global step 74 | self.global_step = tf.Variable(0, trainable=False) 75 | self.epoch_num = tf.Variable(0, trainable=False) 76 | 77 | """--------------------------""" 78 | 79 | def build_graph(self, hparams): 80 | utils.print_out("# Creating %s graph ..." % self.mode) 81 | 82 | with tf.variable_scope("network", dtype=self.dtype, reuse=tf.AUTO_REUSE): 83 | self.top_scope = tf.get_variable_scope() 84 | 85 | # Initializer 86 | initializer = tf.random_uniform_initializer(-hparams.init_weight, hparams.init_weight, 87 | seed=hparams.random_seed) 88 | self.top_scope.set_initializer(initializer) 89 | ## so the initializer will be the default initializer for the following variable in this variable scope 90 | "---------" 91 | # the variable scope specification is left for _encode function 92 | # common components in three mode 93 | if self.architecture == "deepRNN": 94 | # lstm (bi_lstm, then stacked with uni_lstm) 95 | self.src_bi_lstm, self.src_bi_lstm_condition = self._build_bi_lstm(hparams) 96 | self.src_uni_lstm, self.src_uni_lstm_condition = self._build_uni_lstm(hparams) 97 | self.tgt_bi_lstm, self.tgt_bi_lstm_condition = self._build_bi_lstm(hparams) 98 | self.tgt_uni_lstm, self.tgt_uni_lstm_condition = self._build_uni_lstm(hparams) 99 | 100 | # Projector 101 | self.src_projector = self._build_projector(hparams, field='src') 102 | self.tgt_projector = self._build_projector(hparams, field='tgt') 103 | else: 104 | raise ValueError("Unknown architecture_type %s" % hparams.lstm_type) 105 | 106 | "------------" 107 | 108 | # set mode-specific component 109 | self.set_mode_phase(hparams) 110 | 111 | # Saver 112 | self.saver = tf.train.Saver(tf.global_variables(), max_to_keep=5) 113 | 114 | def _single_cell(self, unit_type, num_units, forget_bias, dropout, mode, residual_connection=False): 115 | """Create an instance of a single RNN cell.""" 116 | # dropout (= 1 - keep_prob) is set to 0 during eval and infer 117 | dropout = dropout if mode == tf.contrib.learn.ModeKeys.TRAIN else 0.0 118 | # Cell Type 119 | if unit_type == "lstm": 120 | utils.print_out(" LSTM, forget_bias=%g" % forget_bias, new_line=False) 121 | single_cell = tf.nn.rnn_cell.LSTMCell( 122 | num_units, 123 | forget_bias=forget_bias) 124 | else: 125 | raise ValueError("Unknown unit type %s!" % unit_type) 126 | # Dropout (= 1 - keep_prob) 127 | if dropout > 0.0: 128 | single_cell = tf.nn.rnn_cell.DropoutWrapper( 129 | cell=single_cell, input_keep_prob=(1.0 - dropout)) 130 | utils.print_out(" %s, dropout=%g " % (type(single_cell).__name__, dropout), new_line=False) 131 | # Residual 132 | if residual_connection: 133 | single_cell = tf.nn.rnn_cell.ResidualWrapper(single_cell) 134 | utils.print_out(" %s" % type(single_cell).__name__, new_line=False) 135 | 136 | return single_cell 137 | 138 | def _build_cell(self, num_layers, num_residual_layers): 139 | cell_list = [] 140 | for i in range(num_layers): 141 | utils.print_out(" cell %d " % i, new_line=False) 142 | single_cell = self._single_cell( 143 | unit_type=self.unit_type, 144 | num_units=self.num_units, 145 | forget_bias=self.forget_bias, 146 | dropout=self.dropout, 147 | mode=self.mode, 148 | residual_connection=(i >= (num_layers - num_residual_layers))) 149 | utils.print_out("", new_line=True) 150 | cell_list.append(single_cell) 151 | 152 | if len(cell_list) == 1: # Single layer. 153 | return cell_list[0] 154 | else: # Multi layers 155 | return tf.nn.rnn_cell.MultiRNNCell(cell_list) 156 | 157 | def _build_bi_lstm(self, hparams): 158 | utils.print_out("# Build bidirectional lstm") 159 | num_bi_layers = self.num_bi_layers 160 | num_bi_residual_layers = 0 161 | utils.print_out(" num_bi_layers = %d, num_bi_residual_layers=%d" % (num_bi_layers, num_bi_residual_layers)) 162 | # Construct forward and backward cells 163 | fw_cell = self._build_cell(num_bi_layers, num_bi_residual_layers) 164 | bw_cell = self._build_cell(num_bi_layers, num_bi_residual_layers) 165 | bi_lstm = (fw_cell, bw_cell) 166 | bi_lstm_condition = ("bi", num_bi_layers) 167 | return bi_lstm, bi_lstm_condition 168 | 169 | def _build_uni_lstm(self, hparams): 170 | utils.print_out("# Build unidirectional lstm") 171 | num_uni_layers = self.num_uni_layers 172 | num_uni_residual_layers = self.num_uni_layers - 1 173 | utils.print_out(" num_layers = %d, num_residual_layers=%d" % (num_uni_layers, num_uni_residual_layers)) 174 | cell = self._build_cell(num_uni_layers, num_uni_residual_layers) 175 | uni_lstm = cell 176 | uni_lstm_condition = ("uni", None) 177 | return uni_lstm, uni_lstm_condition 178 | 179 | def _build_projector(self, hparams, field=None): 180 | assert field 181 | if field == "src": 182 | projector = tf.layers.Dense(self.src_feature_size, use_bias=True) 183 | elif field == "tgt": 184 | projector = tf.layers.Dense(self.tgt_feature_size, use_bias=True) 185 | else: 186 | raise ValueError("Unknown field type %s" % field) 187 | return projector 188 | 189 | """----------------------""" 190 | 191 | def _set_input_ref(self, direction, lstm_input_given, ref_given, seq_len_given): 192 | if direction == "src2tgt": 193 | lstm_input = self.iterator.source_ref 194 | ref = self.iterator.target_ref 195 | seq_len = self.iterator.target_sequence_length 196 | return lstm_input, ref, seq_len 197 | 198 | def _set_lstm_projector(self, direction): 199 | # lstm type is related to the input, while the projector used is related to output!!! 200 | if direction in ["src2tgt"]: 201 | bi_lstm, bi_lstm_condition = self.src_bi_lstm, self.src_bi_lstm_condition 202 | uni_lstm, uni_lstm_condition = self.src_uni_lstm, self.src_uni_lstm_condition 203 | lstm_scope = "src_lstm" 204 | 205 | lstm_list = [bi_lstm, uni_lstm] # bi lstm then stacked with uni lstm 206 | lstm_condition_list = [bi_lstm_condition, uni_lstm_condition] 207 | 208 | if direction in ["src2tgt"]: 209 | projector = self.tgt_projector 210 | projector_scope = "tgt_projector" 211 | return lstm_list, lstm_condition_list, lstm_scope, projector, projector_scope 212 | 213 | def _encode(self, lstm_scope, input, seq_len, lstm_list, lstm_condition_list): 214 | # here input is rank-3, [batch size, seq_len, src_feature_size] 215 | assert len(lstm_list) == len(lstm_condition_list) 216 | lstm_outputs = None 217 | with tf.variable_scope(lstm_scope): 218 | for i in range(len(lstm_list)): 219 | if i == 0: 220 | input = input 221 | else: 222 | input = lstm_outputs 223 | lstm = lstm_list[i] 224 | lstm_condition = lstm_condition_list[i] 225 | lstm_type = lstm_condition[0] 226 | if lstm_type == "uni": 227 | lstm_outputs, lstm_state = tf.nn.dynamic_rnn( 228 | cell=lstm, inputs=input, dtype=self.dtype, sequence_length=seq_len, swap_memory=True, scope="") 229 | elif lstm_type == "bi": 230 | fw_cell, bw_cell = lstm 231 | bi_outputs, bi_lstm_state = tf.nn.bidirectional_dynamic_rnn(fw_cell, bw_cell, input, 232 | dtype=self.dtype, 233 | sequence_length=seq_len, 234 | swap_memory=True, scope="") 235 | lstm_outputs = tf.concat(bi_outputs, -1) 236 | num_bi_layers = lstm_condition[1] 237 | if num_bi_layers == 1: 238 | lstm_state = bi_lstm_state 239 | else: 240 | # alternatively concat forward and backward states 241 | lstm_state = [] 242 | for layer_id in range(num_bi_layers): 243 | lstm_state.append(bi_lstm_state[0][layer_id]) # forward 244 | lstm_state.append(bi_lstm_state[1][layer_id]) # backward 245 | lstm_state = tuple(lstm_state) 246 | else: 247 | raise ValueError("Unknown lstm or lstm_condition") 248 | 249 | return lstm_outputs, lstm_state 250 | 251 | def compute_loss(self, hparams, direction, lstm_input_given, ref_given, seq_len_given, feature_size): 252 | 253 | lstm_input, ref, seq_len = self._set_input_ref(direction, lstm_input_given, ref_given, seq_len_given) 254 | lstm_list, lstm_condition_list, lstm_scope, projector, projector_scope = self._set_lstm_projector(direction) 255 | lstm_output, lstm_state = self._encode(lstm_scope, lstm_input, seq_len, lstm_list, lstm_condition_list) 256 | output = projector(lstm_output) 257 | loss = tf.sqrt( 258 | tf.reduce_sum(tf.square(output - ref)) / (tf.to_double(self.batch_size * hparams.src_len * feature_size))) 259 | return loss, output 260 | 261 | def forward(self, hparams, directions=None): 262 | src2tgt_loss = 0 263 | src2tgt_output = None 264 | ## forward directions just include ["src2tgt", "tgt2src", "src2src", "tgt2tgt"] at most 265 | 266 | if "src2tgt" in directions: 267 | src2tgt_loss, src2tgt_output = self.compute_loss(hparams, direction="src2tgt", lstm_input_given=None, 268 | ref_given=None, seq_len_given=None, 269 | forward_loss = src2tgt_loss 270 | return forward_loss, src2tgt_output 271 | 272 | def set_mode_phase(self, hparams): 273 | self.predict_beat_count = tf.math.reduce_sum(self.iterator.target_sequence_length) # need modification 274 | if self.mode == tf.contrib.learn.ModeKeys.TRAIN: 275 | 276 | # set optimizer 277 | self.learning_rate = tf.constant(hparams.learning_rate) 278 | if hparams.optimizer == "adam": 279 | self.optimizer = tf.train.AdamOptimizer(self.learning_rate) 280 | else: 281 | raise ValueError("Unknown optimizer type %s" % hparams.optimizer) 282 | "-------------" 283 | # forward phase 284 | # self.train_forward_loss, self.src2tgt_output, self.tgt2src_output = self.forward( 285 | # hparams, directions=["src2tgt", "tgt2src", "src2src", "tgt2tgt"]) 286 | self.train_forward_loss, self.src2tgt_output, self.tgt2src_output = self.forward( 287 | hparams, directions=self.forward_directions) # this is for test of basic unidirection seq2seq model 288 | 289 | # test variable 290 | utils.test_trainable_variables() 291 | 292 | # Gradients 293 | params = tf.trainable_variables() 294 | forward_gradients = tf.gradients(self.train_forward_loss, params) 295 | forward_clipped_grads, self.forward_grad_norm = tf.clip_by_global_norm(forward_gradients, 5.0) 296 | 297 | # key point: to update the parameter 298 | self.forward_update = self.optimizer.apply_gradients(zip(forward_clipped_grads, params), 299 | global_step=self.global_step) 300 | ## in this step, global step will increase by 1!! 301 | 302 | # Summary 303 | self.forward_train_summary = tf.summary.merge( 304 | [tf.summary.scalar("lr", self.learning_rate), 305 | tf.summary.scalar("forward_train_loss", self.train_forward_loss), 306 | tf.summary.scalar("forward_grad_norm", self.forward_grad_norm), 307 | tf.summary.scalar("forward_clipped_gradient", tf.global_norm(forward_clipped_grads))]) 308 | "---------------" 309 | 310 | # backward phase 311 | self.src_ref_given = tf.placeholder(self.dtype, shape=(self.batch_size, self.length, self.src_feature_size)) 312 | self.tgt_ref_given = tf.placeholder(self.dtype, shape=(self.batch_size, self.length, self.tgt_feature_size)) 313 | self.src2tgt_output_given = tf.placeholder(self.dtype, 314 | shape=(self.batch_size, self.length, self.tgt_feature_size)) 315 | self.tgt2src_output_given = tf.placeholder(self.dtype, 316 | shape=(self.batch_size, self.length, self.src_feature_size)) 317 | """ 318 | self.src_sos_given = tf.placeholder(self.dtype, shape=(1, self.src_feature_size)) 319 | self.tgt_sos_given = tf.placeholder(self.dtype, shape=(1, self.tgt_feature_size)) 320 | """ 321 | self.src_seq_len_given = tf.placeholder(tf.int32, shape=(self.batch_size)) 322 | self.tgt_seq_len_given = tf.placeholder(tf.int32, shape=(self.batch_size)) 323 | 324 | self.train_backward_loss, self.src2tgt2src_output, self.tgt2src2tgt_output = self.backward( 325 | hparams, directions=self.backward_directions, 326 | src2tgt_output=self.src2tgt_output_given, tgt2src_output=self.tgt2src_output_given, 327 | src_ref=self.src_ref_given, tgt_ref=self.tgt_ref_given, 328 | src_seq_len=self.src_seq_len_given, tgt_seq_len=self.tgt_seq_len_given) 329 | 330 | # test variable 331 | utils.test_trainable_variables() 332 | 333 | # Gradients 334 | params = tf.trainable_variables() 335 | backward_gradients = tf.gradients(self.train_backward_loss, params) 336 | backward_clipped_grads, self.backward_grad_norm = tf.clip_by_global_norm(backward_gradients, 5.0) 337 | 338 | # key point: to update the parameter 339 | self.backward_update = self.optimizer.apply_gradients(zip(backward_clipped_grads, params)) 340 | # here we doesn't list the global step to avoid increment by 1 341 | 342 | # Summary 343 | self.backward_train_summary = tf.summary.merge( 344 | [tf.summary.scalar("lr", self.learning_rate), 345 | tf.summary.scalar("backward_train_loss", self.train_backward_loss), 346 | tf.summary.scalar("backward_grad_norm", self.backward_grad_norm), 347 | tf.summary.scalar("backward_clipped_gradient", tf.global_norm(backward_clipped_grads))]) 348 | 349 | elif self.mode == tf.contrib.learn.ModeKeys.EVAL: 350 | self.eval_loss, _, _ = self.forward(hparams, directions=["src2tgt"]) 351 | 352 | elif self.mode == tf.contrib.learn.ModeKeys.INFER: 353 | self.infer_loss, self.infer_target_output, _ = self.forward(hparams, directions=["src2tgt"]) 354 | 355 | """"------------------------""" 356 | 357 | def increase_epoch_num(self, sess): 358 | assert self.mode == tf.contrib.learn.ModeKeys.TRAIN 359 | return sess.run(tf.assign_add(self.epoch_num, 1)) 360 | 361 | def train_forward(self, sess): 362 | assert self.mode == tf.contrib.learn.ModeKeys.TRAIN 363 | output_tuple = TrainForwardOutputTuple(train_summary=self.forward_train_summary, 364 | train_loss=self.train_forward_loss, 365 | predict_beat_count=self.predict_beat_count, 366 | global_step=self.global_step, 367 | batch_size=self.batch_size_tensor, 368 | grad_norm=self.forward_grad_norm, 369 | learning_rate=self.learning_rate, 370 | train_src_ref=self.iterator.source_ref, 371 | train_tgt_ref=self.iterator.target_ref, 372 | train_src2tgt_output=self.src2tgt_output, 373 | src_seq_len=self.iterator.source_sequence_length, 374 | tgt_seq_len=self.iterator.target_sequence_length) 375 | return sess.run([self.forward_update, output_tuple]) 376 | 377 | def train_backward(self, sess, input): 378 | assert self.mode == tf.contrib.learn.ModeKeys.TRAIN 379 | src_ref, tgt_ref, src2tgt_output, tgt2src_output, src_seq_len, tgt_seq_len = input 380 | feed_dict = {self.src_ref_given: src_ref, self.tgt_ref_given: tgt_ref, 381 | self.src2tgt_output_given: src2tgt_output, self.tgt2src_output_given: tgt2src_output, 382 | self.src_seq_len_given: src_seq_len, self.tgt_seq_len_given: tgt_seq_len} 383 | output_tuple = TrainBackwardOutputTuple(train_summary=self.backward_train_summary, 384 | train_backward_loss=self.train_backward_loss, 385 | global_step=self.global_step, 386 | batch_size=self.batch_size_tensor, 387 | grad_norm=self.backward_grad_norm, 388 | learning_rate=self.learning_rate, 389 | train_src_ref=self.src_ref_given, 390 | train_tgt_ref=self.tgt_ref_given) 391 | 392 | return sess.run([self.backward_update, output_tuple], feed_dict=feed_dict) 393 | 394 | def eval(self, sess): 395 | """Execute eval graph.""" 396 | assert self.mode == tf.contrib.learn.ModeKeys.EVAL 397 | output_tuple = EvalOutputTuple(eval_loss=self.eval_loss, 398 | predict_beat_count=self.predict_beat_count, 399 | batch_size=self.batch_size_tensor) 400 | return sess.run(output_tuple) 401 | 402 | def infer(self, sess): 403 | assert self.mode == tf.contrib.learn.ModeKeys.INFER 404 | output_tuple = InferOutputTuple(infer_tgt_output=self.infer_target_output, 405 | infer_tgt_ref=self.iterator.target_ref, 406 | infer_loss=self.infer_loss, 407 | predict_beat_count=self.predict_beat_count, 408 | batch_size=self.batch_size_tensor) 409 | return sess.run(output_tuple) 410 | -------------------------------------------------------------------------------- /code/process.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import collections 4 | import time 5 | import os 6 | 7 | import numpy as np 8 | import tensorflow as tf 9 | import scipy.io as scio 10 | 11 | import model 12 | import utils 13 | 14 | BatchedInput = collections.namedtuple("BatchedInput", 15 | ("initializer", "source_ref", "target_ref", 16 | "source_sequence_length", "target_sequence_length")) 17 | 18 | 19 | def get_iterator(src_dataset, tgt_dataset, batch_size, random_seed, is_train): 20 | output_buffer_size = batch_size * 1000 21 | src_tgt_dataset = tf.data.Dataset.zip((src_dataset, tgt_dataset)) 22 | # just try not to use random seed 23 | if is_train: 24 | src_tgt_dataset = src_tgt_dataset.shuffle(output_buffer_size,# random_seed, 25 | reshuffle_each_iteration=True) 26 | # Filter zero length input sequences. 27 | src_tgt_dataset = src_tgt_dataset.filter( 28 | lambda src_ref, tgt_ref: tf.logical_and(tf.size(src_ref) > 0, tf.size(tgt_ref) > 0)) 29 | 30 | # Add in sequence lengths. 31 | src_tgt_dataset = src_tgt_dataset.map( 32 | lambda src_ref, tgt_ref: (src_ref, tgt_ref, tf.shape(src_ref)[0], tf.shape(tgt_ref)[0])) 33 | 34 | batched_dataset = src_tgt_dataset.batch(batch_size) 35 | 36 | batched_iter = batched_dataset.make_initializable_iterator() 37 | (src_ref, tgt_ref, src_seq_len, tgt_seq_len) = (batched_iter.get_next()) 38 | return BatchedInput( 39 | initializer=batched_iter.initializer, 40 | source_ref=src_ref, ## this should be rank-3 41 | target_ref=tgt_ref, ## this should be rank-3 42 | source_sequence_length=src_seq_len, ## this should be rank-1 43 | target_sequence_length=tgt_seq_len) ## this should be rank-1 44 | 45 | 46 | def get_model_creator(hparams): 47 | """Get the right model class depending on configuration.""" 48 | if hparams.architecture == 'peng': 49 | model_creator = model.Model 50 | """vanilla lstm, seq2seq""" 51 | return model_creator 52 | 53 | 54 | def load_data_mean_std(hparams, data_dir): 55 | if hparams.normalize_principle == "all": 56 | data_mean = scio.loadmat(os.path.join(data_dir, 'all_data_mean'))['data'] 57 | data_std = scio.loadmat(os.path.join(data_dir, 'all_data_std'))['data'] 58 | elif hparams.normalize_principle == "train": 59 | data_mean = scio.loadmat(os.path.join(data_dir, 'train_data_mean'))['data'] 60 | data_std = scio.loadmat(os.path.join(data_dir, 'train_data_std'))['data'] 61 | return data_mean, data_std 62 | 63 | 64 | TrainModel = collections.namedtuple("TrainModel", ("graph", "model", "iterator")) 65 | 66 | 67 | def create_train_model(model_creator, hparams, data_dir): 68 | """Create train graph, model, and iterator.""" 69 | train_data_path = [] 70 | for root, _, name in os.walk(os.path.join(data_dir, 'train_data')): 71 | for x in name: 72 | if x.split('.')[-1] == 'mat': 73 | train_data_path.append(os.path.join(root, x)) 74 | assert len(train_data_path) == 1 75 | train_data = scio.loadmat(*train_data_path)['data'] 76 | assert hparams.src_len == hparams.tgt_len == train_data.shape[1] 77 | graph = tf.Graph() 78 | 79 | with graph.as_default(), tf.container("train"): 80 | # channels: [features, SBP, DBP, MBP] 81 | train_src_data = train_data[:, :, 0:hparams.src_feature_size] 82 | train_tgt_data = train_data[:, :, hparams.src_feature_size:hparams.src_feature_size + hparams.tgt_feature_size] 83 | src_dataset = tf.data.Dataset.from_tensor_slices(train_src_data) 84 | tgt_dataset = tf.data.Dataset.from_tensor_slices(train_tgt_data) 85 | iterator = get_iterator(src_dataset, tgt_dataset, batch_size=hparams.batch_size, 86 | random_seed=hparams.random_seed, is_train=True) 87 | model = model_creator(hparams, iterator=iterator, mode=tf.contrib.learn.ModeKeys.TRAIN) 88 | return TrainModel(graph=graph, model=model, iterator=iterator) 89 | 90 | 91 | EvalModel = collections.namedtuple("EvalModel", ("graph", "model", "iterator", "data_mean", "data_std")) 92 | 93 | 94 | def create_eval_model(model_creator, hparams, data_dir): 95 | """Create eval graph, model and iterator.""" 96 | eval_data_path = [] 97 | for root, _, name in os.walk(os.path.join(data_dir, 'eval_data')): 98 | for x in name: 99 | if x.split('.')[-1] == 'mat': 100 | eval_data_path.append(os.path.join(root, x)) 101 | assert len(eval_data_path) == 1 102 | eval_data = scio.loadmat(*eval_data_path)['data'] 103 | data_mean, data_std = load_data_mean_std(hparams, data_dir) 104 | batch_size = eval_data.shape[0] 105 | graph = tf.Graph() 106 | 107 | with graph.as_default(), tf.container("eval"): 108 | eval_src_data = eval_data[:, :, 0:hparams.src_feature_size] 109 | # channels: [features, SBP, DBP, MBP] 110 | eval_tgt_data = eval_data[:, :, hparams.src_feature_size:hparams.src_feature_size + hparams.tgt_feature_size] 111 | src_dataset = tf.data.Dataset.from_tensor_slices(eval_src_data) 112 | tgt_dataset = tf.data.Dataset.from_tensor_slices(eval_tgt_data) 113 | iterator = get_iterator(src_dataset, tgt_dataset, batch_size=batch_size, 114 | random_seed=hparams.random_seed, is_train=False) 115 | model = model_creator(hparams, iterator=iterator, mode=tf.contrib.learn.ModeKeys.EVAL) 116 | return EvalModel(graph=graph, model=model, iterator=iterator, data_mean=data_mean, data_std=data_std) 117 | 118 | 119 | InferModel = collections.namedtuple("InferModel", ("graph", "model", "iterator")) 120 | 121 | 122 | def create_infer_model(model_creator, hparams, infer_data, batch_size): 123 | """Create inference model.""" 124 | graph = tf.Graph() 125 | 126 | with graph.as_default(), tf.container("infer"): 127 | infer_src_data = infer_data[:, :, 0:hparams.src_feature_size] 128 | # channels:[features, SBP, SBP, MBP] 129 | infer_tgt_data = infer_data[:, :, hparams.src_feature_size:hparams.src_feature_size + hparams.tgt_feature_size] 130 | src_dataset = tf.data.Dataset.from_tensor_slices(infer_src_data) 131 | tgt_dataset = tf.data.Dataset.from_tensor_slices(infer_tgt_data) 132 | iterator = get_iterator(src_dataset, tgt_dataset, batch_size=batch_size, 133 | random_seed=hparams.random_seed, is_train=False) 134 | model = model_creator(hparams, iterator=iterator, mode=tf.contrib.learn.ModeKeys.INFER) 135 | return InferModel(graph=graph, model=model, iterator=iterator) 136 | 137 | 138 | def load_model(model, ckpt_path, session, name): 139 | """Load model from a checkpoint.""" 140 | try: 141 | model.saver.restore(session, ckpt_path) 142 | except tf.errors.NotFoundError as e: 143 | utils.print_out("Can't load checkpoint") 144 | utils.print_out("%s" % str(e)) 145 | # session.run(tf.tables_initializer()) ## why table still need to be initialized even model loaded?? 146 | utils.print_out(" loaded %s model parameters from %s" % (name, ckpt_path)) 147 | return model 148 | 149 | 150 | def create_or_load_model(model, model_dir, session, name): 151 | """Create translation model and initialize or load parameters in session.""" 152 | latest_ckpt = tf.train.latest_checkpoint(model_dir) 153 | if latest_ckpt: 154 | model._replace(model=load_model(model.model, latest_ckpt, session, name)) 155 | utils.print_out("checkpoint found, load checkpoint\n %s" % latest_ckpt) 156 | else: 157 | utils.print_out(" checkpoint not found in %s" % (model_dir)) 158 | utils.print_out(" created %s model with fresh parameters" % (name)) 159 | session.run(tf.global_variables_initializer()) 160 | # session.run(tf.tables_initializer()) 161 | global_step = model.model.global_step.eval(session=session) 162 | epoch_num = model.model.epoch_num.eval(session=session) 163 | return model, global_step, epoch_num 164 | 165 | 166 | def train_eval(hparams, data_dir, model_dir, log_dir): 167 | # Log and output files 168 | log_file = os.path.join(log_dir, "log_%d" % time.time()) 169 | log_f = tf.gfile.GFile(log_file, mode="a") 170 | utils.print_out("# log_file=%s" % log_file, log_f) 171 | 172 | # create model and session 173 | model_creator = get_model_creator(hparams) # model_creator is just a factory function 174 | train_model = create_train_model(model_creator, hparams, data_dir) 175 | eval_model = create_eval_model(model_creator, hparams, data_dir) 176 | config_proto = tf.ConfigProto(log_device_placement=False, allow_soft_placement=True) 177 | config_proto.gpu_options.allow_growth = True 178 | train_sess = tf.Session(config=config_proto, graph=train_model.graph) 179 | eval_sess = tf.Session(config=config_proto, graph=eval_model.graph) 180 | 181 | # Summary writer 182 | summary_writer = tf.summary.FileWriter(os.path.join(log_dir, "train_log"), train_model.graph) 183 | 184 | with train_model.graph.as_default(): 185 | train_model, global_step, epoch_num = create_or_load_model(train_model, model_dir, train_sess, "train") 186 | 187 | # first time train 188 | # start_train_time = time.time() 189 | utils.print_out("# Start step %d, %s" % (global_step, time.ctime()), log_f) 190 | epoch_step = 0 # just to count the current step in this epoch 191 | stats = {"step_time": 0.0, "train_loss": 0.0, 192 | "predict_beat_count": 0.0, # number of beat count 193 | "sequence_count": 0.0, # number of training examples processed 194 | } 195 | info = {} 196 | # Initialize iterators 197 | train_sess.run(train_model.iterator.initializer) 198 | # This is the training loop. 199 | while global_step < hparams.num_train_steps: 200 | ### Run a step ### 201 | start_time = time.time() 202 | try: 203 | # forward train phase 204 | forward_result = train_model.model.train_forward(train_sess)[1] 205 | step_result = forward_result 206 | src_ref, tgt_ref, src2tgt_output, tgt2src_output, src_seq_len, tgt_seq_len = ( 207 | forward_result.train_src_ref, forward_result.train_tgt_ref, 208 | forward_result.train_src2tgt_output, forward_result.train_tgt2src_output, 209 | forward_result.src_seq_len, forward_result.tgt_seq_len) 210 | # backward train phase 211 | if hparams.backward_enable: 212 | backward_input = [src_ref, tgt_ref, src2tgt_output, tgt2src_output, src_seq_len, tgt_seq_len] 213 | backward_result = train_model.model.train_backward(train_sess, backward_input)[1] 214 | epoch_step += 1 215 | except tf.errors.OutOfRangeError: 216 | # Finished going through the training dataset. Go to next epoch. 217 | epoch_step = 0 218 | epoch_num = train_model.model.increase_epoch_num(train_sess) 219 | utils.print_out("# Finished an epoch, global step %d. Perform evaluation" % global_step) 220 | # save model for evaluation 221 | train_model.model.saver.save(train_sess, os.path.join(model_dir, "model.ckpt"), global_step=global_step) 222 | eval(eval_model, eval_sess, model_dir, hparams, summary_writer, log_f) 223 | train_sess.run(train_model.iterator.initializer) 224 | continue 225 | 226 | # Process step_result, accumulate stats, and write summary 227 | global_step = step_result.global_step 228 | summary_writer.add_summary(step_result.train_summary, global_step) 229 | # Update statistics 230 | stats["step_time"] += time.time() - start_time 231 | stats["train_loss"] += step_result.train_loss * step_result.predict_beat_count 232 | stats["predict_beat_count"] += step_result.predict_beat_count 233 | stats["sequence_count"] += step_result.batch_size 234 | if global_step % hparams.steps_per_stats == 0: 235 | info["avg_step_time"] = stats["step_time"] / hparams.steps_per_stats 236 | # the time to take for process one batch 237 | info["sample_per_second"] = stats["sequence_count"] / (stats["step_time"]) 238 | info["avg_beat_loss"] = (stats["train_loss"] / stats["predict_beat_count"]) 239 | info["epoch_num"] = epoch_num 240 | utils.print_out( 241 | "global step: %d, epoch_num: %d, avg step time: %.2fs, sample per second: %.2f, avg beat loss: %.2f, time;%s" % 242 | ( 243 | global_step, info['epoch_num'], info["avg_step_time"], info["sample_per_second"], 244 | info["avg_beat_loss"], 245 | time.ctime()), log_f) 246 | for key in info: 247 | summary_writer.add_summary(tf.Summary(value=[tf.Summary.Value(tag=key, simple_value=info[key])]), 248 | global_step) 249 | 250 | stats = {"step_time": 0.0, "train_loss": 0.0, 251 | "predict_beat_count": 0.0, # word count on the target side 252 | "sequence_count": 0.0, # number of training examples processed 253 | } 254 | 255 | # Done training in one epoch 256 | train_model.model.saver.save( 257 | train_sess, 258 | os.path.join(model_dir, "model.ckpt"), 259 | global_step=global_step) 260 | 261 | # ...result summary 262 | 263 | summary_writer.close() 264 | 265 | 266 | def eval(eval_model, eval_sess, model_dir, hparams, summary_writer, log_f): 267 | with eval_model.graph.as_default(): 268 | eval_model, global_step, epoch_num = create_or_load_model(eval_model, model_dir, eval_sess, "eval") 269 | eval_sess.run(eval_model.model.iterator.initializer) 270 | eval_info = {} 271 | total_loss = 0 272 | total_predict_beat_count = 0 273 | total_sequence_count = 0 274 | while True: 275 | try: 276 | step_result = eval_model.model.eval(eval_sess) 277 | total_loss += step_result.eval_loss * step_result.predict_beat_count 278 | total_predict_beat_count += step_result.predict_beat_count 279 | total_sequence_count += step_result.batch_size 280 | except tf.errors.OutOfRangeError: 281 | eval_info['epoch_num'] = epoch_num 282 | eval_info['eval_avg_beat_loss'] = total_loss / total_predict_beat_count 283 | eval_info['eval_predict_beat_count'] = total_predict_beat_count 284 | eval_info['eval_sample_num'] = total_sequence_count 285 | utils.print_out( 286 | "\neval: global step: %d, epoch_num: %d, eval_avg beat loss: %.2f, eval_predict_beat_count: %d, eval_sample_num: %d, time;%s\n" % 287 | (global_step, eval_info['epoch_num'], eval_info["eval_avg_beat_loss"], 288 | eval_info['eval_predict_beat_count'], eval_info['eval_sample_num'], time.ctime()), log_f) 289 | for key in eval_info: 290 | summary_writer.add_summary( 291 | tf.Summary(value=[tf.Summary.Value(tag=key, simple_value=eval_info[key])]), 292 | global_step) 293 | break 294 | 295 | 296 | def infer(hparams, data_dir, model_dir, result_dir): 297 | # infer mode is different from train and eval mode that we prepare data in advanced 298 | # becasue we may have various test dataset, but only one dataset in the case of train and eval 299 | # prepare data 300 | infer_data_path = [] 301 | for root, _, name in os.walk(os.path.join(data_dir, 'test_data')): 302 | for x in name: 303 | if x.split('.')[-1] == 'mat': 304 | infer_data_path.append(os.path.join(root, x)) 305 | 306 | for j in range(len(infer_data_path)): 307 | data_name = os.path.basename(infer_data_path[j]).split('.')[0] 308 | infer_data = scio.loadmat(infer_data_path[j])['data'] 309 | data_mean, data_std = load_data_mean_std(hparams, data_dir) 310 | batch_size = infer_data.shape[0] 311 | 312 | # Create model 313 | model_creator = get_model_creator(hparams) 314 | infer_model = create_infer_model(model_creator, hparams, infer_data, batch_size) 315 | # TensorFlow model 316 | config_proto = tf.ConfigProto(log_device_placement=False, allow_soft_placement=True) 317 | config_proto.gpu_options.allow_growth = True 318 | infer_sess = tf.Session(config=config_proto, graph=infer_model.graph) 319 | with infer_model.graph.as_default(): 320 | infer_model, global_step, epoch_num = create_or_load_model( 321 | infer_model, model_dir, infer_sess, "infer") 322 | infer_sess.run(infer_model.model.iterator.initializer) 323 | # because we have already set batch size to be the size of all sample, so we dont use loop but just calculate the metrics 324 | # otherwise, we can implement similarly as eval mode 325 | 326 | step_result = infer_model.model.infer(infer_sess) 327 | infer_avg_beat_loss = step_result.infer_loss 328 | total_predict_beat_count = step_result.predict_beat_count 329 | total_sequence_count = step_result.batch_size 330 | infer_tgt_output = step_result.infer_tgt_output 331 | # scale back to the original size 332 | # (both the infered one and the input one needed to be scaled back, because they are both normalized) 333 | infer_real_tgt_feature = np.zeros(infer_tgt_output.shape) 334 | for i in range(hparams.tgt_feature_size): 335 | infer_real_tgt_feature[:, :, i] = ( 336 | infer_tgt_output[:, :, i] * data_std[:, hparams.src_feature_size + i] 337 | + data_mean[:, hparams.src_feature_size + i]) 338 | 339 | origin_tgt_data = infer_data[:, :, 340 | hparams.src_feature_size: hparams.src_feature_size + hparams.tgt_feature_size] 341 | origin_real_tgt_data = np.zeros(origin_tgt_data.shape) 342 | for i in range(hparams.tgt_feature_size): 343 | origin_real_tgt_data[:, :, i] = ( 344 | origin_tgt_data[:, :, i] * data_std[:, hparams.src_feature_size + i] 345 | + data_mean[:, hparams.src_feature_size + i]) 346 | 347 | # infer_info['epoch_num'] = epoch_num 348 | infer_info = {} 349 | infer_info['infer_avg_beat_loss'] = infer_avg_beat_loss 350 | infer_info['infer_predict_beat_count'] = total_predict_beat_count 351 | infer_info['infer_sample_num'] = total_sequence_count 352 | utils.print_out( 353 | "\ninfer: global step: %d, infer_avg beat loss: %.2f, infer_predict_beat_count: %d, infer_sample_num: %d, time;%s\n" % 354 | (global_step, infer_info["infer_avg_beat_loss"], infer_info['infer_predict_beat_count'], 355 | infer_info['infer_sample_num'], time.ctime())) 356 | """ 357 | for key in eval_info: 358 | summary_writer.add_summary( 359 | tf.Summary(value=[tf.Summary.Value(tag=key, simple_value=eval_info[key])]), 360 | global_step) 361 | """ 362 | 363 | # save_result() 364 | step_result_dir = os.path.join(result_dir, 'step-' + str(global_step)) 365 | if not tf.gfile.Exists(step_result_dir): 366 | tf.gfile.MakeDirs(step_result_dir) 367 | scio.savemat(os.path.join(step_result_dir, data_name + '_real_origin.mat'), {'data': origin_real_tgt_data}) 368 | utils.print_out("save origin data to %s" % os.path.join(step_result_dir, data_name + '_real_origin.mat')) 369 | scio.savemat(os.path.join(step_result_dir, data_name + '_real_prediction.mat'), 370 | {'data': infer_real_tgt_feature}) 371 | utils.print_out( 372 | "save prediction data to %s" % os.path.join(step_result_dir, data_name + '_real_prediction.mat')) 373 | 374 | # performance metrics calculation 375 | # RMSE: root mean square error 376 | real_test_loss_SBP_RMSE = np.sqrt( 377 | np.mean(np.square(origin_real_tgt_data[:, :, 0] - infer_real_tgt_feature[:, :, 0]))) 378 | real_test_loss_DBP_RMSE = np.sqrt( 379 | np.mean(np.square(origin_real_tgt_data[:, :, 1] - infer_real_tgt_feature[:, :, 1]))) 380 | real_test_loss_MBP_RMSE = np.sqrt( 381 | np.mean(np.square(origin_real_tgt_data[:, :, 2] - infer_real_tgt_feature[:, :, 2]))) 382 | # MAD: mean absolute difference 383 | real_test_loss_SBP_MAD = np.mean( 384 | np.absolute(origin_real_tgt_data[:, :, 0] - infer_real_tgt_feature[:, :, 0])) 385 | real_test_loss_DBP_MAD = np.mean( 386 | np.absolute(origin_real_tgt_data[:, :, 1] - infer_real_tgt_feature[:, :, 1])) 387 | real_test_loss_MBP_MAD = np.mean( 388 | np.absolute(origin_real_tgt_data[:, :, 2] - infer_real_tgt_feature[:, :, 2])) 389 | # MD: mean difference (absolute is not used!) 390 | real_test_loss_SBP_MD = np.mean(origin_real_tgt_data[:, :, 0] - infer_real_tgt_feature[:, :, 0]) 391 | real_test_loss_DBP_MD = np.mean(origin_real_tgt_data[:, :, 1] - infer_real_tgt_feature[:, :, 1]) 392 | real_test_loss_MBP_MD = np.mean(origin_real_tgt_data[:, :, 2] - infer_real_tgt_feature[:, :, 2]) 393 | # SD1: the standard deviation of delta 394 | real_test_loss_SBP_SD1 = np.sqrt( 395 | np.mean(np.square( 396 | (origin_real_tgt_data[:, :, 0] - infer_real_tgt_feature[:, :, 0]) - real_test_loss_SBP_MD))) 397 | real_test_loss_DBP_SD1 = np.sqrt( 398 | np.mean(np.square( 399 | (origin_real_tgt_data[:, :, 1] - infer_real_tgt_feature[:, :, 1]) - real_test_loss_DBP_MD))) 400 | real_test_loss_MBP_SD1 = np.sqrt( 401 | np.mean(np.square( 402 | (origin_real_tgt_data[:, :, 2] - infer_real_tgt_feature[:, :, 2]) - real_test_loss_MBP_MD))) 403 | # SD2: the standard deviation of predicted value itself 404 | real_test_loss_SBP_SD2 = np.std(infer_real_tgt_feature[:, :, 0]) 405 | real_test_loss_DBP_SD2 = np.std(infer_real_tgt_feature[:, :, 1]) 406 | real_test_loss_MBP_SD2 = np.std(infer_real_tgt_feature[:, :, 2]) 407 | name_collection = ['real_test_loss_SBP_RMSE', 'real_test_loss_DBP_RMSE', 'real_test_loss_MBP_RMSE', 408 | 'real_test_loss_SBP_MAD', 'real_test_loss_DBP_MAD', 'real_test_loss_MBP_MAD', 409 | 'real_test_loss_SBP_MD', 'real_test_loss_DBP_MD', 'real_test_loss_MBP_MD', 410 | 'real_test_loss_SBP_SD1', 'real_test_loss_DBP_SD1', 'real_test_loss_MBP_SD1', 411 | 'real_test_loss_SBP_SD2', 'real_test_loss_DBP_SD2', 'real_test_loss_MBP_SD2'] 412 | metric_collection = [real_test_loss_SBP_RMSE, real_test_loss_DBP_RMSE, real_test_loss_MBP_RMSE, 413 | real_test_loss_SBP_MAD, real_test_loss_DBP_MAD, real_test_loss_MBP_MAD, 414 | real_test_loss_SBP_MD, real_test_loss_DBP_MD, real_test_loss_MBP_MD, 415 | real_test_loss_SBP_SD1, real_test_loss_DBP_SD1, real_test_loss_MBP_SD1, 416 | real_test_loss_SBP_SD2, real_test_loss_DBP_SD2, real_test_loss_MBP_SD2] 417 | 418 | with open(os.path.join(step_result_dir, 'step_' + str(global_step) + '_performance_metrics.txt'), 419 | 'a') as result_fo: 420 | for i in range(len(name_collection)): 421 | if i % 3 == 0: 422 | print('\n') 423 | result_fo.write('\n') 424 | print(data_name + '_' + name_collection[i], ':', metric_collection[i]) 425 | result_fo.write('%s_%s:%f\n' % (data_name, name_collection[i], metric_collection[i])) 426 | -------------------------------------------------------------------------------- /code/readme.md: -------------------------------------------------------------------------------- 1 | ## Main training file 2 | train.py 3 | 4 | ## Model definition 5 | model.py 6 | -------------------------------------------------------------------------------- /code/train.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import argparse 4 | import os 5 | import codecs 6 | import json 7 | 8 | import tensorflow as tf 9 | 10 | import utils 11 | import process 12 | import preprocess 13 | 14 | 15 | def add_arguments(parser): 16 | """Build ArgumentParser.""" 17 | parser.register("type", "bool", lambda v: v.lower() == "true") 18 | """ 19 | # for ordinary lstm 20 | parser.add_argument("--num_layers", type=int, default=4, help="number of layers") 21 | parser.add_argument("--num_residual_layers", type=int, default=2, 22 | help="number of residual layers of encoder, should be not greater than the number of encoder layers.") 23 | parser.add_argument("--lstm_type", type=str, default="uni", help="\ 24 | uni | bi | gnmt. 25 | For bi, we build num_encoder_layers/2 bi-directional layers. 26 | For gnmt, we build 1 bi-directional layer, and (num_encoder_layers - 1) 27 | uni-directional layers.\ 28 | ") 29 | """ 30 | 31 | # network 32 | parser.add_argument("--architecture", type=str, default="deepRNN", help="""deepRNN""") 33 | ## layer(for peng's model) 34 | parser.add_argument("--num_bi_layers", type=int, default=1, 35 | help="number of bidirectional layers, 1 layer acually represent 2 directions") 36 | parser.add_argument("--num_uni_layers", type=int, default=3, 37 | help="number of uni layers stacked on the bi layers, " 38 | "with uni_residual_layer=uni_layers-1 setting as default .") 39 | ## cell 40 | parser.add_argument("--unit_type", type=str, default="lstm", help="lstm") 41 | parser.add_argument("--num_units", type=int, default=128, help="the hidden size of each cell.") 42 | parser.add_argument("--forget_bias", type=float, default=1.0, 43 | help="Forget bias for BasicLSTMCell.") 44 | parser.add_argument("--dropout", type=float, default=0, 45 | help="Dropout rate (not keep_prob)") 46 | ## initializer (uniform initializer as default) 47 | parser.add_argument("--init_weight", type=float, default=0.1, 48 | help=("for uniform init_op, initialize weights " 49 | "between [-this, this].")) 50 | ## optimizer 51 | parser.add_argument("--optimizer", type=str, default="adam", help="the type of optimizer") 52 | parser.add_argument("--learning_rate", type=float, default=1e-3, 53 | help="Learning rate. Adam: 0.001 | 0.0001") 54 | 55 | # data 56 | ## data source 57 | parser.add_argument('--data_source_dir', type=str, 58 | default='/path/to/data/', 59 | help='the path to load training data') 60 | 61 | ## data detail 62 | parser.add_argument("--src_len", type=int, default=64, 63 | help="length of src sequences during training. " 64 | "the input are all in same length") 65 | parser.add_argument("--tgt_len", type=int, default=64, 66 | help="length of tgt sequences during training.") 67 | parser.add_argument("--src_feature_size", type=int, default=7, 68 | help="feature size of src") 69 | parser.add_argument("--tgt_feature_size", type=int, default=3, 70 | help="feature size of tgt") 71 | 72 | # expriment 73 | parser.add_argument("--exprs_dir", type=str, 74 | default='/path/to/save/experiments', 75 | help="the dir to store experiment.") 76 | parser.add_argument("--expr_name", type=str, 77 | default='deepRNN', 78 | help='the name of dir to store config, model, data, figure, result') 79 | parser.add_argument('--train_ratio', default=0.7, type=float, 80 | help='the ratio of data for trainning') 81 | parser.add_argument('--eval_ratio', default=0.1, type=float, 82 | help='the ratio of data for validation') 83 | parser.add_argument("--batch_size", type=int, default=32, help="Batch size.") 84 | parser.add_argument("--random_seed", type=int, default=1234, 85 | help="Random seed (>0, set a specific seed).") 86 | parser.add_argument("--num_train_steps", type=int, default=100000, help="Num steps to train.") 87 | parser.add_argument('--backward_enable', default=False, type="bool", 88 | help="if enable the backward training") 89 | parser.add_argument('--forward_directions', default=["src2tgt"], type=list, 90 | help="the directions for forward phase of traning" 91 | " can only be 'src2tgt'") 92 | parser.add_argument("--steps_per_stats", type=int, default=1, 93 | help=("How many training steps to do per stats logging." 94 | "Save checkpoint every epoch")) 95 | 96 | 97 | def create_dirs(hparams): 98 | """create directories""" 99 | assert hparams.exprs_dir 100 | exprs_dir = hparams.exprs_dir 101 | if not tf.gfile.Exists(exprs_dir): 102 | tf.gfile.MakeDirs(exprs_dir) 103 | utils.print_out("# experiments directory: %s " % exprs_dir) 104 | expr_dir = os.path.join(exprs_dir, hparams.expr_name) 105 | if not tf.gfile.Exists(expr_dir): 106 | tf.gfile.MakeDirs(expr_dir) 107 | utils.print_out("# -experiment directory: %s " % expr_dir) 108 | config_dir = os.path.join(expr_dir, 'config') 109 | if not tf.gfile.Exists(config_dir): 110 | tf.gfile.MakeDirs(config_dir) 111 | utils.print_out("# --config directory: %s " % config_dir) 112 | log_dir = os.path.join(expr_dir, 'log') 113 | if not tf.gfile.Exists(log_dir): 114 | tf.gfile.MakeDirs(log_dir) 115 | utils.print_out("# --log directory: %s " % log_dir) 116 | data_dir = os.path.join(expr_dir, 'data') 117 | if not tf.gfile.Exists(data_dir): 118 | tf.gfile.MakeDirs(data_dir) 119 | utils.print_out("# --data directory: %s " % data_dir) 120 | model_dir = os.path.join(expr_dir, 'model') 121 | if not tf.gfile.Exists(model_dir): 122 | tf.gfile.MakeDirs(model_dir) 123 | utils.print_out("# --model directory: %s " % model_dir) 124 | figure_dir = os.path.join(expr_dir, 'figure') 125 | if not tf.gfile.Exists(figure_dir): 126 | tf.gfile.MakeDirs(figure_dir) 127 | utils.print_out("# --figure directory: %s " % figure_dir) 128 | result_dir = os.path.join(expr_dir, 'result') 129 | if not tf.gfile.Exists(result_dir): 130 | tf.gfile.MakeDirs(result_dir) 131 | utils.print_out("# --result directory: %s " % result_dir) 132 | return expr_dir, config_dir, log_dir, data_dir, model_dir, figure_dir, result_dir 133 | 134 | 135 | def check_and_save_hparams(out_dir, hparams): 136 | """Save hparams.""" 137 | hparams_file = os.path.join(out_dir, "hparams") 138 | if tf.gfile.Exists(hparams_file): 139 | with codecs.getreader("utf-8")(tf.gfile.GFile(hparams_file, "rb")) as f: 140 | origin_hparams = json.load(f) 141 | origin_hparams = tf.contrib.training.HParams(**origin_hparams) 142 | wrong_keys = [] 143 | keys = set(list(hparams.values().keys()) + (list(origin_hparams.values().keys()))) 144 | for key in keys: 145 | if (hparams.values().get(key, None) != origin_hparams.values().get(key, None) or 146 | hparams.values().get(key, None) == None or 147 | hparams.values().get(key, None) == None): 148 | wrong_keys.append(key) 149 | try: 150 | assert origin_hparams.values() == hparams.values() 151 | utils.print_out("using the same hparams of old %s" % hparams_file) 152 | except: 153 | utils.print_out('new hparams not the same with the existed one') 154 | for wrong_key in wrong_keys: 155 | utils.print_out(" keys: %s, \norigin_value: %s, \nnew_value: %s\n" % ( 156 | wrong_key, origin_hparams.values()[wrong_key], hparams.values()[wrong_key])) 157 | raise ValueError 158 | else: 159 | utils.print_out(" not old hparams found, create new hparams to %s" % hparams_file) 160 | with codecs.getwriter("utf-8")(tf.gfile.GFile(hparams_file, "wb")) as f: 161 | f.write(hparams.to_json(indent=4)) 162 | 163 | 164 | def main(): 165 | parser = argparse.ArgumentParser(description='Deep BiLSTM with Residual') 166 | add_arguments(parser) 167 | args = parser.parse_args() 168 | print(args) 169 | hparams = tf.contrib.training.HParams(**vars(args)) 170 | # check GPU device 171 | utils.print_out("# Devices visible to TensorFlow: %s" % repr(tf.Session().list_devices())) 172 | # create dirs 173 | expr_dir, config_dir, log_dir, data_dir, model_dir, figure_dir, result_dir = create_dirs(hparams) 174 | # save hyperameter 175 | check_and_save_hparams(config_dir, hparams) 176 | 177 | stage = 'test' # preprocess','train_eval', or 'test' 178 | assert stage in [, 'train_eval', 'test'], 'stage not recognized' 179 | utils.print_out('stage: %s' % stage) 180 | # if stage == 'preprocess': 181 | # preprocess.preprocess(hparams, data_dir) 182 | # the data are stored in the data_dir for the training step 183 | if stage == 'train_eval': 184 | process.train_eval(hparams, data_dir, model_dir, log_dir) 185 | if stage == 'test': 186 | process.infer(hparams, data_dir, model_dir, result_dir) 187 | 188 | 189 | if __name__ == '__main__': 190 | main() 191 | -------------------------------------------------------------------------------- /code/utils.py: -------------------------------------------------------------------------------- 1 | """Generally useful utility functions.""" 2 | from __future__ import print_function 3 | 4 | import sys 5 | 6 | import tensorflow as tf 7 | 8 | 9 | def print_out(s, f=None, new_line=True): 10 | """Similar to print but with support to flush and output to a file.""" 11 | if isinstance(s, bytes): 12 | s = s.decode("utf-8") 13 | 14 | if f: 15 | f.write(s.encode("utf-8")) 16 | if new_line: 17 | f.write(b"\n") 18 | 19 | # stdout 20 | out_s = s.encode("utf-8") 21 | if not isinstance(out_s, str): 22 | out_s = out_s.decode("utf-8") 23 | print(out_s, end="", file=sys.stdout) 24 | 25 | if new_line: 26 | sys.stdout.write("\n") 27 | sys.stdout.flush() 28 | 29 | def test_global_variables(): 30 | params = tf.global_variables() 31 | print_out("# global variables") 32 | print_out("Format: , , <(soft) device placement>") 33 | for param in params: 34 | print_out(" %s, %s, %s" % (param.name, str(param.get_shape()), param.op.device)) 35 | 36 | def test_trainable_variables(): 37 | params = tf.trainable_variables() 38 | print_out("# trainable variables") 39 | print_out("Format: , , <(soft) device placement>") 40 | for param in params: 41 | print_out(" %s, %s, %s" % (param.name, str(param.get_shape()), param.op.device)) -------------------------------------------------------------------------------- /data/readme.md: -------------------------------------------------------------------------------- 1 | 2 | The data was provided by another institution. Due to the IP issue, we cannot share it now. 3 | But we highly recommended [MMIC](https://mimic.physionet.org/) dataset for validation use. 4 | -------------------------------------------------------------------------------- /deepRnn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psu1/DeepRNN/65ac0c84f5ca85d498e3028902265e48845024e0/deepRnn.png -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psu1/DeepRNN/65ac0c84f5ca85d498e3028902265e48845024e0/demo.png -------------------------------------------------------------------------------- /plotResult.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psu1/DeepRNN/65ac0c84f5ca85d498e3028902265e48845024e0/plotResult.png -------------------------------------------------------------------------------- /table1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psu1/DeepRNN/65ac0c84f5ca85d498e3028902265e48845024e0/table1.png --------------------------------------------------------------------------------