import numpy as np from collections import namedtuple import json import tensorflow as tf # hyperparameters for our model. I was using an older tf version, when HParams was not available ... # controls whether we concatenate (z, c, h), etc for features used for car. MODE_ZCH = 0 MODE_ZC = 1 MODE_Z = 2 MODE_Z_HIDDEN = 3 # extra hidden later MODE_ZH = 4 HyperParams = namedtuple('HyperParams', ['num_steps', 'max_seq_len', 'input_seq_width', 'output_seq_width', 'rnn_size', 'batch_size', 'grad_clip', 'num_mixture', 'learning_rate', 'decay_rate', 'min_learning_rate', 'use_layer_norm', 'use_recurrent_dropout', 'recurrent_dropout_prob', 'use_input_dropout', 'input_dropout_prob', 'use_output_dropout', 'output_dropout_prob', 'is_training', ]) def default_hps(): return HyperParams(num_steps=2000, # train model for 2000 steps. max_seq_len=1000, # train on sequences of 100 input_seq_width=35, # width of our data (32 + 3 actions) output_seq_width=32, # width of our data is 32 rnn_size=256, # number of rnn cells batch_size=100, # minibatch sizes grad_clip=1.0, num_mixture=5, # number of mixtures in MDN learning_rate=0.001, decay_rate=1.0, min_learning_rate=0.00001, use_layer_norm=0, # set this to 1 to get more stable results (less chance of NaN), but slower use_recurrent_dropout=0, recurrent_dropout_prob=0.90, use_input_dropout=0, input_dropout_prob=0.90, use_output_dropout=0, output_dropout_prob=0.90, is_training=1) hps_model = default_hps() hps_sample = hps_model._replace(batch_size=1, max_seq_len=1, use_recurrent_dropout=0, is_training=0) # MDN-RNN model class MDNRNN(): def __init__(self, hps, gpu_mode=True, reuse=False): self.hps = hps with tf.variable_scope('mdn_rnn', reuse=reuse): if not gpu_mode: with tf.device("/cpu:0"): print("model using cpu") self.g = tf.Graph() with self.g.as_default(): self.build_model(hps) else: print("model using gpu") self.g = tf.Graph() with self.g.as_default(): self.build_model(hps) self.init_session() def build_model(self, hps): self.num_mixture = hps.num_mixture KMIX = self.num_mixture # 5 mixtures INWIDTH = hps.input_seq_width # 35 channels OUTWIDTH = hps.output_seq_width # 32 channels LENGTH = self.hps.max_seq_len # 1000 timesteps if hps.is_training: self.global_step = tf.Variable(0, name='global_step', trainable=False) cell_fn = tf.contrib.rnn.LayerNormBasicLSTMCell # use LayerNormLSTM use_recurrent_dropout = False if self.hps.use_recurrent_dropout == 0 else True use_input_dropout = False if self.hps.use_input_dropout == 0 else True use_output_dropout = False if self.hps.use_output_dropout == 0 else True is_training = False if self.hps.is_training == 0 else True use_layer_norm = False if self.hps.use_layer_norm == 0 else True if use_recurrent_dropout: cell = cell_fn(hps.rnn_size, layer_norm=use_layer_norm, dropout_keep_prob=self.hps.recurrent_dropout_prob) else: cell = cell_fn(hps.rnn_size, layer_norm=use_layer_norm) # multi-layer, and dropout: print("input dropout mode =", use_input_dropout) print("output dropout mode =", use_output_dropout) print("recurrent dropout mode =", use_recurrent_dropout) if use_input_dropout: print("applying dropout to input with keep_prob =", self.hps.input_dropout_prob) cell = tf.contrib.rnn.DropoutWrapper(cell, input_keep_prob=self.hps.input_dropout_prob) if use_output_dropout: print("applying dropout to output with keep_prob =", self.hps.output_dropout_prob) cell = tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob=self.hps.output_dropout_prob) self.cell = cell self.sequence_lengths = LENGTH # assume every sample has same length. self.input_x = tf.placeholder(dtype=tf.float32, shape=[self.hps.batch_size, self.hps.max_seq_len, INWIDTH]) self.output_x = tf.placeholder(dtype=tf.float32, shape=[self.hps.batch_size, self.hps.max_seq_len, OUTWIDTH]) actual_input_x = self.input_x self.initial_state = cell.zero_state(batch_size=hps.batch_size, dtype=tf.float32) NOUT = OUTWIDTH * KMIX * 3 with tf.variable_scope('RNN'): output_w = tf.get_variable("output_w", [self.hps.rnn_size, NOUT]) output_b = tf.get_variable("output_b", [NOUT]) output, last_state = tf.nn.dynamic_rnn(cell, actual_input_x, initial_state=self.initial_state, time_major=False, swap_memory=True, dtype=tf.float32, scope="RNN") output = tf.reshape(output, [-1, hps.rnn_size]) output = tf.nn.xw_plus_b(output, output_w, output_b) output = tf.reshape(output, [-1, KMIX * 3]) self.final_state = last_state logSqrtTwoPI = np.log(np.sqrt(2.0 * np.pi)) def tf_lognormal(y, mean, logstd): return -0.5 * ((y - mean) / tf.exp(logstd)) ** 2 - logstd - logSqrtTwoPI def get_lossfunc(logmix, mean, logstd, y): v = logmix + tf_lognormal(y, mean, logstd) v = tf.reduce_logsumexp(v, 1, keepdims=True) return -tf.reduce_mean(v) def get_mdn_coef(output): logmix, mean, logstd = tf.split(output, 3, 1) logmix = logmix - tf.reduce_logsumexp(logmix, 1, keepdims=True) return logmix, mean, logstd out_logmix, out_mean, out_logstd = get_mdn_coef(output) self.out_logmix = out_logmix self.out_mean = out_mean self.out_logstd = out_logstd # reshape target data so that it is compatible with prediction shape flat_target_data = tf.reshape(self.output_x,[-1, 1]) lossfunc = get_lossfunc(out_logmix, out_mean, out_logstd, flat_target_data) self.cost = tf.reduce_mean(lossfunc) if self.hps.is_training == 1: self.lr = tf.Variable(self.hps.learning_rate, trainable=False) optimizer = tf.train.AdamOptimizer(self.lr) gvs = optimizer.compute_gradients(self.cost) capped_gvs = [(tf.clip_by_value(grad, -self.hps.grad_clip, self.hps.grad_clip), var) for grad, var in gvs] self.train_op = optimizer.apply_gradients(capped_gvs, global_step=self.global_step, name='train_step') # initialize vars self.init = tf.global_variables_initializer() t_vars = tf.trainable_variables() self.assign_ops = {} for var in t_vars: #if var.name.startswith('mdn_rnn'): pshape = var.get_shape() pl = tf.placeholder(tf.float32, pshape, var.name[:-2]+'_placeholder') assign_op = var.assign(pl) self.assign_ops[var] = (assign_op, pl) def init_session(self): """Launch TensorFlow session and initialize variables""" self.sess = tf.Session(graph=self.g) self.sess.run(self.init) def close_sess(self): """ Close TensorFlow session """ self.sess.close() def get_model_params(self): # get trainable params. model_names = [] model_params = [] model_shapes = [] with self.g.as_default(): t_vars = tf.trainable_variables() for var in t_vars: #if var.name.startswith('mdn_rnn'): param_name = var.name p = self.sess.run(var) model_names.append(param_name) params = np.round(p*10000).astype(np.int).tolist() model_params.append(params) model_shapes.append(p.shape) return model_params, model_shapes, model_names def get_random_model_params(self, stdev=0.5): # get random params. _, mshape, _ = self.get_model_params() rparam = [] for s in mshape: #rparam.append(np.random.randn(*s)*stdev) rparam.append(np.random.standard_cauchy(s)*stdev) # spice things up return rparam def set_random_params(self, stdev=0.5): rparam = self.get_random_model_params(stdev) self.set_model_params(rparam) def set_model_params(self, params): with self.g.as_default(): t_vars = tf.trainable_variables() idx = 0 for var in t_vars: #if var.name.startswith('mdn_rnn'): pshape = tuple(var.get_shape().as_list()) p = np.array(params[idx]) assert pshape == p.shape, "inconsistent shape" assign_op, pl = self.assign_ops[var] self.sess.run(assign_op, feed_dict={pl.name: p/10000.}) idx += 1 def load_json(self, jsonfile='rnn.json'): with open(jsonfile, 'r') as f: params = json.load(f) self.set_model_params(params) def save_json(self, jsonfile='rnn.json'): model_params, model_shapes, model_names = self.get_model_params() qparams = [] for p in model_params: qparams.append(p) with open(jsonfile, 'wt') as outfile: json.dump(qparams, outfile, sort_keys=True, indent=0, separators=(',', ': ')) def get_pi_idx(x, pdf): # samples from a categorial distribution N = pdf.size accumulate = 0 for i in range(0, N): accumulate += pdf[i] if (accumulate >= x): return i print('error with sampling ensemble') return -1 def sample_sequence(sess, s_model, hps, init_z, actions, temperature=1.0, seq_len=1000): # generates a random sequence using the trained model OUTWIDTH = hps.output_seq_width INWIDTH = hps.input_seq_width prev_x = np.zeros((1, 1, OUTWIDTH)) prev_x[0][0] = init_z prev_state = sess.run(s_model.initial_state) ''' if prev_data is not None: # encode the previous data into the hidden state first for i in range(prev_data.shape[0]): prev_x[0][0] = prev_data[i] feed = {s_model.input_x: prev_x, s_model.initial_state:prev_state} [next_state] = sess.run([s_model.final_state], feed) prev_state = next_state ''' strokes = np.zeros((seq_len, OUTWIDTH), dtype=np.float32) for i in range(seq_len): input_x = np.concatenate((prev_x, actions[i].reshape((1, 1, 3))), axis=2) feed = {s_model.input_x: input_x, s_model.initial_state:prev_state} [logmix, mean, logstd, next_state] = sess.run([s_model.out_logmix, s_model.out_mean, s_model.out_logstd, s_model.final_state], feed) # adjust temperatures logmix2 = np.copy(logmix)/temperature logmix2 -= logmix2.max() logmix2 = np.exp(logmix2) logmix2 /= logmix2.sum(axis=1).reshape(OUTWIDTH, 1) mixture_idx = np.zeros(OUTWIDTH) chosen_mean = np.zeros(OUTWIDTH) chosen_logstd = np.zeros(OUTWIDTH) for j in range(OUTWIDTH): idx = get_pi_idx(np.random.rand(), logmix2[j]) mixture_idx[j] = idx chosen_mean[j] = mean[j][idx] chosen_logstd[j] = logstd[j][idx] rand_gaussian = np.random.randn(OUTWIDTH)*np.sqrt(temperature) next_x = chosen_mean+np.exp(chosen_logstd)*rand_gaussian strokes[i,:] = next_x prev_x[0][0] = next_x prev_state = next_state return strokes def rnn_init_state(rnn): return rnn.sess.run(rnn.initial_state) def rnn_next_state(rnn, z, a, prev_state): input_x = np.concatenate((z.reshape((1, 1, 32)), a.reshape((1, 1, 3))), axis=2) feed = {rnn.input_x: input_x, rnn.initial_state:prev_state} return rnn.sess.run(rnn.final_state, feed) def rnn_output_size(mode): if mode == MODE_ZCH: return (32+256+256) if (mode == MODE_ZC) or (mode == MODE_ZH): return (32+256) return 32 # MODE_Z or MODE_Z_HIDDEN def rnn_output(state, z, mode): if mode == MODE_ZCH: return np.concatenate([z, np.concatenate((state.c,state.h), axis=1)[0]]) if mode == MODE_ZC: return np.concatenate([z, state.c[0]]) if mode == MODE_ZH: return np.concatenate([z, state.h[0]]) return z # MODE_Z or MODE_Z_HIDDEN