Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/agents/tutorials/bandits_tutorial.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Tutorial sobre Multi-Armed Bandits no TF-Agents

Configuração

Se você ainda não instalou as seguintes dependências, execute:

!pip install tf-agents

Importação

import abc import numpy as np import tensorflow as tf from tf_agents.agents import tf_agent from tf_agents.drivers import driver from tf_agents.environments import py_environment from tf_agents.environments import tf_environment from tf_agents.environments import tf_py_environment from tf_agents.policies import tf_policy from tf_agents.specs import array_spec from tf_agents.specs import tensor_spec from tf_agents.trajectories import time_step as ts from tf_agents.trajectories import trajectory from tf_agents.trajectories import policy_step nest = tf.nest

Introdução

O problema Multi-Armed Bandit (MAB) é um caso especial de aprendizado por reforço: um agente coleta recompensas em um ambiente ao pegar algumas ações após observar algum estado do ambiente. A principal diferença entre o RL geral e o MAB é que, no MAB, presumimos que a ação tomada pelo agente não influencia o próximo estado do ambiente. Por isso, os agentes não modelam transições de estado, creditam recompensas a ações anteriores ou "planejam antes" para chegar a estados ricos de recompensas.

Como em outros domínios de RL, o objetivo de um agente de MAB é encontrar uma política que colete o máximo de recompensas possível. Seria um erro, no entanto, sempre tentar explorar a ação que promete a recompensa mais alta, porque então há uma chance de perder ações melhores se não explorarmos o suficiente. Esse é o principal problema a ser resolvido em (MAB), geralmente chamado de dilema exploration-exploitation.

Os ambientes bandit, políticas e agentes para MAB podem ser encontrados em subdiretórios de tf_agents/bandits.

Ambientes

No TF-Agents, a classe de ambiente serve a função de dar informações sobre o estado atual (isso é chamado de observação ou contexto), recebendo uma ação como entrada, realizando uma transição de estado e gerando uma recompensa. Essa classe também lida com a redefinição quando um episódio termina, para um novo episódio começar. Isso é realizado com a chamada de uma função reset quando um estado é rotulado como o "último" do episódio.

Para mais detalhes, confira o tutorial de ambientes do TF-Agents.

Conforme mencionado acima, o MAB difere do RL geral porque as ações não influenciam a próxima observação. Outra diferença é que, nos Bandits, não há "episódios": todo timestep começa com uma nova observação, independentemente dos timesteps anteriores.

Para conferir se as observações são independentes e abstrair o conceito de episódios do RL, apresentamos subclasses de PyEnvironment e TFEnvironment: BanditPyEnvironment e BanditTFEnvironment. Essas classes expõem duas funções de membros privadas que permanecem para serem implementadas pelo usuário:

@abc.abstractmethod def _observe(self):

e

@abc.abstractmethod def _apply_action(self, action):

A função _observe retorna uma observação. Em seguida, a política escolhe uma ação com base nessa observação. A _apply_action recebe essa ação como entrada e retorna a recompensa correspondente. Essas funções de membros privadas são chamadas pelas funções reset e step, respectivamente.

class BanditPyEnvironment(py_environment.PyEnvironment): def __init__(self, observation_spec, action_spec): self._observation_spec = observation_spec self._action_spec = action_spec super(BanditPyEnvironment, self).__init__() # Helper functions. def action_spec(self): return self._action_spec def observation_spec(self): return self._observation_spec def _empty_observation(self): return tf.nest.map_structure(lambda x: np.zeros(x.shape, x.dtype), self.observation_spec()) # These two functions below should not be overridden by subclasses. def _reset(self): """Returns a time step containing an observation.""" return ts.restart(self._observe(), batch_size=self.batch_size) def _step(self, action): """Returns a time step containing the reward for the action taken.""" reward = self._apply_action(action) return ts.termination(self._observe(), reward) # These two functions below are to be implemented in subclasses. @abc.abstractmethod def _observe(self): """Returns an observation.""" @abc.abstractmethod def _apply_action(self, action): """Applies `action` to the Environment and returns the corresponding reward. """

