def translate_space(space): """ Translates an openAI space into an RLGraph Space object. Args: space (gym.spaces.Space): The openAI Space to be translated. Returns: Space: The translated Rlgraph Space. """ if isinstance(space, gym.spaces.Discrete): if space.n == 2: return BoolBox() else: return IntBox(space.n) elif isinstance(space, gym.spaces.MultiBinary): return BoolBox(shape=(space.n, )) elif isinstance(space, gym.spaces.MultiDiscrete): return IntBox(low=np.zeros((space.nvec.ndim, ), dtype_("uint8", "np")), high=space.nvec) elif isinstance(space, gym.spaces.Box): return FloatBox(low=space.low, high=space.high) elif isinstance(space, gym.spaces.Tuple): return Tuple( *[OpenAIGymEnv.translate_space(s) for s in space.spaces]) elif isinstance(space, gym.spaces.Dict): return Dict({ k: OpenAIGymEnv.translate_space(v) for k, v in space.spaces.items() }) else: raise RLGraphError( "Unknown openAI gym Space class for state_space!")
def __init__(self, low=None, high=None, shape=None, dtype="int32", **kwargs): """ Three kinds of valid input: IntBox(6) # only high is given -> low assumed to be 0 (0D scalar). IntBox(0, 2) # low and high are given as scalars and shape is assumed to be 0D scalar. IntBox(-1, 1, (3,4)) # low and high are scalars, and shape is provided. IntBox(np.array([-1,-2]), np.array([2,4])) # low and high are arrays of the same shape (no shape given!) NOTE: The `high` value for IntBoxes is excluded. Valid values thus are from the interval: [low,high[ """ if low is None: assert high is None, "ERROR: If `low` is None, `high` must be None as well!" low = -LARGE_INTEGER high = LARGE_INTEGER # support calls like (IntBox(5) -> low=0, high=5) elif high is None: high = low low = 0 dtype = dtype_(dtype, "np") assert dtype in [np.int16, np.int32, np.int64, np.uint8], \ "ERROR: IntBox does not allow dtype '{}'!".format(dtype) super(IntBox, self).__init__(low=low, high=high, shape=shape, dtype=dtype, **kwargs) self.num_categories = None if self.global_bounds is False else self.global_bounds[1]
def create_variables(self, input_spaces, action_space=None): # Overwrite parent's method as we don't need a custom registry. if self.record_space is None: self.record_space = input_spaces["records"] # Make sure all input-records have a batch rank and determine the shapes and dtypes. shapes = [] dtypes = [] names = [] for key, value in self.record_space.flatten().items(): # TODO: what if single items come in without a time-rank? Then this check here will fail. # We are expecting single items. The incoming batch-rank is actually a time-rank: Add the batch rank. sanity_check_space( value, must_have_batch_rank=self.only_insert_single_records is False) shape = value.get_shape(with_time_rank=value.has_time_rank) shapes.append(shape) dtypes.append(dtype_(value.dtype)) names.append(key) # Construct the wrapped FIFOQueue object. if get_backend() == "tf": if self.reuse_variable_scope: shared_name = self.reuse_variable_scope + ("/" + self.scope if self.scope else "") else: shared_name = self.global_scope self.queue = tf.FIFOQueue(capacity=self.capacity, dtypes=dtypes, shapes=shapes, names=names, shared_name=shared_name)
def _graph_fn_apply(self, text_inputs): """ Args: text_inputs (SingleDataOp): The Text input to generate a hash bucket for. Returns: tuple: - SingleDataOp: The hash lookup table (int64) that can be used as input to embedding-lookups. - SingleDataOp: The length (number of words) of the longest string in the `text_input` batch. """ if get_backend() == "tf": # Split the input string. split_text_inputs = tf.string_split(source=text_inputs, delimiter=self.delimiter) # Build a tensor of n rows (number of items in text_inputs) words with dense = tf.sparse_tensor_to_dense(sp_input=split_text_inputs, default_value="") length = tf.reduce_sum(input_tensor=tf.to_int32(x=tf.not_equal(x=dense, y="")), axis=-1) if self.hash_function == "fast": hash_bucket = tf.string_to_hash_bucket_fast(input=dense, num_buckets=self.num_hash_buckets) else: hash_bucket = tf.string_to_hash_bucket_strong(input=dense, num_buckets=self.num_hash_buckets, key=self.hash_keys) # Int64 is tf's default for `string_to_hash_bucket` operation: Can leave as is. if self.dtype != "int64": hash_bucket = tf.cast(x=hash_bucket, dtype=dtype_(self.dtype)) # Hash-bucket output is always batch-major. hash_bucket._batch_rank = 0 hash_bucket._time_rank = 1 return hash_bucket, length
def __init__(self, low=None, high=None, shape=None, dtype="float32", **kwargs): if low is None: assert high is None, "ERROR: If `low` is None, `high` must be None as well!" low = float("-inf") high = float("inf") self.unbounded = True else: self.unbounded = False # support calls like (FloatBox(1.0) -> low=0.0, high=1.0) if high is None: high = low low = 0.0 dtype = dtype_(dtype, "np") assert dtype in [ np.float16, np.float32, np.float64 ], "ERROR: FloatBox does not allow dtype '{}'!".format(dtype) super(FloatBox, self).__init__(low=low, high=high, shape=shape, dtype=dtype, **kwargs)
def create_variables(self, input_spaces, action_space=None): # Store the original structure for later recovery. dtypes = list() shapes = list() idx = 0 while True: key = "inputs[{}]".format(idx) if key not in input_spaces: break flat_keys = list() for flat_key, flat_space in input_spaces[key].flatten().items(): dtypes.append(dtype_(flat_space.dtype)) shapes.append( flat_space.get_shape(with_batch_rank=True, with_time_rank=True)) flat_keys.append(flat_key) self.flat_keys.append(flat_keys) idx += 1 if get_backend() == "tf": self.area = tf.contrib.staging.StagingArea(dtypes, shapes)
def _read_records(self, indices): """ Obtains record values for the provided indices. Args: indices ndarray: Indices to read. Assumed to be not contiguous. Returns: dict: Record value dict. """ records = {} for name in self.record_space_flat.keys(): records[name] = [] if self.size > 0: for index in indices: record = self.memory_values[index] for name in self.record_space_flat.keys(): records[name].append(record[name]) else: # TODO figure out how to do default handling in pytorch builds. # Fill with default vals for build. for name in self.record_space_flat.keys(): if get_backend() == "pytorch": records[name] = torch.zeros( self.record_space_flat[name].shape, dtype=dtype_(self.record_space_flat[name].dtype, "pytorch")) else: records[name] = np.zeros( self.record_space_flat[name].shape) # Convert if necessary: list of tensors fails at space inference otherwise. if get_backend() == "pytorch": for name in self.record_space_flat.keys(): records[name] = torch.squeeze(torch.stack(records[name])) return records
def _graph_fn_call(self, inputs): """ Gray-scales images of arbitrary rank. Normally, the images' rank is 3 (width/height/colors), but can also be: batch/width/height/colors, or any other. However, the last rank must be of size: len(self.weights). Args: inputs (tensor): Single image or a batch of images to be gray-scaled (last rank=n colors, where n=len(self.weights)). Returns: DataOp: The op for processing the images. """ # The reshaped weights used for the grayscale operation. if isinstance(inputs, list): inputs = np.asarray(inputs) images_shape = get_shape(inputs) assert images_shape[-1] == self.last_rank,\ "ERROR: Given image's shape ({}) does not match number of weights (last rank must be {})!".\ format(images_shape, self.last_rank) if self.backend == "python" or get_backend() == "python": if inputs.ndim == 4: grayscaled = [] for i in range_(len(inputs)): scaled = cv2.cvtColor(inputs[i], cv2.COLOR_RGB2GRAY) grayscaled.append(scaled) scaled_images = np.asarray(grayscaled) # Keep last dim. if self.keep_rank: scaled_images = scaled_images[:, :, :, np.newaxis] else: # Sample by sample. scaled_images = cv2.cvtColor(inputs, cv2.COLOR_RGB2GRAY) return scaled_images elif get_backend() == "pytorch": if len(inputs.shape) == 4: grayscaled = [] for i in range_(len(inputs)): scaled = cv2.cvtColor(inputs[i].numpy(), cv2.COLOR_RGB2GRAY) grayscaled.append(scaled) scaled_images = np.asarray(grayscaled) # Keep last dim. if self.keep_rank: scaled_images = scaled_images[:, :, :, np.newaxis] else: # Sample by sample. scaled_images = cv2.cvtColor(inputs.numpy(), cv2.COLOR_RGB2GRAY) return torch.tensor(scaled_images) elif get_backend() == "tf": weights_reshaped = np.reshape( self.weights, newshape=tuple([1] * (get_rank(inputs) - 1)) + (self.last_rank, )) # Do we need to convert? # The dangerous thing is that multiplying an int tensor (image) with float weights results in an all # 0 tensor). if "int" in str(dtype_(inputs.dtype)): weighted = weights_reshaped * tf.cast(inputs, dtype=dtype_("float")) else: weighted = weights_reshaped * inputs reduced = tf.reduce_sum(weighted, axis=-1, keepdims=self.keep_rank) # Cast back to original dtype. if "int" in str(dtype_(inputs.dtype)): reduced = tf.cast(reduced, dtype=inputs.dtype) return reduced