mirror of
https://github.com/wassname/Run-Skeleton-Run.git
synced 2026-09-09 11:15:53 +08:00
pytorch version
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
from baselines.baselines_common.console_util import *
|
||||
from baselines.baselines_common.dataset import Dataset
|
||||
from baselines.baselines_common.math_util import *
|
||||
from baselines.baselines_common.misc_util import *
|
||||
@@ -0,0 +1,38 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def cg(f_Ax, b, cg_iters=10, callback=None, verbose=False, residual_tol=1e-10):
|
||||
"""
|
||||
Demmel p 312
|
||||
"""
|
||||
p = b.copy()
|
||||
r = b.copy()
|
||||
x = np.zeros_like(b)
|
||||
rdotr = r.dot(r)
|
||||
|
||||
fmtstr = "%10i %10.3g %10.3g"
|
||||
titlestr = "%10s %10s %10s"
|
||||
if verbose:
|
||||
print(titlestr % ("iter", "residual norm", "soln norm"))
|
||||
|
||||
for i in range(cg_iters):
|
||||
if callback is not None:
|
||||
callback(x)
|
||||
if verbose: print(fmtstr % (i, rdotr, np.linalg.norm(x)))
|
||||
z = f_Ax(p)
|
||||
v = rdotr / p.dot(z)
|
||||
x += v * p
|
||||
r -= v * z
|
||||
newrdotr = r.dot(r)
|
||||
mu = newrdotr / rdotr
|
||||
p = r + mu * p
|
||||
|
||||
rdotr = newrdotr
|
||||
if rdotr < residual_tol:
|
||||
break
|
||||
|
||||
if callback is not None:
|
||||
callback(x)
|
||||
if verbose:
|
||||
print(fmtstr % (i + 1, rdotr, np.linalg.norm(x))) # pylint: disable=W0631
|
||||
return x
|
||||
@@ -0,0 +1,62 @@
|
||||
from __future__ import print_function
|
||||
from contextlib import contextmanager
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Misc
|
||||
# ================================================================
|
||||
|
||||
def fmt_row(width, row, header=False):
|
||||
out = " | ".join(fmt_item(x, width) for x in row)
|
||||
if header: out = out + "\n" + "-" * len(out)
|
||||
return out
|
||||
|
||||
|
||||
def fmt_item(x, l):
|
||||
if isinstance(x, np.ndarray):
|
||||
assert x.ndim == 0
|
||||
x = x.item()
|
||||
if isinstance(x, float):
|
||||
rep = "%g" % x
|
||||
else:
|
||||
rep = str(x)
|
||||
return " " * (l - len(rep)) + rep
|
||||
|
||||
|
||||
color2num = dict(
|
||||
gray=30,
|
||||
red=31,
|
||||
green=32,
|
||||
yellow=33,
|
||||
blue=34,
|
||||
magenta=35,
|
||||
cyan=36,
|
||||
white=37,
|
||||
crimson=38
|
||||
)
|
||||
|
||||
|
||||
def colorize(string, color, bold=False, highlight=False):
|
||||
attr = []
|
||||
num = color2num[color]
|
||||
if highlight: num += 10
|
||||
attr.append(str(num))
|
||||
if bold: attr.append('1')
|
||||
return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string)
|
||||
|
||||
|
||||
MESSAGE_DEPTH = 0
|
||||
|
||||
|
||||
@contextmanager
|
||||
def timed(msg):
|
||||
global MESSAGE_DEPTH # pylint: disable=W0603
|
||||
print(colorize('\t' * MESSAGE_DEPTH + '=: ' + msg, color='magenta'))
|
||||
tstart = time.time()
|
||||
MESSAGE_DEPTH += 1
|
||||
yield
|
||||
MESSAGE_DEPTH -= 1
|
||||
print(colorize('\t' * MESSAGE_DEPTH + "done in %.3f seconds" % (time.time() - tstart),
|
||||
color='magenta'))
|
||||
@@ -0,0 +1,63 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Dataset(object):
|
||||
def __init__(self, data_map, deterministic=False, shuffle=True):
|
||||
self.data_map = data_map
|
||||
self.deterministic = deterministic
|
||||
self.enable_shuffle = shuffle
|
||||
self.n = next(iter(data_map.values())).shape[0]
|
||||
self._next_id = 0
|
||||
self.shuffle()
|
||||
|
||||
def shuffle(self):
|
||||
if self.deterministic:
|
||||
return
|
||||
perm = np.arange(self.n)
|
||||
np.random.shuffle(perm)
|
||||
|
||||
for key in self.data_map:
|
||||
self.data_map[key] = self.data_map[key][perm]
|
||||
|
||||
self._next_id = 0
|
||||
|
||||
def next_batch(self, batch_size):
|
||||
if self._next_id >= self.n and self.enable_shuffle:
|
||||
self.shuffle()
|
||||
|
||||
cur_id = self._next_id
|
||||
cur_batch_size = min(batch_size, self.n - self._next_id)
|
||||
self._next_id += cur_batch_size
|
||||
|
||||
data_map = dict()
|
||||
for key in self.data_map:
|
||||
data_map[key] = self.data_map[key][cur_id:cur_id + cur_batch_size]
|
||||
return data_map
|
||||
|
||||
def iterate_once(self, batch_size):
|
||||
if self.enable_shuffle: self.shuffle()
|
||||
|
||||
while self._next_id <= self.n - batch_size:
|
||||
yield self.next_batch(batch_size)
|
||||
self._next_id = 0
|
||||
|
||||
def subset(self, num_elements, deterministic=True):
|
||||
data_map = dict()
|
||||
for key in self.data_map:
|
||||
data_map[key] = self.data_map[key][:num_elements]
|
||||
return Dataset(data_map, deterministic)
|
||||
|
||||
|
||||
def iterbatches(arrays, *, num_batches=None, batch_size=None, shuffle=True,
|
||||
include_final_partial_batch=True):
|
||||
assert (num_batches is None) != (
|
||||
batch_size is None), 'Provide num_batches or batch_size, but not both'
|
||||
arrays = tuple(map(np.asarray, arrays))
|
||||
n = arrays[0].shape[0]
|
||||
assert all(a.shape[0] == n for a in arrays[1:])
|
||||
inds = np.arange(n)
|
||||
if shuffle: np.random.shuffle(inds)
|
||||
sections = np.arange(0, n, batch_size)[1:] if num_batches is None else num_batches
|
||||
for batch_inds in np.array_split(inds, sections):
|
||||
if include_final_partial_batch or len(batch_inds) == batch_size:
|
||||
yield tuple(a[batch_inds] for a in arrays)
|
||||
@@ -0,0 +1,377 @@
|
||||
import tensorflow as tf
|
||||
import numpy as np
|
||||
import baselines.baselines_common.tf_util as U
|
||||
from tensorflow.python.ops import math_ops
|
||||
from tensorflow.python.ops import nn
|
||||
|
||||
|
||||
class Pd(object):
|
||||
"""
|
||||
A particular probability distribution
|
||||
"""
|
||||
|
||||
def flatparam(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def mode(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def neglogp(self, x):
|
||||
# Usually it's easier to define the negative logprob
|
||||
raise NotImplementedError
|
||||
|
||||
def kl(self, other):
|
||||
raise NotImplementedError
|
||||
|
||||
def entropy(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def sample(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def logp(self, x):
|
||||
return - self.neglogp(x)
|
||||
|
||||
|
||||
class PdType(object):
|
||||
"""
|
||||
Parametrized family of probability distributions
|
||||
"""
|
||||
|
||||
def pdclass(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def pdfromflat(self, flat):
|
||||
return self.pdclass()(flat)
|
||||
|
||||
def param_shape(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def sample_shape(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def sample_dtype(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def param_placeholder(self, prepend_shape, name=None):
|
||||
return tf.placeholder(dtype=tf.float32, shape=prepend_shape + self.param_shape(), name=name)
|
||||
|
||||
def sample_placeholder(self, prepend_shape, name=None):
|
||||
return tf.placeholder(dtype=self.sample_dtype(), shape=prepend_shape + self.sample_shape(),
|
||||
name=name)
|
||||
|
||||
|
||||
class CategoricalPdType(PdType):
|
||||
def __init__(self, ncat):
|
||||
self.ncat = ncat
|
||||
|
||||
def pdclass(self):
|
||||
return CategoricalPd
|
||||
|
||||
def param_shape(self):
|
||||
return [self.ncat]
|
||||
|
||||
def sample_shape(self):
|
||||
return []
|
||||
|
||||
def sample_dtype(self):
|
||||
return tf.int32
|
||||
|
||||
|
||||
class MultiCategoricalPdType(PdType):
|
||||
def __init__(self, low, high):
|
||||
self.low = low
|
||||
self.high = high
|
||||
self.ncats = high - low + 1
|
||||
|
||||
def pdclass(self):
|
||||
return MultiCategoricalPd
|
||||
|
||||
def pdfromflat(self, flat):
|
||||
return MultiCategoricalPd(self.low, self.high, flat)
|
||||
|
||||
def param_shape(self):
|
||||
return [sum(self.ncats)]
|
||||
|
||||
def sample_shape(self):
|
||||
return [len(self.ncats)]
|
||||
|
||||
def sample_dtype(self):
|
||||
return tf.int32
|
||||
|
||||
|
||||
class DiagGaussianPdType(PdType):
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
|
||||
def pdclass(self):
|
||||
return DiagGaussianPd
|
||||
|
||||
def param_shape(self):
|
||||
return [2 * self.size]
|
||||
|
||||
def sample_shape(self):
|
||||
return [self.size]
|
||||
|
||||
def sample_dtype(self):
|
||||
return tf.float32
|
||||
|
||||
|
||||
class BernoulliPdType(PdType):
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
|
||||
def pdclass(self):
|
||||
return BernoulliPd
|
||||
|
||||
def param_shape(self):
|
||||
return [self.size]
|
||||
|
||||
def sample_shape(self):
|
||||
return [self.size]
|
||||
|
||||
def sample_dtype(self):
|
||||
return tf.int32
|
||||
|
||||
|
||||
# WRONG SECOND DERIVATIVES
|
||||
# class CategoricalPd(Pd):
|
||||
# def __init__(self, logits):
|
||||
# self.logits = logits
|
||||
# self.ps = tf.nn.softmax(logits)
|
||||
# @classmethod
|
||||
# def fromflat(cls, flat):
|
||||
# return cls(flat)
|
||||
# def flatparam(self):
|
||||
# return self.logits
|
||||
# def mode(self):
|
||||
# return U.argmax(self.logits, axis=-1)
|
||||
# def logp(self, x):
|
||||
# return -tf.nn.sparse_softmax_cross_entropy_with_logits(self.logits, x)
|
||||
# def kl(self, other):
|
||||
# return tf.nn.softmax_cross_entropy_with_logits(other.logits, self.ps) \
|
||||
# - tf.nn.softmax_cross_entropy_with_logits(self.logits, self.ps)
|
||||
# def entropy(self):
|
||||
# return tf.nn.softmax_cross_entropy_with_logits(self.logits, self.ps)
|
||||
# def sample(self):
|
||||
# u = tf.random_uniform(tf.shape(self.logits))
|
||||
# return U.argmax(self.logits - tf.log(-tf.log(u)), axis=-1)
|
||||
|
||||
class CategoricalPd(Pd):
|
||||
def __init__(self, logits):
|
||||
self.logits = logits
|
||||
|
||||
def flatparam(self):
|
||||
return self.logits
|
||||
|
||||
def mode(self):
|
||||
return U.argmax(self.logits, axis=-1)
|
||||
|
||||
def neglogp(self, x):
|
||||
# return tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.logits, labels=x)
|
||||
# Note: we can't use sparse_softmax_cross_entropy_with_logits because
|
||||
# the implementation does not allow second-order derivatives...
|
||||
one_hot_actions = tf.one_hot(x, self.logits.get_shape().as_list()[-1])
|
||||
return tf.nn.softmax_cross_entropy_with_logits(
|
||||
logits=self.logits,
|
||||
labels=one_hot_actions)
|
||||
|
||||
def kl(self, other):
|
||||
a0 = self.logits - U.max(self.logits, axis=-1, keepdims=True)
|
||||
a1 = other.logits - U.max(other.logits, axis=-1, keepdims=True)
|
||||
ea0 = tf.exp(a0)
|
||||
ea1 = tf.exp(a1)
|
||||
z0 = U.sum(ea0, axis=-1, keepdims=True)
|
||||
z1 = U.sum(ea1, axis=-1, keepdims=True)
|
||||
p0 = ea0 / z0
|
||||
return U.sum(p0 * (a0 - tf.log(z0) - a1 + tf.log(z1)), axis=-1)
|
||||
|
||||
def entropy(self):
|
||||
a0 = self.logits - U.max(self.logits, axis=-1, keepdims=True)
|
||||
ea0 = tf.exp(a0)
|
||||
z0 = U.sum(ea0, axis=-1, keepdims=True)
|
||||
p0 = ea0 / z0
|
||||
return U.sum(p0 * (tf.log(z0) - a0), axis=-1)
|
||||
|
||||
def sample(self):
|
||||
u = tf.random_uniform(tf.shape(self.logits))
|
||||
return tf.argmax(self.logits - tf.log(-tf.log(u)), axis=-1)
|
||||
|
||||
@classmethod
|
||||
def fromflat(cls, flat):
|
||||
return cls(flat)
|
||||
|
||||
|
||||
class MultiCategoricalPd(Pd):
|
||||
def __init__(self, low, high, flat):
|
||||
self.flat = flat
|
||||
self.low = tf.constant(low, dtype=tf.int32)
|
||||
self.categoricals = list(
|
||||
map(CategoricalPd, tf.split(flat, high - low + 1, axis=len(flat.get_shape()) - 1)))
|
||||
|
||||
def flatparam(self):
|
||||
return self.flat
|
||||
|
||||
def mode(self):
|
||||
return self.low + tf.cast(tf.stack([p.mode() for p in self.categoricals], axis=-1),
|
||||
tf.int32)
|
||||
|
||||
def neglogp(self, x):
|
||||
return tf.add_n([p.neglogp(px) for p, px in zip(
|
||||
self.categoricals, tf.unstack(x - self.low,
|
||||
axis=len(x.get_shape()) - 1))])
|
||||
|
||||
def kl(self, other):
|
||||
return tf.add_n([
|
||||
p.kl(q) for p, q in zip(self.categoricals, other.categoricals)
|
||||
])
|
||||
|
||||
def entropy(self):
|
||||
return tf.add_n([p.entropy() for p in self.categoricals])
|
||||
|
||||
def sample(self):
|
||||
return self.low + tf.cast(tf.stack([p.sample() for p in self.categoricals], axis=-1),
|
||||
tf.int32)
|
||||
|
||||
@classmethod
|
||||
def fromflat(cls, flat):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DiagGaussianPd(Pd):
|
||||
def __init__(self, flat):
|
||||
self.flat = flat
|
||||
mean, logstd = tf.split(axis=len(flat.shape) - 1, num_or_size_splits=2, value=flat)
|
||||
self.mean = mean
|
||||
self.logstd = logstd
|
||||
self.std = tf.exp(logstd)
|
||||
|
||||
def flatparam(self):
|
||||
return self.flat
|
||||
|
||||
def mode(self):
|
||||
return self.mean
|
||||
|
||||
def neglogp(self, x):
|
||||
return 0.5 * U.sum(tf.square((x - self.mean) / self.std), axis=-1) \
|
||||
+ 0.5 * np.log(2.0 * np.pi) * tf.to_float(tf.shape(x)[-1]) \
|
||||
+ U.sum(self.logstd, axis=-1)
|
||||
|
||||
def kl(self, other):
|
||||
assert isinstance(other, DiagGaussianPd)
|
||||
return U.sum(other.logstd - self.logstd + (
|
||||
tf.square(self.std) + tf.square(self.mean - other.mean)) / (
|
||||
2.0 * tf.square(other.std)) - 0.5, axis=-1)
|
||||
|
||||
def entropy(self):
|
||||
return U.sum(self.logstd + .5 * np.log(2.0 * np.pi * np.e), axis=-1)
|
||||
|
||||
def sample(self):
|
||||
return self.mean + self.std * tf.random_normal(tf.shape(self.mean))
|
||||
|
||||
@classmethod
|
||||
def fromflat(cls, flat):
|
||||
return cls(flat)
|
||||
|
||||
|
||||
class BernoulliPd(Pd):
|
||||
def __init__(self, logits):
|
||||
self.logits = logits
|
||||
self.ps = tf.sigmoid(logits)
|
||||
|
||||
def flatparam(self):
|
||||
return self.logits
|
||||
|
||||
def mode(self):
|
||||
return tf.round(self.ps)
|
||||
|
||||
def neglogp(self, x):
|
||||
return U.sum(
|
||||
tf.nn.sigmoid_cross_entropy_with_logits(logits=self.logits, labels=tf.to_float(x)),
|
||||
axis=-1)
|
||||
|
||||
def kl(self, other):
|
||||
return U.sum(tf.nn.sigmoid_cross_entropy_with_logits(logits=other.logits, labels=self.ps),
|
||||
axis=-1) - U.sum(
|
||||
tf.nn.sigmoid_cross_entropy_with_logits(logits=self.logits, labels=self.ps), axis=-1)
|
||||
|
||||
def entropy(self):
|
||||
return U.sum(tf.nn.sigmoid_cross_entropy_with_logits(logits=self.logits, labels=self.ps),
|
||||
axis=-1)
|
||||
|
||||
def sample(self):
|
||||
u = tf.random_uniform(tf.shape(self.ps))
|
||||
return tf.to_float(math_ops.less(u, self.ps))
|
||||
|
||||
@classmethod
|
||||
def fromflat(cls, flat):
|
||||
return cls(flat)
|
||||
|
||||
|
||||
def make_pdtype(ac_space):
|
||||
from gym import spaces
|
||||
if isinstance(ac_space, spaces.Box):
|
||||
assert len(ac_space.shape) == 1
|
||||
return DiagGaussianPdType(ac_space.shape[0])
|
||||
elif isinstance(ac_space, spaces.Discrete):
|
||||
return CategoricalPdType(ac_space.n)
|
||||
elif isinstance(ac_space, spaces.MultiDiscrete):
|
||||
return MultiCategoricalPdType(ac_space.low, ac_space.high)
|
||||
elif isinstance(ac_space, spaces.MultiBinary):
|
||||
return BernoulliPdType(ac_space.n)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def shape_el(v, i):
|
||||
maybe = v.get_shape()[i]
|
||||
if maybe is not None:
|
||||
return maybe
|
||||
else:
|
||||
return tf.shape(v)[i]
|
||||
|
||||
|
||||
@U.in_session
|
||||
def test_probtypes():
|
||||
np.random.seed(0)
|
||||
|
||||
pdparam_diag_gauss = np.array([-.2, .3, .4, -.5, .1, -.5, .1, 0.8])
|
||||
diag_gauss = DiagGaussianPdType(pdparam_diag_gauss.size // 2) # pylint: disable=E1101
|
||||
validate_probtype(diag_gauss, pdparam_diag_gauss)
|
||||
|
||||
pdparam_categorical = np.array([-.2, .3, .5])
|
||||
categorical = CategoricalPdType(pdparam_categorical.size) # pylint: disable=E1101
|
||||
validate_probtype(categorical, pdparam_categorical)
|
||||
|
||||
pdparam_bernoulli = np.array([-.2, .3, .5])
|
||||
bernoulli = BernoulliPdType(pdparam_bernoulli.size) # pylint: disable=E1101
|
||||
validate_probtype(bernoulli, pdparam_bernoulli)
|
||||
|
||||
|
||||
def validate_probtype(probtype, pdparam):
|
||||
N = 100000
|
||||
# Check to see if mean negative log likelihood == differential entropy
|
||||
Mval = np.repeat(pdparam[None, :], N, axis=0)
|
||||
M = probtype.param_placeholder([N])
|
||||
X = probtype.sample_placeholder([N])
|
||||
pd = probtype.pdclass()(M)
|
||||
calcloglik = U.function([X, M], pd.logp(X))
|
||||
calcent = U.function([M], pd.entropy())
|
||||
Xval = U.eval(pd.sample(), feed_dict={M: Mval})
|
||||
logliks = calcloglik(Xval, Mval)
|
||||
entval_ll = - logliks.mean() # pylint: disable=E1101
|
||||
entval_ll_stderr = logliks.std() / np.sqrt(N) # pylint: disable=E1101
|
||||
entval = calcent(Mval).mean() # pylint: disable=E1101
|
||||
assert np.abs(entval - entval_ll) < 3 * entval_ll_stderr # within 3 sigmas
|
||||
|
||||
# Check to see if kldiv[p,q] = - ent[p] - E_p[log q]
|
||||
M2 = probtype.param_placeholder([N])
|
||||
pd2 = probtype.pdclass()(M2)
|
||||
q = pdparam + np.random.randn(pdparam.size) * 0.1
|
||||
Mval2 = np.repeat(q[None, :], N, axis=0)
|
||||
calckl = U.function([M, M2], pd.kl(pd2))
|
||||
klval = calckl(Mval, Mval2).mean() # pylint: disable=E1101
|
||||
logliks = calcloglik(Xval, Mval2)
|
||||
klval_ll = - entval - logliks.mean() # pylint: disable=E1101
|
||||
klval_ll_stderr = logliks.std() / np.sqrt(N) # pylint: disable=E1101
|
||||
assert np.abs(klval - klval_ll) < 3 * klval_ll_stderr # within 3 sigmas
|
||||
@@ -0,0 +1,92 @@
|
||||
import numpy as np
|
||||
import scipy.signal
|
||||
|
||||
|
||||
def discount(x, gamma):
|
||||
"""
|
||||
computes discounted sums along 0th dimension of x.
|
||||
|
||||
inputs
|
||||
------
|
||||
x: ndarray
|
||||
gamma: float
|
||||
|
||||
outputs
|
||||
-------
|
||||
y: ndarray with same shape as x, satisfying
|
||||
|
||||
y[t] = x[t] + gamma*x[t+1] + gamma^2*x[t+2] + ... + gamma^k x[t+k],
|
||||
where k = len(x) - t - 1
|
||||
|
||||
"""
|
||||
assert x.ndim >= 1
|
||||
return scipy.signal.lfilter([1], [1, -gamma], x[::-1], axis=0)[::-1]
|
||||
|
||||
|
||||
def explained_variance(ypred, y):
|
||||
"""
|
||||
Computes fraction of variance that ypred explains about y.
|
||||
Returns 1 - Var[y-ypred] / Var[y]
|
||||
|
||||
interpretation:
|
||||
ev=0 => might as well have predicted zero
|
||||
ev=1 => perfect prediction
|
||||
ev<0 => worse than just predicting zero
|
||||
|
||||
"""
|
||||
assert y.ndim == 1 and ypred.ndim == 1
|
||||
vary = np.var(y)
|
||||
return np.nan if vary == 0 else 1 - np.var(y - ypred) / vary
|
||||
|
||||
|
||||
def explained_variance_2d(ypred, y):
|
||||
assert y.ndim == 2 and ypred.ndim == 2
|
||||
vary = np.var(y, axis=0)
|
||||
out = 1 - np.var(y - ypred) / vary
|
||||
out[vary < 1e-10] = 0
|
||||
return out
|
||||
|
||||
|
||||
def ncc(ypred, y):
|
||||
return np.corrcoef(ypred, y)[1, 0]
|
||||
|
||||
|
||||
def flatten_arrays(arrs):
|
||||
return np.concatenate([arr.flat for arr in arrs])
|
||||
|
||||
|
||||
def unflatten_vector(vec, shapes):
|
||||
i = 0
|
||||
arrs = []
|
||||
for shape in shapes:
|
||||
size = np.prod(shape)
|
||||
arr = vec[i:i + size].reshape(shape)
|
||||
arrs.append(arr)
|
||||
i += size
|
||||
return arrs
|
||||
|
||||
|
||||
def discount_with_boundaries(X, New, gamma):
|
||||
"""
|
||||
X: 2d array of floats, time x features
|
||||
New: 2d array of bools, indicating when a new episode has started
|
||||
"""
|
||||
Y = np.zeros_like(X)
|
||||
T = X.shape[0]
|
||||
Y[T - 1] = X[T - 1]
|
||||
for t in range(T - 2, -1, -1):
|
||||
Y[t] = X[t] + gamma * Y[t + 1] * (1 - New[t + 1])
|
||||
return Y
|
||||
|
||||
|
||||
def test_discount_with_boundaries():
|
||||
gamma = 0.9
|
||||
x = np.array([1.0, 2.0, 3.0, 4.0], 'float32')
|
||||
starts = [1.0, 0.0, 0.0, 1.0]
|
||||
y = discount_with_boundaries(x, starts, gamma)
|
||||
assert np.allclose(y, [
|
||||
1 + gamma * 2 + gamma ** 2 * 3,
|
||||
2 + gamma * 3,
|
||||
3,
|
||||
4
|
||||
])
|
||||
@@ -0,0 +1,328 @@
|
||||
import gym
|
||||
import numpy as np
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
|
||||
def zipsame(*seqs):
|
||||
L = len(seqs[0])
|
||||
assert all(len(seq) == L for seq in seqs[1:])
|
||||
return zip(*seqs)
|
||||
|
||||
|
||||
def unpack(seq, sizes):
|
||||
"""
|
||||
Unpack 'seq' into a sequence of lists, with lengths specified by 'sizes'.
|
||||
None = just one bare element, not a list
|
||||
|
||||
Example:
|
||||
unpack([1,2,3,4,5,6], [3,None,2]) -> ([1,2,3], 4, [5,6])
|
||||
"""
|
||||
seq = list(seq)
|
||||
it = iter(seq)
|
||||
assert sum(1 if s is None else s for s in sizes) == len(seq), "Trying to unpack %s into %s" % (seq, sizes)
|
||||
for size in sizes:
|
||||
if size is None:
|
||||
yield it.__next__()
|
||||
else:
|
||||
li = []
|
||||
for _ in range(size):
|
||||
li.append(it.__next__())
|
||||
yield li
|
||||
|
||||
|
||||
class EzPickle(object):
|
||||
"""Objects that are pickled and unpickled via their constructor
|
||||
arguments.
|
||||
|
||||
Example usage:
|
||||
|
||||
class Dog(Animal, EzPickle):
|
||||
def __init__(self, furcolor, tailkind="bushy"):
|
||||
Animal.__init__()
|
||||
EzPickle.__init__(furcolor, tailkind)
|
||||
...
|
||||
|
||||
When this object is unpickled, a new Dog will be constructed by passing the provided
|
||||
furcolor and tailkind into the constructor. However, philosophers are still not sure
|
||||
whether it is still the same dog.
|
||||
|
||||
This is generally needed only for environments which wrap C/C++ code, such as MuJoCo
|
||||
and Atari.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._ezpickle_args = args
|
||||
self._ezpickle_kwargs = kwargs
|
||||
|
||||
def __getstate__(self):
|
||||
return {"_ezpickle_args": self._ezpickle_args, "_ezpickle_kwargs": self._ezpickle_kwargs}
|
||||
|
||||
def __setstate__(self, d):
|
||||
out = type(self)(*d["_ezpickle_args"], **d["_ezpickle_kwargs"])
|
||||
self.__dict__.update(out.__dict__)
|
||||
|
||||
|
||||
def set_global_seeds(i):
|
||||
try:
|
||||
import tensorflow as tf
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
tf.set_random_seed(i)
|
||||
np.random.seed(i)
|
||||
random.seed(i)
|
||||
|
||||
|
||||
def pretty_eta(seconds_left):
|
||||
"""Print the number of seconds in human readable format.
|
||||
|
||||
Examples:
|
||||
2 days
|
||||
2 hours and 37 minutes
|
||||
less than a minute
|
||||
|
||||
Paramters
|
||||
---------
|
||||
seconds_left: int
|
||||
Number of seconds to be converted to the ETA
|
||||
Returns
|
||||
-------
|
||||
eta: str
|
||||
String representing the pretty ETA.
|
||||
"""
|
||||
minutes_left = seconds_left // 60
|
||||
seconds_left %= 60
|
||||
hours_left = minutes_left // 60
|
||||
minutes_left %= 60
|
||||
days_left = hours_left // 24
|
||||
hours_left %= 24
|
||||
|
||||
def helper(cnt, name):
|
||||
return "{} {}{}".format(str(cnt), name, ('s' if cnt > 1 else ''))
|
||||
|
||||
if days_left > 0:
|
||||
msg = helper(days_left, 'day')
|
||||
if hours_left > 0:
|
||||
msg += ' and ' + helper(hours_left, 'hour')
|
||||
return msg
|
||||
if hours_left > 0:
|
||||
msg = helper(hours_left, 'hour')
|
||||
if minutes_left > 0:
|
||||
msg += ' and ' + helper(minutes_left, 'minute')
|
||||
return msg
|
||||
if minutes_left > 0:
|
||||
return helper(minutes_left, 'minute')
|
||||
return 'less than a minute'
|
||||
|
||||
|
||||
class RunningAvg(object):
|
||||
def __init__(self, gamma, init_value=None):
|
||||
"""Keep a running estimate of a quantity. This is a bit like mean
|
||||
but more sensitive to recent changes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
gamma: float
|
||||
Must be between 0 and 1, where 0 is the most sensitive to recent
|
||||
changes.
|
||||
init_value: float or None
|
||||
Initial value of the estimate. If None, it will be set on the first update.
|
||||
"""
|
||||
self._value = init_value
|
||||
self._gamma = gamma
|
||||
|
||||
def update(self, new_val):
|
||||
"""Update the estimate.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
new_val: float
|
||||
new observated value of estimated quantity.
|
||||
"""
|
||||
if self._value is None:
|
||||
self._value = new_val
|
||||
else:
|
||||
self._value = self._gamma * self._value + (1.0 - self._gamma) * new_val
|
||||
|
||||
def __float__(self):
|
||||
"""Get the current estimate"""
|
||||
return self._value
|
||||
|
||||
|
||||
class SimpleMonitor(gym.Wrapper):
|
||||
def __init__(self, env):
|
||||
"""Adds two qunatities to info returned by every step:
|
||||
|
||||
num_steps: int
|
||||
Number of steps takes so far
|
||||
rewards: [float]
|
||||
All the cumulative rewards for the episodes completed so far.
|
||||
"""
|
||||
super().__init__(env)
|
||||
# current episode state
|
||||
self._current_reward = None
|
||||
self._num_steps = None
|
||||
# temporary monitor state that we do not save
|
||||
self._time_offset = None
|
||||
self._total_steps = None
|
||||
# monitor state
|
||||
self._episode_rewards = []
|
||||
self._episode_lengths = []
|
||||
self._episode_end_times = []
|
||||
|
||||
def _reset(self):
|
||||
obs = self.env.reset()
|
||||
# recompute temporary state if needed
|
||||
if self._time_offset is None:
|
||||
self._time_offset = time.time()
|
||||
if len(self._episode_end_times) > 0:
|
||||
self._time_offset -= self._episode_end_times[-1]
|
||||
if self._total_steps is None:
|
||||
self._total_steps = sum(self._episode_lengths)
|
||||
# update monitor state
|
||||
if self._current_reward is not None:
|
||||
self._episode_rewards.append(self._current_reward)
|
||||
self._episode_lengths.append(self._num_steps)
|
||||
self._episode_end_times.append(time.time() - self._time_offset)
|
||||
# reset episode state
|
||||
self._current_reward = 0
|
||||
self._num_steps = 0
|
||||
|
||||
return obs
|
||||
|
||||
def _step(self, action):
|
||||
obs, rew, done, info = self.env.step(action)
|
||||
self._current_reward += rew
|
||||
self._num_steps += 1
|
||||
self._total_steps += 1
|
||||
info['steps'] = self._total_steps
|
||||
info['rewards'] = self._episode_rewards
|
||||
return (obs, rew, done, info)
|
||||
|
||||
def get_state(self):
|
||||
return {
|
||||
'env_id': self.env.unwrapped.spec.id,
|
||||
'episode_data': {
|
||||
'episode_rewards': self._episode_rewards,
|
||||
'episode_lengths': self._episode_lengths,
|
||||
'episode_end_times': self._episode_end_times,
|
||||
'initial_reset_time': 0,
|
||||
}
|
||||
}
|
||||
|
||||
def set_state(self, state):
|
||||
assert state['env_id'] == self.env.unwrapped.spec.id
|
||||
ed = state['episode_data']
|
||||
self._episode_rewards = ed['episode_rewards']
|
||||
self._episode_lengths = ed['episode_lengths']
|
||||
self._episode_end_times = ed['episode_end_times']
|
||||
|
||||
|
||||
def boolean_flag(parser, name, default=False, help=None):
|
||||
"""Add a boolean flag to argparse parser.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parser: argparse.Parser
|
||||
parser to add the flag to
|
||||
name: str
|
||||
--<name> will enable the flag, while --no-<name> will disable it
|
||||
default: bool or None
|
||||
default value of the flag
|
||||
help: str
|
||||
help string for the flag
|
||||
"""
|
||||
dest = name.replace('-', '_')
|
||||
parser.add_argument("--" + name, action="store_true", default=default, dest=dest, help=help)
|
||||
parser.add_argument("--no-" + name, action="store_false", dest=dest)
|
||||
|
||||
|
||||
def get_wrapper_by_name(env, classname):
|
||||
"""Given an a gym environment possibly wrapped multiple times, returns a wrapper
|
||||
of class named classname or raises ValueError if no such wrapper was applied
|
||||
|
||||
Parameters
|
||||
----------
|
||||
env: gym.Env of gym.Wrapper
|
||||
gym environment
|
||||
classname: str
|
||||
name of the wrapper
|
||||
|
||||
Returns
|
||||
-------
|
||||
wrapper: gym.Wrapper
|
||||
wrapper named classname
|
||||
"""
|
||||
currentenv = env
|
||||
while True:
|
||||
if classname == currentenv.class_name():
|
||||
return currentenv
|
||||
elif isinstance(currentenv, gym.Wrapper):
|
||||
currentenv = currentenv.env
|
||||
else:
|
||||
raise ValueError("Couldn't find wrapper named %s" % classname)
|
||||
|
||||
|
||||
def relatively_safe_pickle_dump(obj, path, compression=False):
|
||||
"""This is just like regular pickle dump, except from the fact that failure cases are
|
||||
different:
|
||||
|
||||
- It's never possible that we end up with a pickle in corrupted state.
|
||||
- If a there was a different file at the path, that file will remain unchanged in the
|
||||
even of failure (provided that filesystem rename is atomic).
|
||||
- it is sometimes possible that we end up with useless temp file which needs to be
|
||||
deleted manually (it will be removed automatically on the next function call)
|
||||
|
||||
The indended use case is periodic checkpoints of experiment state, such that we never
|
||||
corrupt previous checkpoints if the current one fails.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj: object
|
||||
object to pickle
|
||||
path: str
|
||||
path to the output file
|
||||
compression: bool
|
||||
if true pickle will be compressed
|
||||
"""
|
||||
temp_storage = path + ".relatively_safe"
|
||||
if compression:
|
||||
# Using gzip here would be simpler, but the size is limited to 2GB
|
||||
with tempfile.NamedTemporaryFile() as uncompressed_file:
|
||||
pickle.dump(obj, uncompressed_file)
|
||||
with zipfile.ZipFile(temp_storage, "w", compression=zipfile.ZIP_DEFLATED) as myzip:
|
||||
myzip.write(uncompressed_file.name, "data")
|
||||
else:
|
||||
with open(temp_storage, "wb") as f:
|
||||
pickle.dump(obj, f)
|
||||
os.rename(temp_storage, path)
|
||||
|
||||
|
||||
def pickle_load(path, compression=False):
|
||||
"""Unpickle a possible compressed pickle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path: str
|
||||
path to the output file
|
||||
compression: bool
|
||||
if true assumes that pickle was compressed when created and attempts decompression.
|
||||
|
||||
Returns
|
||||
-------
|
||||
obj: object
|
||||
the unpickled object
|
||||
"""
|
||||
|
||||
if compression:
|
||||
with zipfile.ZipFile(path, "r", compression=zipfile.ZIP_DEFLATED) as myzip:
|
||||
with myzip.open("data") as f:
|
||||
return pickle.load(f)
|
||||
else:
|
||||
with open(path, "rb") as f:
|
||||
return pickle.load(f)
|
||||
@@ -0,0 +1,85 @@
|
||||
from mpi4py import MPI
|
||||
import baselines.baselines_common.tf_util as U
|
||||
import tensorflow as tf
|
||||
import numpy as np
|
||||
|
||||
|
||||
class MpiAdam(object):
|
||||
def __init__(self, var_list, *,
|
||||
beta1=0.9, beta2=0.999, epsilon=1e-08,
|
||||
scale_grad_by_procs=True,
|
||||
comm=None):
|
||||
self.var_list = var_list
|
||||
self.beta1 = beta1
|
||||
self.beta2 = beta2
|
||||
self.epsilon = epsilon
|
||||
self.scale_grad_by_procs = scale_grad_by_procs
|
||||
size = sum(U.numel(v) for v in var_list)
|
||||
self.m = np.zeros(size, 'float32')
|
||||
self.v = np.zeros(size, 'float32')
|
||||
|
||||
self.t = 0
|
||||
self.setfromflat = U.SetFromFlat(var_list)
|
||||
self.getflat = U.GetFlat(var_list)
|
||||
self.comm = MPI.COMM_WORLD if comm is None else comm
|
||||
|
||||
def update(self, localg, stepsize):
|
||||
if self.t % 100 == 0:
|
||||
self.check_synced()
|
||||
localg = localg.astype('float32')
|
||||
globalg = np.zeros_like(localg)
|
||||
self.comm.Allreduce(localg, globalg, op=MPI.SUM)
|
||||
if self.scale_grad_by_procs:
|
||||
globalg /= self.comm.Get_size()
|
||||
|
||||
self.t += 1
|
||||
a = stepsize * np.sqrt(1 - self.beta2 ** self.t) / (1 - self.beta1 ** self.t)
|
||||
self.m = self.beta1 * self.m + (1 - self.beta1) * globalg
|
||||
self.v = self.beta2 * self.v + (1 - self.beta2) * (globalg * globalg)
|
||||
step = (- a) * self.m / (np.sqrt(self.v) + self.epsilon)
|
||||
self.setfromflat(self.getflat() + step)
|
||||
|
||||
def sync(self):
|
||||
theta = self.getflat()
|
||||
self.comm.Bcast(theta, root=0)
|
||||
self.setfromflat(theta)
|
||||
|
||||
def check_synced(self):
|
||||
if self.comm.Get_rank() == 0: # this is root
|
||||
theta = self.getflat()
|
||||
self.comm.Bcast(theta, root=0)
|
||||
else:
|
||||
thetalocal = self.getflat()
|
||||
thetaroot = np.empty_like(thetalocal)
|
||||
self.comm.Bcast(thetaroot, root=0)
|
||||
assert (thetaroot == thetalocal).all(), (thetaroot, thetalocal)
|
||||
|
||||
|
||||
@U.in_session
|
||||
def test_MpiAdam():
|
||||
np.random.seed(0)
|
||||
tf.set_random_seed(0)
|
||||
|
||||
a = tf.Variable(np.random.randn(3).astype('float32'))
|
||||
b = tf.Variable(np.random.randn(2, 5).astype('float32'))
|
||||
loss = tf.reduce_sum(tf.square(a)) + tf.reduce_sum(tf.sin(b))
|
||||
|
||||
stepsize = 1e-2
|
||||
update_op = tf.train.AdamOptimizer(stepsize).minimize(loss)
|
||||
do_update = U.function([], loss, updates=[update_op])
|
||||
|
||||
tf.get_default_session().run(tf.global_variables_initializer())
|
||||
for i in range(10):
|
||||
print(i, do_update())
|
||||
|
||||
tf.set_random_seed(0)
|
||||
tf.get_default_session().run(tf.global_variables_initializer())
|
||||
|
||||
var_list = [a, b]
|
||||
lossandgrad = U.function([], [loss, U.flatgrad(loss, var_list)], updates=[update_op])
|
||||
adam = MpiAdam(var_list)
|
||||
|
||||
for i in range(10):
|
||||
l, g = lossandgrad()
|
||||
adam.update(g, stepsize)
|
||||
print(i, l)
|
||||
@@ -0,0 +1,24 @@
|
||||
import os, subprocess, sys
|
||||
|
||||
|
||||
def mpi_fork(n, bind_to_core=False):
|
||||
"""Re-launches the current script with workers
|
||||
Returns "parent" for original parent, "child" for MPI children
|
||||
"""
|
||||
if n <= 1:
|
||||
return "child"
|
||||
if os.getenv("IN_MPI") is None:
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
MKL_NUM_THREADS="1",
|
||||
OMP_NUM_THREADS="1",
|
||||
IN_MPI="1"
|
||||
)
|
||||
args = ["mpirun", "-np", str(n)]
|
||||
if bind_to_core:
|
||||
args += ["-bind-to", "core"]
|
||||
args += [sys.executable] + sys.argv
|
||||
subprocess.check_call(args, env=env)
|
||||
return "parent"
|
||||
else:
|
||||
return "child"
|
||||
@@ -0,0 +1,52 @@
|
||||
from mpi4py import MPI
|
||||
import numpy as np
|
||||
from baselines.baselines_common import zipsame
|
||||
|
||||
|
||||
def mpi_moments(x, axis=0):
|
||||
x = np.asarray(x, dtype='float64')
|
||||
newshape = list(x.shape)
|
||||
newshape.pop(axis)
|
||||
n = np.prod(newshape, dtype=int)
|
||||
totalvec = np.zeros(n * 2 + 1, 'float64')
|
||||
addvec = np.concatenate([x.sum(axis=axis).ravel(),
|
||||
np.square(x).sum(axis=axis).ravel(),
|
||||
np.array([x.shape[axis]], dtype='float64')])
|
||||
MPI.COMM_WORLD.Allreduce(addvec, totalvec, op=MPI.SUM)
|
||||
sum = totalvec[:n]
|
||||
sumsq = totalvec[n:2 * n]
|
||||
count = totalvec[2 * n]
|
||||
if count == 0:
|
||||
mean = np.empty(newshape);
|
||||
mean[:] = np.nan
|
||||
std = np.empty(newshape);
|
||||
std[:] = np.nan
|
||||
else:
|
||||
mean = sum / count
|
||||
std = np.sqrt(np.maximum(sumsq / count - np.square(mean), 0))
|
||||
return mean, std, count
|
||||
|
||||
|
||||
def test_runningmeanstd():
|
||||
comm = MPI.COMM_WORLD
|
||||
np.random.seed(0)
|
||||
for (triple, axis) in [
|
||||
((np.random.randn(3), np.random.randn(4), np.random.randn(5)), 0),
|
||||
((np.random.randn(3, 2), np.random.randn(4, 2), np.random.randn(5, 2)), 0),
|
||||
((np.random.randn(2, 3), np.random.randn(2, 4), np.random.randn(2, 4)), 1),
|
||||
]:
|
||||
|
||||
x = np.concatenate(triple, axis=axis)
|
||||
ms1 = [x.mean(axis=axis), x.std(axis=axis), x.shape[axis]]
|
||||
|
||||
ms2 = mpi_moments(triple[comm.Get_rank()], axis=axis)
|
||||
|
||||
for (a1, a2) in zipsame(ms1, ms2):
|
||||
print(a1, a2)
|
||||
assert np.allclose(a1, a2)
|
||||
print("ok!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# mpirun -np 3 python <script>
|
||||
test_runningmeanstd()
|
||||
@@ -0,0 +1,112 @@
|
||||
from mpi4py import MPI
|
||||
import tensorflow as tf
|
||||
import baselines.baselines_common.tf_util as U
|
||||
import numpy as np
|
||||
|
||||
|
||||
class RunningMeanStd(object):
|
||||
# https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Parallel_algorithm
|
||||
def __init__(self, epsilon=1e-2, shape=()):
|
||||
self._sum = tf.get_variable(
|
||||
dtype=tf.float64,
|
||||
shape=shape,
|
||||
initializer=tf.constant_initializer(0.0),
|
||||
name="runningsum", trainable=False)
|
||||
self._sumsq = tf.get_variable(
|
||||
dtype=tf.float64,
|
||||
shape=shape,
|
||||
initializer=tf.constant_initializer(epsilon),
|
||||
name="runningsumsq", trainable=False)
|
||||
self._count = tf.get_variable(
|
||||
dtype=tf.float64,
|
||||
shape=(),
|
||||
initializer=tf.constant_initializer(epsilon),
|
||||
name="count", trainable=False)
|
||||
self.shape = shape
|
||||
|
||||
self.mean = tf.to_float(self._sum / self._count)
|
||||
self.std = tf.sqrt(
|
||||
tf.maximum(tf.to_float(self._sumsq / self._count) - tf.square(self.mean), 1e-2))
|
||||
|
||||
newsum = tf.placeholder(shape=self.shape, dtype=tf.float64, name='sum')
|
||||
newsumsq = tf.placeholder(shape=self.shape, dtype=tf.float64, name='var')
|
||||
newcount = tf.placeholder(shape=[], dtype=tf.float64, name='count')
|
||||
self.incfiltparams = U.function([newsum, newsumsq, newcount], [],
|
||||
updates=[tf.assign_add(self._sum, newsum),
|
||||
tf.assign_add(self._sumsq, newsumsq),
|
||||
tf.assign_add(self._count, newcount)])
|
||||
|
||||
def update(self, x):
|
||||
x = x.astype('float64')
|
||||
n = int(np.prod(self.shape))
|
||||
totalvec = np.zeros(n * 2 + 1, 'float64')
|
||||
addvec = np.concatenate([x.sum(axis=0).ravel(), np.square(x).sum(axis=0).ravel(),
|
||||
np.array([len(x)], dtype='float64')])
|
||||
MPI.COMM_WORLD.Allreduce(addvec, totalvec, op=MPI.SUM)
|
||||
self.incfiltparams(totalvec[0:n].reshape(self.shape), totalvec[n:2 * n].reshape(self.shape),
|
||||
totalvec[2 * n])
|
||||
|
||||
|
||||
@U.in_session
|
||||
def test_runningmeanstd():
|
||||
for (x1, x2, x3) in [
|
||||
(np.random.randn(3), np.random.randn(4), np.random.randn(5)),
|
||||
(np.random.randn(3, 2), np.random.randn(4, 2), np.random.randn(5, 2)),
|
||||
]:
|
||||
rms = RunningMeanStd(epsilon=0.0, shape=x1.shape[1:])
|
||||
U.initialize()
|
||||
|
||||
x = np.concatenate([x1, x2, x3], axis=0)
|
||||
ms1 = [x.mean(axis=0), x.std(axis=0)]
|
||||
rms.update(x1)
|
||||
rms.update(x2)
|
||||
rms.update(x3)
|
||||
ms2 = U.eval([rms.mean, rms.std])
|
||||
|
||||
assert np.allclose(ms1, ms2)
|
||||
|
||||
|
||||
@U.in_session
|
||||
def test_dist():
|
||||
np.random.seed(0)
|
||||
p1, p2, p3 = (np.random.randn(3, 1), np.random.randn(4, 1), np.random.randn(5, 1))
|
||||
q1, q2, q3 = (np.random.randn(6, 1), np.random.randn(7, 1), np.random.randn(8, 1))
|
||||
|
||||
# p1,p2,p3=(np.random.randn(3), np.random.randn(4), np.random.randn(5))
|
||||
# q1,q2,q3=(np.random.randn(6), np.random.randn(7), np.random.randn(8))
|
||||
|
||||
comm = MPI.COMM_WORLD
|
||||
assert comm.Get_size() == 2
|
||||
if comm.Get_rank() == 0:
|
||||
x1, x2, x3 = p1, p2, p3
|
||||
elif comm.Get_rank() == 1:
|
||||
x1, x2, x3 = q1, q2, q3
|
||||
else:
|
||||
assert False
|
||||
|
||||
rms = RunningMeanStd(epsilon=0.0, shape=(1,))
|
||||
U.initialize()
|
||||
|
||||
rms.update(x1)
|
||||
rms.update(x2)
|
||||
rms.update(x3)
|
||||
|
||||
bigvec = np.concatenate([p1, p2, p3, q1, q2, q3])
|
||||
|
||||
def checkallclose(x, y):
|
||||
print(x, y)
|
||||
return np.allclose(x, y)
|
||||
|
||||
assert checkallclose(
|
||||
bigvec.mean(axis=0),
|
||||
U.eval(rms.mean)
|
||||
)
|
||||
assert checkallclose(
|
||||
bigvec.std(axis=0),
|
||||
U.eval(rms.std)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run with mpirun -np 2 python <filename>
|
||||
test_dist()
|
||||
@@ -0,0 +1,35 @@
|
||||
from mpi4py import MPI
|
||||
import baselines.baselines_common.tf_util as U
|
||||
import tensorflow as tf
|
||||
|
||||
|
||||
class MpiSaver(object):
|
||||
def __init__(self, var_list=None, *,
|
||||
comm=None,
|
||||
log_prefix="/tmp"):
|
||||
self.var_list = var_list
|
||||
self.t = 0
|
||||
|
||||
self.saver = tf.train.Saver(
|
||||
var_list=var_list,
|
||||
max_to_keep=100,
|
||||
keep_checkpoint_every_n_hours=0.25,
|
||||
pad_step_number=True,
|
||||
save_relative_paths=True)
|
||||
self.log_prefix = log_prefix
|
||||
|
||||
self.comm = MPI.COMM_WORLD if comm is None else comm
|
||||
|
||||
def restore(self, restore_from=None):
|
||||
if restore_from is not None:
|
||||
self.saver.restore(U.get_session(), restore_from)
|
||||
self.t += int(restore_from.split("-")[-1])
|
||||
self.sync()
|
||||
|
||||
def sync(self):
|
||||
if self.comm.Get_rank() == 0: # this is root
|
||||
self.saver.save(
|
||||
U.get_session(),
|
||||
"{}/model.ckpt".format(self.log_prefix),
|
||||
global_step=self.t)
|
||||
self.t += 1
|
||||
@@ -0,0 +1,99 @@
|
||||
"""This file is used for specifying various schedules that evolve over
|
||||
time throughout the execution of the algorithm, such as:
|
||||
- learning rate for the optimizer
|
||||
- exploration epsilon for the epsilon greedy exploration strategy
|
||||
- beta parameter for beta parameter in prioritized replay
|
||||
|
||||
Each schedule has a function `value(t)` which returns the current value
|
||||
of the parameter given the timestep t of the optimization procedure.
|
||||
"""
|
||||
|
||||
|
||||
class Schedule(object):
|
||||
def value(self, t):
|
||||
"""Value of the schedule at time t"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ConstantSchedule(object):
|
||||
def __init__(self, value):
|
||||
"""Value remains constant over time.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value: float
|
||||
Constant value of the schedule
|
||||
"""
|
||||
self._v = value
|
||||
|
||||
def value(self, t):
|
||||
"""See Schedule.value"""
|
||||
return self._v
|
||||
|
||||
|
||||
def linear_interpolation(l, r, alpha):
|
||||
return l + alpha * (r - l)
|
||||
|
||||
|
||||
class PiecewiseSchedule(object):
|
||||
def __init__(self, endpoints, interpolation=linear_interpolation, outside_value=None):
|
||||
"""Piecewise schedule.
|
||||
|
||||
endpoints: [(int, int)]
|
||||
list of pairs `(time, value)` meanining that schedule should output
|
||||
`value` when `t==time`. All the values for time must be sorted in
|
||||
an increasing order. When t is between two times, e.g. `(time_a, value_a)`
|
||||
and `(time_b, value_b)`, such that `time_a <= t < time_b` then value outputs
|
||||
`interpolation(value_a, value_b, alpha)` where alpha is a fraction of
|
||||
time passed between `time_a` and `time_b` for time `t`.
|
||||
interpolation: lambda float, float, float: float
|
||||
a function that takes value to the left and to the right of t according
|
||||
to the `endpoints`. Alpha is the fraction of distance from left endpoint to
|
||||
right endpoint that t has covered. See linear_interpolation for example.
|
||||
outside_value: float
|
||||
if the value is requested outside of all the intervals sepecified in
|
||||
`endpoints` this value is returned. If None then AssertionError is
|
||||
raised when outside value is requested.
|
||||
"""
|
||||
idxes = [e[0] for e in endpoints]
|
||||
assert idxes == sorted(idxes)
|
||||
self._interpolation = interpolation
|
||||
self._outside_value = outside_value
|
||||
self._endpoints = endpoints
|
||||
|
||||
def value(self, t):
|
||||
"""See Schedule.value"""
|
||||
for (l_t, l), (r_t, r) in zip(self._endpoints[:-1], self._endpoints[1:]):
|
||||
if l_t <= t and t < r_t:
|
||||
alpha = float(t - l_t) / (r_t - l_t)
|
||||
return self._interpolation(l, r, alpha)
|
||||
|
||||
# t does not belong to any of the pieces, so doom.
|
||||
assert self._outside_value is not None
|
||||
return self._outside_value
|
||||
|
||||
|
||||
class LinearSchedule(object):
|
||||
def __init__(self, schedule_timesteps, final_p, initial_p=1.0):
|
||||
"""Linear interpolation between initial_p and final_p over
|
||||
schedule_timesteps. After this many timesteps pass final_p is
|
||||
returned.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
schedule_timesteps: int
|
||||
Number of timesteps for which to linearly anneal initial_p
|
||||
to final_p
|
||||
initial_p: float
|
||||
initial output value
|
||||
final_p: float
|
||||
final output value
|
||||
"""
|
||||
self.schedule_timesteps = schedule_timesteps
|
||||
self.final_p = final_p
|
||||
self.initial_p = initial_p
|
||||
|
||||
def value(self, t):
|
||||
"""See Schedule.value"""
|
||||
fraction = min(float(t) / self.schedule_timesteps, 1.0)
|
||||
return self.initial_p + fraction * (self.final_p - self.initial_p)
|
||||
@@ -0,0 +1,146 @@
|
||||
import operator
|
||||
|
||||
|
||||
class SegmentTree(object):
|
||||
def __init__(self, capacity, operation, neutral_element):
|
||||
"""Build a Segment Tree data structure.
|
||||
|
||||
https://en.wikipedia.org/wiki/Segment_tree
|
||||
|
||||
Can be used as regular array, but with two
|
||||
important differences:
|
||||
|
||||
a) setting item's value is slightly slower.
|
||||
It is O(lg capacity) instead of O(1).
|
||||
b) user has access to an efficient `reduce`
|
||||
operation which reduces `operation` over
|
||||
a contiguous subsequence of items in the
|
||||
array.
|
||||
|
||||
Paramters
|
||||
---------
|
||||
capacity: int
|
||||
Total size of the array - must be a power of two.
|
||||
operation: lambda obj, obj -> obj
|
||||
and operation for combining elements (eg. sum, max)
|
||||
must for a mathematical group together with the set of
|
||||
possible values for array elements.
|
||||
neutral_element: obj
|
||||
neutral element for the operation above. eg. float('-inf')
|
||||
for max and 0 for sum.
|
||||
"""
|
||||
assert capacity > 0 and capacity & (capacity - 1) == 0, "capacity must be positive and a power of 2."
|
||||
self._capacity = capacity
|
||||
self._value = [neutral_element for _ in range(2 * capacity)]
|
||||
self._operation = operation
|
||||
|
||||
def _reduce_helper(self, start, end, node, node_start, node_end):
|
||||
if start == node_start and end == node_end:
|
||||
return self._value[node]
|
||||
mid = (node_start + node_end) // 2
|
||||
if end <= mid:
|
||||
return self._reduce_helper(start, end, 2 * node, node_start, mid)
|
||||
else:
|
||||
if mid + 1 <= start:
|
||||
return self._reduce_helper(start, end, 2 * node + 1, mid + 1, node_end)
|
||||
else:
|
||||
return self._operation(
|
||||
self._reduce_helper(start, mid, 2 * node, node_start, mid),
|
||||
self._reduce_helper(mid + 1, end, 2 * node + 1, mid + 1, node_end)
|
||||
)
|
||||
|
||||
def reduce(self, start=0, end=None):
|
||||
"""Returns result of applying `self.operation`
|
||||
to a contiguous subsequence of the array.
|
||||
|
||||
self.operation(arr[start], operation(arr[start+1], operation(... arr[end])))
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start: int
|
||||
beginning of the subsequence
|
||||
end: int
|
||||
end of the subsequences
|
||||
|
||||
Returns
|
||||
-------
|
||||
reduced: obj
|
||||
result of reducing self.operation over the specified range of array elements.
|
||||
"""
|
||||
if end is None:
|
||||
end = self._capacity
|
||||
if end < 0:
|
||||
end += self._capacity
|
||||
end -= 1
|
||||
return self._reduce_helper(start, end, 1, 0, self._capacity - 1)
|
||||
|
||||
def __setitem__(self, idx, val):
|
||||
# index of the leaf
|
||||
idx += self._capacity
|
||||
self._value[idx] = val
|
||||
idx //= 2
|
||||
while idx >= 1:
|
||||
self._value[idx] = self._operation(
|
||||
self._value[2 * idx],
|
||||
self._value[2 * idx + 1]
|
||||
)
|
||||
idx //= 2
|
||||
|
||||
def __getitem__(self, idx):
|
||||
assert 0 <= idx < self._capacity
|
||||
return self._value[self._capacity + idx]
|
||||
|
||||
|
||||
class SumSegmentTree(SegmentTree):
|
||||
def __init__(self, capacity):
|
||||
super(SumSegmentTree, self).__init__(
|
||||
capacity=capacity,
|
||||
operation=operator.add,
|
||||
neutral_element=0.0
|
||||
)
|
||||
|
||||
def sum(self, start=0, end=None):
|
||||
"""Returns arr[start] + ... + arr[end]"""
|
||||
return super(SumSegmentTree, self).reduce(start, end)
|
||||
|
||||
def find_prefixsum_idx(self, prefixsum):
|
||||
"""Find the highest index `i` in the array such that
|
||||
sum(arr[0] + arr[1] + ... + arr[i - i]) <= prefixsum
|
||||
|
||||
if array values are probabilities, this function
|
||||
allows to sample indexes according to the discrete
|
||||
probability efficiently.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
perfixsum: float
|
||||
upperbound on the sum of array prefix
|
||||
|
||||
Returns
|
||||
-------
|
||||
idx: int
|
||||
highest index satisfying the prefixsum constraint
|
||||
"""
|
||||
assert 0 <= prefixsum <= self.sum() + 1e-5
|
||||
idx = 1
|
||||
while idx < self._capacity: # while non-leaf
|
||||
if self._value[2 * idx] > prefixsum:
|
||||
idx = 2 * idx
|
||||
else:
|
||||
prefixsum -= self._value[2 * idx]
|
||||
idx = 2 * idx + 1
|
||||
return idx - self._capacity
|
||||
|
||||
|
||||
class MinSegmentTree(SegmentTree):
|
||||
def __init__(self, capacity):
|
||||
super(MinSegmentTree, self).__init__(
|
||||
capacity=capacity,
|
||||
operation=min,
|
||||
neutral_element=float('inf')
|
||||
)
|
||||
|
||||
def min(self, start=0, end=None):
|
||||
"""Returns min(arr[start], ..., arr[end])"""
|
||||
|
||||
return super(MinSegmentTree, self).reduce(start, end)
|
||||
@@ -0,0 +1,753 @@
|
||||
import numpy as np
|
||||
import tensorflow as tf # pylint: ignore-module
|
||||
import builtins
|
||||
import functools
|
||||
import copy
|
||||
import os
|
||||
import collections
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Make consistent with numpy
|
||||
# ================================================================
|
||||
|
||||
clip = tf.clip_by_value
|
||||
|
||||
|
||||
def sum(x, axis=None, keepdims=False):
|
||||
axis = None if axis is None else [axis]
|
||||
return tf.reduce_sum(x, axis=axis, keep_dims=keepdims)
|
||||
|
||||
|
||||
def mean(x, axis=None, keepdims=False):
|
||||
axis = None if axis is None else [axis]
|
||||
return tf.reduce_mean(x, axis=axis, keep_dims=keepdims)
|
||||
|
||||
|
||||
def var(x, axis=None, keepdims=False):
|
||||
meanx = mean(x, axis=axis, keepdims=keepdims)
|
||||
return mean(tf.square(x - meanx), axis=axis, keepdims=keepdims)
|
||||
|
||||
|
||||
def std(x, axis=None, keepdims=False):
|
||||
return tf.sqrt(var(x, axis=axis, keepdims=keepdims))
|
||||
|
||||
|
||||
def max(x, axis=None, keepdims=False):
|
||||
axis = None if axis is None else [axis]
|
||||
return tf.reduce_max(x, axis=axis, keep_dims=keepdims)
|
||||
|
||||
|
||||
def min(x, axis=None, keepdims=False):
|
||||
axis = None if axis is None else [axis]
|
||||
return tf.reduce_min(x, axis=axis, keep_dims=keepdims)
|
||||
|
||||
|
||||
def concatenate(arrs, axis=0):
|
||||
return tf.concat(axis=axis, values=arrs)
|
||||
|
||||
|
||||
def argmax(x, axis=None):
|
||||
return tf.argmax(x, axis=axis)
|
||||
|
||||
|
||||
def switch(condition, then_expression, else_expression):
|
||||
"""Switches between two operations depending on a scalar value (int or bool).
|
||||
Note that both `then_expression` and `else_expression`
|
||||
should be symbolic tensors of the *same shape*.
|
||||
|
||||
# Arguments
|
||||
condition: scalar tensor.
|
||||
then_expression: TensorFlow operation.
|
||||
else_expression: TensorFlow operation.
|
||||
"""
|
||||
x_shape = copy.copy(then_expression.get_shape())
|
||||
x = tf.cond(tf.cast(condition, 'bool'),
|
||||
lambda: then_expression,
|
||||
lambda: else_expression)
|
||||
x.set_shape(x_shape)
|
||||
return x
|
||||
|
||||
# ================================================================
|
||||
# Extras
|
||||
# ================================================================
|
||||
|
||||
|
||||
def l2loss(params):
|
||||
if len(params) == 0:
|
||||
return tf.constant(0.0)
|
||||
else:
|
||||
return tf.add_n([sum(tf.square(p)) for p in params])
|
||||
|
||||
|
||||
def lrelu(x, leak=0.2):
|
||||
f1 = 0.5 * (1 + leak)
|
||||
f2 = 0.5 * (1 - leak)
|
||||
return f1 * x + f2 * abs(x)
|
||||
|
||||
|
||||
def categorical_sample_logits(X):
|
||||
# https://github.com/tensorflow/tensorflow/issues/456
|
||||
U = tf.random_uniform(tf.shape(X))
|
||||
return argmax(X - tf.log(-tf.log(U)), axis=1)
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Inputs
|
||||
# ================================================================
|
||||
|
||||
|
||||
def is_placeholder(x):
|
||||
return type(x) is tf.Tensor and len(x.op.inputs) == 0
|
||||
|
||||
|
||||
class TfInput(object):
|
||||
def __init__(self, name="(unnamed)"):
|
||||
"""Generalized Tensorflow placeholder. The main differences are:
|
||||
- possibly uses multiple placeholders internally and returns multiple values
|
||||
- can apply light postprocessing to the value feed to placeholder.
|
||||
"""
|
||||
self.name = name
|
||||
|
||||
def get(self):
|
||||
"""Return the tf variable(s) representing the possibly postprocessed value
|
||||
of placeholder(s).
|
||||
"""
|
||||
raise NotImplemented()
|
||||
|
||||
def make_feed_dict(data):
|
||||
"""Given data input it to the placeholder(s)."""
|
||||
raise NotImplemented()
|
||||
|
||||
|
||||
class PlacholderTfInput(TfInput):
|
||||
def __init__(self, placeholder):
|
||||
"""Wrapper for regular tensorflow placeholder."""
|
||||
super().__init__(placeholder.name)
|
||||
self._placeholder = placeholder
|
||||
|
||||
def get(self):
|
||||
return self._placeholder
|
||||
|
||||
def make_feed_dict(self, data):
|
||||
return {self._placeholder: data}
|
||||
|
||||
|
||||
class BatchInput(PlacholderTfInput):
|
||||
def __init__(self, shape, dtype=tf.float32, name=None):
|
||||
"""Creates a placeholder for a batch of tensors of a given shape and dtype
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shape: [int]
|
||||
shape of a single elemenet of the batch
|
||||
dtype: tf.dtype
|
||||
number representation used for tensor contents
|
||||
name: str
|
||||
name of the underlying placeholder
|
||||
"""
|
||||
super().__init__(tf.placeholder(dtype, [None] + list(shape), name=name))
|
||||
|
||||
|
||||
class Uint8Input(PlacholderTfInput):
|
||||
def __init__(self, shape, name=None):
|
||||
"""Takes input in uint8 format which is cast to float32 and divided by 255
|
||||
before passing it to the model.
|
||||
|
||||
On GPU this ensures lower data transfer times.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shape: [int]
|
||||
shape of the tensor.
|
||||
name: str
|
||||
name of the underlying placeholder
|
||||
"""
|
||||
|
||||
super().__init__(tf.placeholder(tf.uint8, [None] + list(shape), name=name))
|
||||
self._shape = shape
|
||||
self._output = tf.cast(super().get(), tf.float32) / 255.0
|
||||
|
||||
def get(self):
|
||||
return self._output
|
||||
|
||||
|
||||
def ensure_tf_input(thing):
|
||||
"""Takes either tf.placeholder of TfInput and outputs equivalent TfInput"""
|
||||
if isinstance(thing, TfInput):
|
||||
return thing
|
||||
elif is_placeholder(thing):
|
||||
return PlacholderTfInput(thing)
|
||||
else:
|
||||
raise ValueError("Must be a placeholder or TfInput")
|
||||
|
||||
# ================================================================
|
||||
# Mathematical utils
|
||||
# ================================================================
|
||||
|
||||
|
||||
def huber_loss(x, delta=1.0):
|
||||
"""Reference: https://en.wikipedia.org/wiki/Huber_loss"""
|
||||
return tf.where(
|
||||
tf.abs(x) < delta,
|
||||
tf.square(x) * 0.5,
|
||||
delta * (tf.abs(x) - 0.5 * delta)
|
||||
)
|
||||
|
||||
# ================================================================
|
||||
# Optimizer utils
|
||||
# ================================================================
|
||||
|
||||
|
||||
def minimize_and_clip(optimizer, objective, var_list, clip_val=10):
|
||||
"""Minimized `objective` using `optimizer` w.r.t. variables in
|
||||
`var_list` while ensure the norm of the gradients for each
|
||||
variable is clipped to `clip_val`
|
||||
"""
|
||||
gradients = optimizer.compute_gradients(objective, var_list=var_list)
|
||||
for i, (grad, var) in enumerate(gradients):
|
||||
if grad is not None:
|
||||
gradients[i] = (tf.clip_by_norm(grad, clip_val), var)
|
||||
return optimizer.apply_gradients(gradients)
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Global session
|
||||
# ================================================================
|
||||
|
||||
def get_session():
|
||||
"""Returns recently made Tensorflow session"""
|
||||
return tf.get_default_session()
|
||||
|
||||
|
||||
def make_session(num_cpu):
|
||||
"""Returns a session that will use <num_cpu> CPU's only"""
|
||||
tf_config = tf.ConfigProto(
|
||||
inter_op_parallelism_threads=num_cpu,
|
||||
intra_op_parallelism_threads=num_cpu)
|
||||
return tf.Session(config=tf_config)
|
||||
|
||||
|
||||
def single_threaded_session():
|
||||
"""Returns a session which will only use a single CPU"""
|
||||
return make_session(1)
|
||||
|
||||
|
||||
ALREADY_INITIALIZED = set()
|
||||
|
||||
|
||||
def initialize():
|
||||
"""Initialize all the uninitialized variables in the global scope."""
|
||||
new_variables = set(tf.global_variables()) - ALREADY_INITIALIZED
|
||||
get_session().run(tf.variables_initializer(new_variables))
|
||||
ALREADY_INITIALIZED.update(new_variables)
|
||||
|
||||
|
||||
def eval(expr, feed_dict=None):
|
||||
if feed_dict is None:
|
||||
feed_dict = {}
|
||||
return get_session().run(expr, feed_dict=feed_dict)
|
||||
|
||||
|
||||
VALUE_SETTERS = collections.OrderedDict()
|
||||
|
||||
|
||||
def set_value(v, val):
|
||||
global VALUE_SETTERS
|
||||
if v in VALUE_SETTERS:
|
||||
set_op, set_endpoint = VALUE_SETTERS[v]
|
||||
else:
|
||||
set_endpoint = tf.placeholder(v.dtype)
|
||||
set_op = v.assign(set_endpoint)
|
||||
VALUE_SETTERS[v] = (set_op, set_endpoint)
|
||||
get_session().run(set_op, feed_dict={set_endpoint: val})
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Saving variables
|
||||
# ================================================================
|
||||
|
||||
|
||||
def load_state(fname):
|
||||
saver = tf.train.Saver()
|
||||
saver.restore(get_session(), fname)
|
||||
|
||||
|
||||
def save_state(fname):
|
||||
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
||||
saver = tf.train.Saver()
|
||||
saver.save(get_session(), fname)
|
||||
|
||||
# ================================================================
|
||||
# Model components
|
||||
# ================================================================
|
||||
|
||||
|
||||
def normc_initializer(std=1.0):
|
||||
def _initializer(shape, dtype=None, partition_info=None): # pylint: disable=W0613
|
||||
out = np.random.randn(*shape).astype(np.float32)
|
||||
out *= std / np.sqrt(np.square(out).sum(axis=0, keepdims=True))
|
||||
return tf.constant(out)
|
||||
return _initializer
|
||||
|
||||
|
||||
def conv2d(x, num_filters, name, filter_size=(3, 3), stride=(1, 1), pad="SAME", dtype=tf.float32, collections=None,
|
||||
summary_tag=None):
|
||||
with tf.variable_scope(name):
|
||||
stride_shape = [1, stride[0], stride[1], 1]
|
||||
filter_shape = [filter_size[0], filter_size[1], int(x.get_shape()[3]), num_filters]
|
||||
|
||||
# there are "num input feature maps * filter height * filter width"
|
||||
# inputs to each hidden unit
|
||||
fan_in = intprod(filter_shape[:3])
|
||||
# each unit in the lower layer receives a gradient from:
|
||||
# "num output feature maps * filter height * filter width" /
|
||||
# pooling size
|
||||
fan_out = intprod(filter_shape[:2]) * num_filters
|
||||
# initialize weights with random weights
|
||||
w_bound = np.sqrt(6. / (fan_in + fan_out))
|
||||
|
||||
w = tf.get_variable("W", filter_shape, dtype, tf.random_uniform_initializer(-w_bound, w_bound),
|
||||
collections=collections)
|
||||
b = tf.get_variable("b", [1, 1, 1, num_filters], initializer=tf.zeros_initializer(),
|
||||
collections=collections)
|
||||
|
||||
if summary_tag is not None:
|
||||
tf.summary.image(summary_tag,
|
||||
tf.transpose(tf.reshape(w, [filter_size[0], filter_size[1], -1, 1]),
|
||||
[2, 0, 1, 3]),
|
||||
max_images=10)
|
||||
|
||||
return tf.nn.conv2d(x, w, stride_shape, pad) + b
|
||||
|
||||
|
||||
def dense(x, size, name, weight_init=None, bias=True):
|
||||
w = tf.get_variable(name + "/w", [x.get_shape()[1], size], initializer=weight_init)
|
||||
ret = tf.matmul(x, w)
|
||||
if bias:
|
||||
b = tf.get_variable(name + "/b", [size], initializer=tf.zeros_initializer())
|
||||
return ret + b
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
def wndense(x, size, name, init_scale=1.0):
|
||||
v = tf.get_variable(name + "/V", [int(x.get_shape()[1]), size],
|
||||
initializer=tf.random_normal_initializer(0, 0.05))
|
||||
g = tf.get_variable(name + "/g", [size], initializer=tf.constant_initializer(init_scale))
|
||||
b = tf.get_variable(name + "/b", [size], initializer=tf.constant_initializer(0.0))
|
||||
|
||||
# use weight normalization (Salimans & Kingma, 2016)
|
||||
x = tf.matmul(x, v)
|
||||
scaler = g / tf.sqrt(sum(tf.square(v), axis=0, keepdims=True))
|
||||
return tf.reshape(scaler, [1, size]) * x + tf.reshape(b, [1, size])
|
||||
|
||||
|
||||
def densenobias(x, size, name, weight_init=None):
|
||||
return dense(x, size, name, weight_init=weight_init, bias=False)
|
||||
|
||||
|
||||
def dropout(x, pkeep, phase=None, mask=None):
|
||||
mask = tf.floor(pkeep + tf.random_uniform(tf.shape(x))) if mask is None else mask
|
||||
if phase is None:
|
||||
return mask * x
|
||||
else:
|
||||
return switch(phase, mask * x, pkeep * x)
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Theano-like Function
|
||||
# ================================================================
|
||||
|
||||
|
||||
|
||||
def function(inputs, outputs, updates=None, givens=None):
|
||||
"""Just like Theano function. Take a bunch of tensorflow placeholders and expressions
|
||||
computed based on those placeholders and produces f(inputs) -> outputs. Function f takes
|
||||
values to be fed to the input's placeholders and produces the values of the expressions
|
||||
in outputs.
|
||||
|
||||
Input values can be passed in the same order as inputs or can be provided as kwargs based
|
||||
on placeholder name (passed to constructor or accessible via placeholder.op.name).
|
||||
|
||||
Example:
|
||||
x = tf.placeholder(tf.int32, (), name="x")
|
||||
y = tf.placeholder(tf.int32, (), name="y")
|
||||
z = 3 * x + 2 * y
|
||||
lin = function([x, y], z, givens={y: 0})
|
||||
|
||||
with single_threaded_session():
|
||||
initialize()
|
||||
|
||||
assert lin(2) == 6
|
||||
assert lin(x=3) == 9
|
||||
assert lin(2, 2) == 10
|
||||
assert lin(x=2, y=3) == 12
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inputs: [tf.placeholder or TfInput]
|
||||
list of input arguments
|
||||
outputs: [tf.Variable] or tf.Variable
|
||||
list of outputs or a single output to be returned from function. Returned
|
||||
value will also have the same shape.
|
||||
"""
|
||||
if isinstance(outputs, list):
|
||||
return _Function(inputs, outputs, updates, givens=givens)
|
||||
elif isinstance(outputs, (dict, collections.OrderedDict)):
|
||||
f = _Function(inputs, outputs.values(), updates, givens=givens)
|
||||
return lambda *args, **kwargs: type(outputs)(zip(outputs.keys(), f(*args, **kwargs)))
|
||||
else:
|
||||
f = _Function(inputs, [outputs], updates, givens=givens)
|
||||
return lambda *args, **kwargs: f(*args, **kwargs)[0]
|
||||
|
||||
|
||||
class _Function(object):
|
||||
def __init__(self, inputs, outputs, updates, givens, check_nan=False):
|
||||
for inpt in inputs:
|
||||
if not issubclass(type(inpt), TfInput):
|
||||
assert len(inpt.op.inputs) == 0, "inputs should all be placeholders of src.baselines_common.TfInput"
|
||||
self.inputs = inputs
|
||||
updates = updates or []
|
||||
self.update_group = tf.group(*updates)
|
||||
self.outputs_update = list(outputs) + [self.update_group]
|
||||
self.givens = {} if givens is None else givens
|
||||
self.check_nan = check_nan
|
||||
|
||||
def _feed_input(self, feed_dict, inpt, value):
|
||||
if issubclass(type(inpt), TfInput):
|
||||
feed_dict.update(inpt.make_feed_dict(value))
|
||||
elif is_placeholder(inpt):
|
||||
feed_dict[inpt] = value
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
assert len(args) <= len(self.inputs), "Too many arguments provided"
|
||||
feed_dict = {}
|
||||
# Update the args
|
||||
for inpt, value in zip(self.inputs, args):
|
||||
self._feed_input(feed_dict, inpt, value)
|
||||
# Update the kwargs
|
||||
kwargs_passed_inpt_names = set()
|
||||
for inpt in self.inputs[len(args):]:
|
||||
inpt_name = inpt.name.split(':')[0]
|
||||
inpt_name = inpt_name.split('/')[-1]
|
||||
assert inpt_name not in kwargs_passed_inpt_names, \
|
||||
"this function has two arguments with the same name \"{}\", so kwargs cannot be used.".format(inpt_name)
|
||||
if inpt_name in kwargs:
|
||||
kwargs_passed_inpt_names.add(inpt_name)
|
||||
self._feed_input(feed_dict, inpt, kwargs.pop(inpt_name))
|
||||
else:
|
||||
assert inpt in self.givens, "Missing argument " + inpt_name
|
||||
assert len(kwargs) == 0, "Function got extra arguments " + str(list(kwargs.keys()))
|
||||
# Update feed dict with givens.
|
||||
for inpt in self.givens:
|
||||
feed_dict[inpt] = feed_dict.get(inpt, self.givens[inpt])
|
||||
results = get_session().run(self.outputs_update, feed_dict=feed_dict)[:-1]
|
||||
if self.check_nan:
|
||||
if any(np.isnan(r).any() for r in results):
|
||||
raise RuntimeError("Nan detected")
|
||||
return results
|
||||
|
||||
|
||||
def mem_friendly_function(nondata_inputs, data_inputs, outputs, batch_size):
|
||||
if isinstance(outputs, list):
|
||||
return _MemFriendlyFunction(nondata_inputs, data_inputs, outputs, batch_size)
|
||||
else:
|
||||
f = _MemFriendlyFunction(nondata_inputs, data_inputs, [outputs], batch_size)
|
||||
return lambda *inputs: f(*inputs)[0]
|
||||
|
||||
|
||||
class _MemFriendlyFunction(object):
|
||||
def __init__(self, nondata_inputs, data_inputs, outputs, batch_size):
|
||||
self.nondata_inputs = nondata_inputs
|
||||
self.data_inputs = data_inputs
|
||||
self.outputs = list(outputs)
|
||||
self.batch_size = batch_size
|
||||
|
||||
def __call__(self, *inputvals):
|
||||
assert len(inputvals) == len(self.nondata_inputs) + len(self.data_inputs)
|
||||
nondata_vals = inputvals[0:len(self.nondata_inputs)]
|
||||
data_vals = inputvals[len(self.nondata_inputs):]
|
||||
feed_dict = dict(zip(self.nondata_inputs, nondata_vals))
|
||||
n = data_vals[0].shape[0]
|
||||
for v in data_vals[1:]:
|
||||
assert v.shape[0] == n
|
||||
for i_start in range(0, n, self.batch_size):
|
||||
slice_vals = [v[i_start:builtins.min(i_start + self.batch_size, n)] for v in data_vals]
|
||||
for (var, val) in zip(self.data_inputs, slice_vals):
|
||||
feed_dict[var] = val
|
||||
results = tf.get_default_session().run(self.outputs, feed_dict=feed_dict)
|
||||
if i_start == 0:
|
||||
sum_results = results
|
||||
else:
|
||||
for i in range(len(results)):
|
||||
sum_results[i] = sum_results[i] + results[i]
|
||||
for i in range(len(results)):
|
||||
sum_results[i] = sum_results[i] / n
|
||||
return sum_results
|
||||
|
||||
# ================================================================
|
||||
# Modules
|
||||
# ================================================================
|
||||
|
||||
|
||||
class Module(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.first_time = True
|
||||
self.scope = None
|
||||
self.cache = {}
|
||||
|
||||
def __call__(self, *args):
|
||||
if args in self.cache:
|
||||
print("(%s) retrieving value from cache" % (self.name,))
|
||||
return self.cache[args]
|
||||
with tf.variable_scope(self.name, reuse=not self.first_time):
|
||||
scope = tf.get_variable_scope().name
|
||||
if self.first_time:
|
||||
self.scope = scope
|
||||
print("(%s) running function for the first time" % (self.name,))
|
||||
else:
|
||||
assert self.scope == scope, "Tried calling function with a different scope"
|
||||
print("(%s) running function on new inputs" % (self.name,))
|
||||
self.first_time = False
|
||||
out = self._call(*args)
|
||||
self.cache[args] = out
|
||||
return out
|
||||
|
||||
def _call(self, *args):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def trainable_variables(self):
|
||||
assert self.scope is not None, "need to call module once before getting variables"
|
||||
return tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, self.scope)
|
||||
|
||||
@property
|
||||
def variables(self):
|
||||
assert self.scope is not None, "need to call module once before getting variables"
|
||||
return tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, self.scope)
|
||||
|
||||
|
||||
def module(name):
|
||||
@functools.wraps
|
||||
def wrapper(f):
|
||||
class WrapperModule(Module):
|
||||
def _call(self, *args):
|
||||
return f(*args)
|
||||
return WrapperModule(name)
|
||||
return wrapper
|
||||
|
||||
# ================================================================
|
||||
# Graph traversal
|
||||
# ================================================================
|
||||
|
||||
|
||||
VARIABLES = {}
|
||||
|
||||
|
||||
def get_parents(node):
|
||||
return node.op.inputs
|
||||
|
||||
|
||||
def topsorted(outputs):
|
||||
"""
|
||||
Topological sort via non-recursive depth-first search
|
||||
"""
|
||||
assert isinstance(outputs, (list, tuple))
|
||||
marks = {}
|
||||
out = []
|
||||
stack = [] # pylint: disable=W0621
|
||||
# i: node
|
||||
# jidx = number of children visited so far from that node
|
||||
# marks: state of each node, which is one of
|
||||
# 0: haven't visited
|
||||
# 1: have visited, but not done visiting children
|
||||
# 2: done visiting children
|
||||
for x in outputs:
|
||||
stack.append((x, 0))
|
||||
while stack:
|
||||
(i, jidx) = stack.pop()
|
||||
if jidx == 0:
|
||||
m = marks.get(i, 0)
|
||||
if m == 0:
|
||||
marks[i] = 1
|
||||
elif m == 1:
|
||||
raise ValueError("not a dag")
|
||||
else:
|
||||
continue
|
||||
ps = get_parents(i)
|
||||
if jidx == len(ps):
|
||||
marks[i] = 2
|
||||
out.append(i)
|
||||
else:
|
||||
stack.append((i, jidx + 1))
|
||||
j = ps[jidx]
|
||||
stack.append((j, 0))
|
||||
return out
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Flat vectors
|
||||
# ================================================================
|
||||
|
||||
def var_shape(x):
|
||||
out = x.get_shape().as_list()
|
||||
assert all(isinstance(a, int) for a in out), \
|
||||
"shape function assumes that shape is fully known"
|
||||
return out
|
||||
|
||||
|
||||
def numel(x):
|
||||
return intprod(var_shape(x))
|
||||
|
||||
|
||||
def intprod(x):
|
||||
return int(np.prod(x))
|
||||
|
||||
|
||||
def flatgrad(loss, var_list, clip_norm=None):
|
||||
grads = tf.gradients(loss, var_list)
|
||||
if clip_norm is not None:
|
||||
grads = [tf.clip_by_norm(grad, clip_norm=clip_norm) for grad in grads]
|
||||
return tf.concat(axis=0, values=[
|
||||
tf.reshape(grad if grad is not None else tf.zeros_like(v), [numel(v)])
|
||||
for (v, grad) in zip(var_list, grads)
|
||||
])
|
||||
|
||||
|
||||
class SetFromFlat(object):
|
||||
def __init__(self, var_list, dtype=tf.float32):
|
||||
shapes = list(map(var_shape, var_list))
|
||||
total_size = np.sum([intprod(shape) for shape in shapes])
|
||||
|
||||
self.theta = theta = tf.placeholder(dtype, [total_size])
|
||||
start = 0
|
||||
assigns = []
|
||||
for (shape, v) in zip(shapes, var_list):
|
||||
size = intprod(shape)
|
||||
assigns.append(tf.assign(v, tf.reshape(theta[start:start + size], shape)))
|
||||
start += size
|
||||
self.op = tf.group(*assigns)
|
||||
|
||||
def __call__(self, theta):
|
||||
get_session().run(self.op, feed_dict={self.theta: theta})
|
||||
|
||||
|
||||
class GetFlat(object):
|
||||
def __init__(self, var_list):
|
||||
self.op = tf.concat(axis=0, values=[tf.reshape(v, [numel(v)]) for v in var_list])
|
||||
|
||||
def __call__(self):
|
||||
return get_session().run(self.op)
|
||||
|
||||
# ================================================================
|
||||
# Misc
|
||||
# ================================================================
|
||||
|
||||
|
||||
def fancy_slice_2d(X, inds0, inds1):
|
||||
"""
|
||||
like numpy X[inds0, inds1]
|
||||
XXX this implementation is bad
|
||||
"""
|
||||
inds0 = tf.cast(inds0, tf.int64)
|
||||
inds1 = tf.cast(inds1, tf.int64)
|
||||
shape = tf.cast(tf.shape(X), tf.int64)
|
||||
ncols = shape[1]
|
||||
Xflat = tf.reshape(X, [-1])
|
||||
return tf.gather(Xflat, inds0 * ncols + inds1)
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Scopes
|
||||
# ================================================================
|
||||
|
||||
|
||||
def scope_vars(scope, trainable_only=False):
|
||||
"""
|
||||
Get variables inside a scope
|
||||
The scope can be specified as a string
|
||||
|
||||
Parameters
|
||||
----------
|
||||
scope: str or VariableScope
|
||||
scope in which the variables reside.
|
||||
trainable_only: bool
|
||||
whether or not to return only the variables that were marked as trainable.
|
||||
|
||||
Returns
|
||||
-------
|
||||
vars: [tf.Variable]
|
||||
list of variables in `scope`.
|
||||
"""
|
||||
return tf.get_collection(
|
||||
tf.GraphKeys.TRAINABLE_VARIABLES if trainable_only else tf.GraphKeys.GLOBAL_VARIABLES,
|
||||
scope=scope if isinstance(scope, str) else scope.name
|
||||
)
|
||||
|
||||
|
||||
def scope_name():
|
||||
"""Returns the name of current scope as a string, e.g. deepq/q_func"""
|
||||
return tf.get_variable_scope().name
|
||||
|
||||
|
||||
def absolute_scope_name(relative_scope_name):
|
||||
"""Appends parent scope name to `relative_scope_name`"""
|
||||
return scope_name() + "/" + relative_scope_name
|
||||
|
||||
|
||||
def lengths_to_mask(lengths_b, max_length):
|
||||
"""
|
||||
Turns a vector of lengths into a boolean mask
|
||||
|
||||
Args:
|
||||
lengths_b: an integer vector of lengths
|
||||
max_length: maximum length to fill the mask
|
||||
|
||||
Returns:
|
||||
a boolean array of shape (batch_size, max_length)
|
||||
row[i] consists of True repeated lengths_b[i] times, followed by False
|
||||
"""
|
||||
lengths_b = tf.convert_to_tensor(lengths_b)
|
||||
assert lengths_b.get_shape().ndims == 1
|
||||
mask_bt = tf.expand_dims(tf.range(max_length), 0) < tf.expand_dims(lengths_b, 1)
|
||||
return mask_bt
|
||||
|
||||
|
||||
def in_session(f):
|
||||
@functools.wraps(f)
|
||||
def newfunc(*args, **kwargs):
|
||||
with tf.Session():
|
||||
f(*args, **kwargs)
|
||||
return newfunc
|
||||
|
||||
|
||||
_PLACEHOLDER_CACHE = {} # name -> (placeholder, dtype, shape)
|
||||
|
||||
|
||||
def get_placeholder(name, dtype, shape):
|
||||
if name in _PLACEHOLDER_CACHE:
|
||||
out, dtype1, shape1 = _PLACEHOLDER_CACHE[name]
|
||||
assert dtype1 == dtype and shape1 == shape
|
||||
return out
|
||||
else:
|
||||
out = tf.placeholder(dtype=dtype, shape=shape, name=name)
|
||||
_PLACEHOLDER_CACHE[name] = (out, dtype, shape)
|
||||
return out
|
||||
|
||||
|
||||
def get_placeholder_cached(name):
|
||||
return _PLACEHOLDER_CACHE[name][0]
|
||||
|
||||
|
||||
def flattenallbut0(x):
|
||||
return tf.reshape(x, [-1, intprod(x.get_shape().as_list()[1:])])
|
||||
|
||||
|
||||
def reset():
|
||||
global _PLACEHOLDER_CACHE
|
||||
global VARIABLES
|
||||
_PLACEHOLDER_CACHE = {}
|
||||
VARIABLES = {}
|
||||
tf.reset_default_graph()
|
||||
@@ -0,0 +1,19 @@
|
||||
class VecEnv(object):
|
||||
"""
|
||||
Vectorized environment base class
|
||||
"""
|
||||
def step(self, vac):
|
||||
"""
|
||||
Apply sequence of actions to sequence of environments
|
||||
actions -> (observations, rewards, news)
|
||||
|
||||
where 'news' is a boolean vector indicating whether each element is new.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
def reset(self):
|
||||
"""
|
||||
Reset all environments
|
||||
"""
|
||||
raise NotImplementedError
|
||||
def close(self):
|
||||
pass
|
||||
@@ -0,0 +1,79 @@
|
||||
import numpy as np
|
||||
from multiprocessing import Process, Pipe
|
||||
from src.common.vec_env import VecEnv
|
||||
|
||||
|
||||
def worker(remote, env_fn_wrapper):
|
||||
env = env_fn_wrapper.x()
|
||||
while True:
|
||||
cmd, data = remote.recv()
|
||||
if cmd == 'step':
|
||||
ob, reward, done, info = env.step(data)
|
||||
if done:
|
||||
ob = env.reset()
|
||||
remote.send((ob, reward, done, info))
|
||||
elif cmd == 'reset':
|
||||
ob = env.reset()
|
||||
remote.send(ob)
|
||||
elif cmd == 'close':
|
||||
remote.close()
|
||||
break
|
||||
elif cmd == 'get_spaces':
|
||||
remote.send((env.action_space, env.observation_space))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class CloudpickleWrapper(object):
|
||||
"""
|
||||
Uses cloudpickle to serialize contents (otherwise multiprocessing tries to use pickle)
|
||||
"""
|
||||
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
def __getstate__(self):
|
||||
import cloudpickle
|
||||
return cloudpickle.dumps(self.x)
|
||||
|
||||
def __setstate__(self, ob):
|
||||
import pickle
|
||||
self.x = pickle.loads(ob)
|
||||
|
||||
|
||||
class SubprocVecEnv(VecEnv):
|
||||
def __init__(self, env_fns):
|
||||
"""
|
||||
envs: list of gym environments to run in subprocesses
|
||||
"""
|
||||
nenvs = len(env_fns)
|
||||
self.remotes, self.work_remotes = zip(*[Pipe() for _ in range(nenvs)])
|
||||
self.ps = [Process(target=worker, args=(work_remote, CloudpickleWrapper(env_fn)))
|
||||
for (work_remote, env_fn) in zip(self.work_remotes, env_fns)]
|
||||
for p in self.ps:
|
||||
p.start()
|
||||
|
||||
self.remotes[0].send(('get_spaces', None))
|
||||
self.action_space, self.observation_space = self.remotes[0].recv()
|
||||
|
||||
def step(self, actions):
|
||||
for remote, action in zip(self.remotes, actions):
|
||||
remote.send(('step', action))
|
||||
results = [remote.recv() for remote in self.remotes]
|
||||
obs, rews, dones, infos = zip(*results)
|
||||
return np.stack(obs), np.stack(rews), np.stack(dones), infos
|
||||
|
||||
def reset(self):
|
||||
for remote in self.remotes:
|
||||
remote.send(('reset', None))
|
||||
return np.stack([remote.recv() for remote in self.remotes])
|
||||
|
||||
def close(self):
|
||||
for remote in self.remotes:
|
||||
remote.send(('close', None))
|
||||
for p in self.ps:
|
||||
p.join()
|
||||
|
||||
@property
|
||||
def num_envs(self):
|
||||
return len(self.remotes)
|
||||
Reference in New Issue
Block a user