A classe abstrata ínterim acima implementa as funções _reset e _step de PyEnvironment e expõe as funções abstratas _observe e _apply_action a serem implementadas pelas subclasses.

Um exemplo simples de classe de ambiente

A seguinte classe fornece um ambiente bastante simples para o qual a observação é um número inteiro aleatório entre -2 e 2, há 3 ações possíveis (0, 1, 2) e a recompensa é o produto da ação e da observação.

class SimplePyEnvironment(BanditPyEnvironment): def __init__(self): action_spec = array_spec.BoundedArraySpec( shape=(), dtype=np.int32, minimum=0, maximum=2, name='action') observation_spec = array_spec.BoundedArraySpec( shape=(1,), dtype=np.int32, minimum=-2, maximum=2, name='observation') super(SimplePyEnvironment, self).__init__(observation_spec, action_spec) def _observe(self): self._observation = np.random.randint(-2, 3, (1,), dtype='int32') return self._observation def _apply_action(self, action): return action * self._observation

Agora podemos usar esse ambiente para obter observações e receber recompensas para nossas ações.

environment = SimplePyEnvironment() observation = environment.reset().observation print("observation: %d" % observation) action = 2 #@param print("action: %d" % action) reward = environment.step(action).reward print("reward: %f" % reward)

Ambientes TF

É possível definir um ambiente de bandit ao criar subclasses de BanditTFEnvironment ou, de maneira semelhante a ambientes de RL, é possível definir um BanditPyEnvironment e o envolver com TFPyEnvironment. Para fins de simplicidade, vamos escolher a última opção neste tutorial.

tf_environment = tf_py_environment.TFPyEnvironment(environment)

Políticas

Uma política em um problema de bandit funciona da mesma maneira que um problema de RL: fornece uma ação (ou distribuição de ações), a partir de uma observação como entrada.

Para mais detalhes, confira o tutorial de política do TF-Agents.

Assim como ambientes, há duas maneiras de construir uma política: é possível criar uma PyPolicy e a envolver com uma TFPyPolicy ou criar diretamente uma TFPolicy. Aqui decidimos usar o método direto.

Como esse exemplo é bastante simples, podemos definir a política ideal manualmente. A ação só depende do sinal da observação, 0 quando é negativo e 2 quando é positivo.

class SignPolicy(tf_policy.TFPolicy): def __init__(self): observation_spec = tensor_spec.BoundedTensorSpec( shape=(1,), dtype=tf.int32, minimum=-2, maximum=2) time_step_spec = ts.time_step_spec(observation_spec) action_spec = tensor_spec.BoundedTensorSpec( shape=(), dtype=tf.int32, minimum=0, maximum=2) super(SignPolicy, self).__init__(time_step_spec=time_step_spec, action_spec=action_spec) def _distribution(self, time_step): pass def _variables(self): return () def _action(self, time_step, policy_state, seed): observation_sign = tf.cast(tf.sign(time_step.observation[0]), dtype=tf.int32) action = observation_sign + 1 return policy_step.PolicyStep(action, policy_state)

Agora podemos solicitar uma observação do ambiente, chamar a política para escolher uma ação, e o ambiente gerará a recompensa:

sign_policy = SignPolicy() current_time_step = tf_environment.reset() print('Observation:') print (current_time_step.observation) action = sign_policy.action(current_time_step).action print('Action:') print (action) reward = tf_environment.step(action).reward print('Reward:') print(reward)

A maneira que os ambientes de bandit são implementados garante que, a cada passo realizado, seja recebida a recompensa para a ação tomada e também a próxima observação.

step = tf_environment.reset() action = 1 next_step = tf_environment.step(action) reward = next_step.reward next_observation = next_step.observation print("Reward: ") print(reward) print("Next observation:") print(next_observation)

Agentes

Agora que temos os ambientes e as políticas de bandit, é hora de definir também os agentes de bandit, que lidam com a mudança da política com base nas amostras de treinamento.

