def test_parse_discrete_space(self, ffnn_config): input_space = spaces.Discrete(n=self.input_n) output_space = spaces.Discrete(n=self.output_n) dummy_individual = np.zeros((FeedForwardNumPy.get_individual_size( ffnn_config, input_space=input_space, output_space=output_space))) brain = FeedForwardPyTorch(input_space=input_space, output_space=output_space, individual=dummy_individual, config=ffnn_config) # Test input space sample_input = 1 assert input_space.contains(sample_input) parsed_input = brain.parse_brain_input(sample_input) # Parsed Input should be one-hot encoded assert len(parsed_input.shape) == 1 assert len(parsed_input) == input_space.n assert all(x == 0 for x in parsed_input[:sample_input]) assert parsed_input[sample_input] == 1 assert all(x == 0 for x in parsed_input[sample_input + 1:]) # Test output space brain_output = brain.step(sample_input) # This should be an Integer in [0, output_space.n - 1] assert isinstance(brain_output, np.integer) assert 0 <= brain_output < output_space.n
def test_parse_tuple_space(self, ffnn_config): # Prepare some spaces to test the Tuple space one_dimensional_box = spaces.Box(low=self.low, high=self.high, shape=self.one_dimensional_shape) multi_dimensional_box = spaces.Box(low=self.low, high=self.high, shape=self.multi_dimensional_shape) first_discrete = spaces.Discrete(n=self.input_n) second_discrete = spaces.Discrete(n=self.output_n) input_space = spaces.Tuple([one_dimensional_box, first_discrete, multi_dimensional_box, second_discrete]) output_space = spaces.Tuple([second_discrete, multi_dimensional_box, one_dimensional_box, first_discrete]) dummy_individual = np.zeros((FeedForwardNumPy.get_individual_size( ffnn_config, input_space=input_space, output_space=output_space))) brain = FeedForwardPyTorch(input_space=input_space, output_space=output_space, individual=dummy_individual, config=ffnn_config) # Test input space sample_input = (np.ones(self.one_dimensional_shape), 1, np.ones(self.multi_dimensional_shape) * 2, 2) assert input_space.contains(sample_input) parsed_input = brain.parse_brain_input(sample_input) assert len(parsed_input.shape) == 1 assert spaces.flatdim(input_space) == len(parsed_input) # Test output space brain_output = brain.step(sample_input) # 4 nested spaces inside the input space assert len(brain_output) == 4 # First nested space is second_discrete nested_output = brain_output[0] assert isinstance(nested_output, np.integer) # Second nested space is multi_dimensional_box nested_output = brain_output[1] assert len(nested_output.shape) == len(self.multi_dimensional_shape) assert all([x == y for (x, y) in zip(nested_output.shape, self.multi_dimensional_shape)]) # Third nested space is one_dimensional_box nested_output = brain_output[2] assert len(nested_output.shape) == len(self.one_dimensional_shape) assert all([x == y for (x, y) in zip(nested_output.shape, self.one_dimensional_shape)]) # Fourth nested space is first_discrete nested_output = brain_output[3] assert isinstance(nested_output, np.integer)
def test_parse_rgb_input(self, ffnn_config): # Environments with RGB observations usually use spaces.Box as their observation space, which means it is # the input space for the brains input_space = spaces.Box(low=self.low, high=self.high, shape=self.rgb_shape) output_space = spaces.Box(low=self.low, high=self.high, shape=self.one_dimensional_shape) # First test the input space # Not used but needed to create a Brain dummy_individual = np.zeros((FeedForwardNumPy.get_individual_size( ffnn_config, input_space=input_space, output_space=output_space))) brain = FeedForwardPyTorch(input_space=input_space, output_space=output_space, individual=dummy_individual, config=ffnn_config) sample_input = np.ones(input_space.shape) assert input_space.contains(sample_input) # Assume now we have RGB input space, in our flatten function this should return a multidimensional NumPy array parsed_input = brain.parse_brain_input(sample_input) # When input_space == spaces.Box the input is not transformed assert np.array_equal(sample_input, parsed_input) assert (isinstance(parsed_input, np.ndarray) and len( parsed_input.shape) == 3 and parsed_input.shape == sample_input.shape)
def test_parse_box_space(self, ffnn_config): input_space = spaces.Box(low=self.low, high=self.high, shape=self.one_dimensional_shape) output_space = spaces.Box(low=self.low, high=self.high, shape=self.multi_dimensional_shape) # First test the input space # Not used but needed to create a Brain dummy_individual = np.zeros((FeedForwardNumPy.get_individual_size( ffnn_config, input_space=input_space, output_space=output_space))) brain = FeedForwardPyTorch(input_space=input_space, output_space=output_space, individual=dummy_individual, config=ffnn_config) sample_input = np.ones(input_space.shape) assert input_space.contains(sample_input) parsed_input = brain.parse_brain_input(sample_input) # When input_space == spaces.Box the input is not transformed assert np.array_equal(sample_input, parsed_input) # Now test the output space brain_output = brain.step(sample_input) assert brain_output.shape == output_space.shape
def test_lstm_output(self, ffnn_config: FeedForwardCfg): input_size = 28 output_size = 8 number_of_inputs = 10000 input_space = gym.spaces.Box(-1, 1, (input_size, )) output_space = gym.spaces.Box(-1, 1, (output_size, )) individual_size = FeedForwardPyTorch.get_individual_size( ffnn_config, input_space, output_space) # Make two copies to avoid possible errors due to PyTorch reusing data from the same memory address individual_pytorch = np.random.randn(individual_size).astype( np.float32) individual_numpy = np.copy(individual_pytorch) with torch.no_grad(): ffnn_pytorch = FeedForwardPyTorch(input_space, output_space, individual_pytorch, ffnn_config) ffnn_numpy = FeedForwardNumPy(input_space, output_space, individual_numpy, ffnn_config) with torch.no_grad(): # Hidden layers are in a stacked list, so unpack first hidden_layers = ffnn_config.hidden_layers[0] current_input = input_size for i, hidden_layer in enumerate(hidden_layers): weight_array_pytorch: np.ndarray = ffnn_pytorch.layers[ i].weight.data.numpy() weight_array_numpy: np.ndarray = ffnn_numpy.weights[i] assert np.array_equal(weight_array_pytorch, weight_array_numpy) assert weight_array_pytorch.shape == (hidden_layer, current_input) if ffnn_config.use_bias: bias_array_pytorch: np.ndarray = ffnn_pytorch.layers[ i].bias.data.numpy() bias_array_numpy: np.ndarray = ffnn_numpy.bias[i] assert np.array_equal(bias_array_pytorch, bias_array_numpy) assert bias_array_pytorch.shape == (hidden_layer, ) current_input = hidden_layer assert weight_array_pytorch.shape == (current_input, output_size) inputs = np.random.randn(number_of_inputs, input_size).astype(np.float32) ffnn_pytorch_outputs = [] ffnn_numpy_outputs = [] ffnn_pytorch_times = [] ffnn_numpy_times = [] # Collect predictions of PyTorch and NumPy implementations and collect time data for i in inputs: with torch.no_grad(): time_s = time.time() ffnn_pytorch_output = ffnn_pytorch.step(i) ffnn_pytorch_times.append(time.time() - time_s) ffnn_pytorch_outputs.append(ffnn_pytorch_output) time_s = time.time() ffnn_numpy_output = ffnn_numpy.step(i) ffnn_numpy_times.append(time.time() - time_s) ffnn_numpy_outputs.append(ffnn_numpy_output) ffnn_pytorch_outputs = np.array(ffnn_pytorch_outputs) ffnn_numpy_outputs = np.array(ffnn_numpy_outputs) ffnn_pytorch_times = np.array(ffnn_pytorch_times) ffnn_numpy_times = np.array(ffnn_numpy_times) assert len(ffnn_pytorch_outputs) == len(ffnn_numpy_outputs) assert ffnn_pytorch_outputs.size == ffnn_numpy_outputs.size print( "\nPyTorch Mean Prediction Time {}s | NumPy Mean Prediction Time {}s" .format(np.mean(ffnn_pytorch_times), np.mean(ffnn_numpy_times))) print( "PyTorch Stddev Prediction Time {}s | NumPy Stddev Prediction Time {}s" .format(np.std(ffnn_pytorch_times), np.std(ffnn_numpy_times))) print( "PyTorch Max Prediction Time {}s | NumPy Max Prediction Time {}s". format(np.max(ffnn_pytorch_times), np.max(ffnn_numpy_times))) print( "PyTorch Min Prediction Time {}s | NumPy Min Prediction Time {}s". format(np.min(ffnn_pytorch_times), np.min(ffnn_numpy_times))) # Use percentage instead of np.allclose() because some results exceed the rtol value, but it is a low percentage close_percentage = np.count_nonzero( np.isclose(ffnn_pytorch_outputs, ffnn_numpy_outputs)) / ffnn_pytorch_outputs.size assert close_percentage > 0.98 print( "Equal predictions between PyTorch and NumPy", "Implementation of FFNN: {}% of {} predictions".format( close_percentage * 100, number_of_inputs))
def test_ffnn_predict_speedtest(self): def predict_with_maximum_speed(u, A, B, C, D, E, a, b, c, d, e): x = np.matmul(A, u) x = np.add(x, a) x = np.maximum(0, x) x = np.matmul(B, x) x = np.add(x, b) x = np.maximum(0, x) x = np.matmul(C, x) x = np.add(x, c) x = np.maximum(0, x) x = np.matmul(D, x) x = np.add(x, d) x = np.maximum(0, x) x = np.matmul(E, x) x = np.add(x, e) return x input_size = 6 output_size = 1 number_of_inputs = 3000000 input_space = gym.spaces.Box(-1, 1, (input_size, )) output_space = gym.spaces.Box(-1, 1, (output_size, )) ffnn_config = FeedForwardCfg(type="FeedForward_Numpy", use_bias=True, hidden_layers=[[16, 32, 16, 8]], neuron_activation="relu", neuron_activation_output="linear") individual_size = FeedForwardPyTorch.get_individual_size( ffnn_config, input_space, output_space) individual = np.random.rand(individual_size).astype(np.float32) ffnn_numpy = FeedForwardNumPy(input_space, output_space, individual, ffnn_config) # Weight Matrizes A = ffnn_numpy.weights_hidden_layers[0].copy() B = ffnn_numpy.weights_hidden_layers[1].copy() C = ffnn_numpy.weights_hidden_layers[2].copy() D = ffnn_numpy.weights_hidden_layers[3].copy() E = ffnn_numpy.weights_output_layer.copy() # Biases a = ffnn_numpy.biases_hidden_layers[0].copy() b = ffnn_numpy.biases_hidden_layers[1].copy() c = ffnn_numpy.biases_hidden_layers[2].copy() d = ffnn_numpy.biases_hidden_layers[3].copy() e = ffnn_numpy.biases_output_layer.copy() inputs = np.random.rand(number_of_inputs, input_size, 1).astype(np.float32) t_start = time.time() output_ffnn_class = ffnn_numpy.predict(inputs) time_ffnn_class = time.time() - t_start print(time_ffnn_class) t_start = time.time() output_maximum_speed = predict_with_maximum_speed( inputs, A, B, C, D, E, a, b, c, d, e) time_maximum_speed = time.time() - t_start print(time_maximum_speed) relative_speed_tolerance = time_ffnn_class / time_maximum_speed - 1 print(relative_speed_tolerance) print( np.count_nonzero( np.isclose(output_ffnn_class, output_maximum_speed))) assert number_of_inputs == np.count_nonzero( np.isclose(output_ffnn_class, output_maximum_speed))