A API para agentes de bandit não são diferentes dos agentes de RL: o agente só precisa implementar os métodos _initialize e _train e definir uma policy e collect_policy.

Um ambiente mais complicado

Antes de escrever nosso agente de bandit, precisamos ter um ambiente um pouco mais difícil de entender. Para deixar as coisas mais animadas, o próximo ambiente sempre fornecerá reward = observation * action ou reward = -observation * action. Isso será decidido quando o ambiente for inicializado.

class TwoWayPyEnvironment(BanditPyEnvironment): def __init__(self): action_spec = array_spec.BoundedArraySpec( shape=(), dtype=np.int32, minimum=0, maximum=2, name='action') observation_spec = array_spec.BoundedArraySpec( shape=(1,), dtype=np.int32, minimum=-2, maximum=2, name='observation') # Flipping the sign with probability 1/2. self._reward_sign = 2 * np.random.randint(2) - 1 print("reward sign:") print(self._reward_sign) super(TwoWayPyEnvironment, self).__init__(observation_spec, action_spec) def _observe(self): self._observation = np.random.randint(-2, 3, (1,), dtype='int32') return self._observation def _apply_action(self, action): return self._reward_sign * action * self._observation[0] two_way_tf_environment = tf_py_environment.TFPyEnvironment(TwoWayPyEnvironment())

Uma política mais complicada

Um ambiente mais complicado pede uma política mais complicada. Precisamos de uma política que detecte o comportamento do ambiente subjacente. Há três situações com que a política precisa lidar:

  1. O agente ainda não detectou a versão em execução do ambiente.

  2. O agente detectou que a versão original do ambiente está em execução.

  3. O agente detectou que a versão invertida do ambiente está em execução.

Definimos uma tf_variable chamada _situation para armazenar essa informações codificadas como valores em [0, 2] e fazer a política se comportar de maneira adequada.

class TwoWaySignPolicy(tf_policy.TFPolicy): def __init__(self, situation): observation_spec = tensor_spec.BoundedTensorSpec( shape=(1,), dtype=tf.int32, minimum=-2, maximum=2) action_spec = tensor_spec.BoundedTensorSpec( shape=(), dtype=tf.int32, minimum=0, maximum=2) time_step_spec = ts.time_step_spec(observation_spec) self._situation = situation super(TwoWaySignPolicy, self).__init__(time_step_spec=time_step_spec, action_spec=action_spec) def _distribution(self, time_step): pass def _variables(self): return [self._situation] def _action(self, time_step, policy_state, seed): sign = tf.cast(tf.sign(time_step.observation[0, 0]), dtype=tf.int32) def case_unknown_fn(): # Choose 1 so that we get information on the sign. return tf.constant(1, shape=(1,)) # Choose 0 or 2, depending on the situation and the sign of the observation. def case_normal_fn(): return tf.constant(sign + 1, shape=(1,)) def case_flipped_fn(): return tf.constant(1 - sign, shape=(1,)) cases = [(tf.equal(self._situation, 0), case_unknown_fn), (tf.equal(self._situation, 1), case_normal_fn), (tf.equal(self._situation, 2), case_flipped_fn)] action = tf.case(cases, exclusive=True) return policy_step.PolicyStep(action, policy_state)

Agente

Agora é hora de definir o agente que detecta o sinal do ambiente e define a política de maneira apropriada.

class SignAgent(tf_agent.TFAgent): def __init__(self): self._situation = tf.Variable(0, dtype=tf.int32) policy = TwoWaySignPolicy(self._situation) time_step_spec = policy.time_step_spec action_spec = policy.action_spec super(SignAgent, self).__init__(time_step_spec=time_step_spec, action_spec=action_spec, policy=policy, collect_policy=policy, train_sequence_length=None) def _initialize(self): return tf.compat.v1.variables_initializer(self.variables) def _train(self, experience, weights=None): observation = experience.observation action = experience.action reward = experience.reward # We only need to change the value of the situation variable if it is # unknown (0) right now, and we can infer the situation only if the # observation is not 0. needs_action = tf.logical_and(tf.equal(self._situation, 0), tf.not_equal(reward, 0)) def new_situation_fn(): """This returns either 1 or 2, depending on the signs.""" return (3 - tf.sign(tf.cast(observation[0, 0, 0], dtype=tf.int32) * tf.cast(action[0, 0], dtype=tf.int32) * tf.cast(reward[0, 0], dtype=tf.int32))) / 2 new_situation = tf.cond(needs_action, new_situation_fn, lambda: self._situation) new_situation = tf.cast(new_situation, tf.int32) tf.compat.v1.assign(self._situation, new_situation) return tf_agent.LossInfo((), ()) sign_agent = SignAgent()

No código acima, o agente define a política, e a variável situation é compartilhada pelo agente e pela política.

Além disso, o parâmetro experience da função _train é uma trajetória:

Trajetórias

No TF-Agents, trajectories são tuplas nomeadas que contêm amostras dos passos anteriores realizados. Essas amostras são usadas pelo agente para treinar e atualizar a política. No RL, as trajetórias precisam conter informações sobre o estado atual, o estado seguinte e se o episódio atual já terminou. Como no mundo do Bandit não precisamos disso, configuramos uma função helper para criar uma trajetória:

# We need to add another dimension here because the agent expects the # trajectory of shape [batch_size, time, ...], but in this tutorial we assume # that both batch size and time are 1. Hence all the expand_dims. def trajectory_for_bandit(initial_step, action_step, final_step): return trajectory.Trajectory(observation=tf.expand_dims(initial_step.observation, 0), action=tf.expand_dims(action_step.action, 0), policy_info=action_step.info, reward=tf.expand_dims(final_step.reward, 0), discount=tf.expand_dims(final_step.discount, 0), step_type=tf.expand_dims(initial_step.step_type, 0), next_step_type=tf.expand_dims(final_step.step_type, 0))

Treinamento de um agente

Agora todas as partes já estão prontas para treinar nosso agente de bandit.

step = two_way_tf_environment.reset() for _ in range(10): action_step = sign_agent.collect_policy.action(step) next_step = two_way_tf_environment.step(action_step.action) experience = trajectory_for_bandit(step, action_step, next_step) print(experience) sign_agent.train(experience) step = next_step

A partir da saída, é possível ver que, após o segundo passo (a menos que a observação tenha sido 0 no primeiro passo), a política escolhe a ação corretamente e, então, a recompensa coletada é sempre não negativa.

Um exemplo real de Bandit contextual

No resto deste tutorial, usamos os ambientes e agentes pré-implementados da biblioteca Bandits do TF-Agents.

# Imports for example. from tf_agents.bandits.agents import lin_ucb_agent from tf_agents.bandits.environments import stationary_stochastic_py_environment as sspe from tf_agents.bandits.metrics import tf_metrics from tf_agents.drivers import dynamic_step_driver from tf_agents.replay_buffers import tf_uniform_replay_buffer import matplotlib.pyplot as plt

Ambiente estocástico estacionário com funções de payoff lineares

O ambiente usado neste exemplo é o StationaryStochasticPyEnvironment. Esse ambiente aceita como parâmetro uma função (geralmente ruidosa) para dar observações (contexto) e, para cada braço (arm), aceita uma função (também ruidosa) que computa a recompensa com base na observação específica. Em nosso exemplo, usamos o contexto como amostra de maneira uniforme a partir de um cubo d-dimensional, e as funções de recompensa são funções lineares do contexto, além de um pouco de ruído gaussiano.

batch_size = 2 # @param arm0_param = [-3, 0, 1, -2] # @param arm1_param = [1, -2, 3, 0] # @param arm2_param = [0, 0, 1, 1] # @param def context_sampling_fn(batch_size): """Contexts from [-10, 10]^4.""" def _context_sampling_fn(): return np.random.randint(-10, 10, [batch_size, 4]).astype(np.float32) return _context_sampling_fn class LinearNormalReward(object): """A class that acts as linear reward function when called.""" def __init__(self, theta, sigma): self.theta = theta self.sigma = sigma def __call__(self, x): mu = np.dot(x, self.theta) return np.random.normal(mu, self.sigma) arm0_reward_fn = LinearNormalReward(arm0_param, 1) arm1_reward_fn = LinearNormalReward(arm1_param, 1) arm2_reward_fn = LinearNormalReward(arm2_param, 1) environment = tf_py_environment.TFPyEnvironment( sspe.StationaryStochasticPyEnvironment( context_sampling_fn(batch_size), [arm0_reward_fn, arm1_reward_fn, arm2_reward_fn], batch_size=batch_size))

Agente LinUCB

O agente abaixo implementa o algoritmo LinUCB.

observation_spec = tensor_spec.TensorSpec([4], tf.float32) time_step_spec = ts.time_step_spec(observation_spec) action_spec = tensor_spec.BoundedTensorSpec( dtype=tf.int32, shape=(), minimum=0, maximum=2) agent = lin_ucb_agent.LinearUCBAgent(time_step_spec=time_step_spec, action_spec=action_spec)

Métrica de arrependimento

A métrica mais importante do Bandit é o arrependimento, calculado como a diferença entre a recompensa coletada pelo agente e a recompensa esperada de uma política de oráculo que tem acesso às funções de recompensa do ambiente. A RegretMetric precisa de uma função baseline_reward_fn que calcule a melhor recompensa esperada possível a partir de uma observação. Para nosso exemplo, precisamos pegar o máximo dos equivalentes sem ruído das funções de recompensa que já definidos para o ambiente.

def compute_optimal_reward(observation): expected_reward_for_arms = [ tf.linalg.matvec(observation, tf.cast(arm0_param, dtype=tf.float32)), tf.linalg.matvec(observation, tf.cast(arm1_param, dtype=tf.float32)), tf.linalg.matvec(observation, tf.cast(arm2_param, dtype=tf.float32))] optimal_action_reward = tf.reduce_max(expected_reward_for_arms, axis=0) return optimal_action_reward regret_metric = tf_metrics.RegretMetric(compute_optimal_reward)

Treinamento

Agora unimos todos os componentes apresentados acima: o ambiente, a política e o agente. Executamos a política no ambiente, geramos dados de treinamento com a ajuda de um driver e treinamos o agente com os dados.

Observe que há dois parâmetros que, juntos, especificam o número de passos realizados. num_iterations especifica quantas vezes executamos o loop de treinamento, enquanto o driver faz steps_per_loop passos por iteração. O principal motivo para manter esses dois parâmetros é que algumas operações são realizadas por iteração e outras são realizadas pelo driver em todos os passos. Por exemplo, a função train do agente só é chamada uma vez por iteração. O trade-off aqui é que, se treinarmos com mais frequência, nossa política será mais "recente", mas o treinamento com lotes maiores pode economizar mais tempo.

num_iterations = 90 # @param steps_per_loop = 1 # @param replay_buffer = tf_uniform_replay_buffer.TFUniformReplayBuffer( data_spec=agent.policy.trajectory_spec, batch_size=batch_size, max_length=steps_per_loop) observers = [replay_buffer.add_batch, regret_metric] driver = dynamic_step_driver.DynamicStepDriver( env=environment, policy=agent.collect_policy, num_steps=steps_per_loop * batch_size, observers=observers) regret_values = [] for _ in range(num_iterations): driver.run() loss_info = agent.train(replay_buffer.gather_all()) replay_buffer.clear() regret_values.append(regret_metric.result()) plt.plot(regret_values) plt.ylabel('Average Regret') plt.xlabel('Number of Iterations')

Depois de executar o último fragmento de código, o plot resultante (com sorte) mostra que o arrependimento médio está caindo conforme o agente é treinado, e a política fica melhor em descobrir a ação correta considerando a observação.

Próximos passos

Para ver mais exemplos práticos, consulte bandits/agents/examples, com exemplos prontos para execução em diferentes agentes e ambientes.

A biblioteca do TF-Agents também é capaz de lidar com Multi-Armed Bandits com características por braço. Para isso, consulte o tutorial de bandits por braço.