thu-ml / tianshou

An elegant PyTorch deep reinforcement learning library.
https://tianshou.org
MIT License
7.83k stars 1.12k forks source link

How to support multi-agent reinforcement learning #121

Open youkaichao opened 4 years ago

youkaichao commented 4 years ago
youkaichao commented 4 years ago

This issue can be used to track the design of multi-agent reinforcement learning implementation.

youkaichao commented 4 years ago

After some pilot study, I find there are three paradigms of multi-agent reinforcement learning:

  1. simultaneous move, at each timestep, all the agent take their actions (example: moba games)

  2. cyclic move, players take action in turn (example: Go game)

  3. conditional move, at each timestep, the environment conditionally selects an agent to take action. (example: Pig Game)

The problem is how to transform these paradigms to the following standard RL procedure:

action = policy(state)
next_state, reward = env.step(action)

For simultaneous move, the solution is simple: we can just add a num_agent dimension to state, action, reward. Nothing else is going to change.

For 2 & 3 (cyclic move & conditional move), an elegant solution is:

action = policy(state, agent_id)
next_state, next_agent_id, reward = env.step(action)

By constructing new state state_ma = {'state':state, 'agent_id':agent_id}, essentially we can go back to the standard case:

action = policy(state_ma)
next_state_ma, reward = env.step(action)

Just be careful that reward here can be rewards for all the players (the action of one player may affect all players).

Usually, the legal action set varies with state, so it is more convenient to denote state_ma = {'state':state, 'legal_actions':legal_actions, 'agent_id':agent_id}.

duburcqa commented 4 years ago

I don't think there is a need for multi-agent reinforcement learning at short term. To me the priority is to improve the current functionality. There is major flaws in the current implementation, especially regarding the efficiency of distributed sampling, which are way more critical to handle than adding new features. The core has be to robust before building on top of it.

Yet, of course, it is still interesting to discuss the implementation of future features!

Trinkle23897 commented 4 years ago

I don't think there is a need for multi-agent reinforcement learning at short term. To me the priority is to improve the current functionality. There is major flaws in the current implementation, especially regarding the efficiency of distributed sampling, which are way more critical to handle than adding new features. The core has be to robust before building on top of it.

Yet, of course, it is still interesting to discuss the implementation of future features!

This feature does not change any of the current code and is also compatible with 2d-buffer. I think it is independent with what you said.

duburcqa commented 4 years ago

I think it is independent with what you said.

Of course it is! Work force being limited, someone working on a specific feature necessarily affects / slow-down the development of the others. That's the point of prioritizing development of some features wrt to others, it is often not because of interdependencies, but rather because of limited work force.

But obviously, in the setup of an open-source project, anyone is free to work on the features they want to.

youkaichao commented 4 years ago

I gave an example of playing Tic Tac Toe in the test case, without modifying the core code.

What is the next step seems unclear. I do not know how people in MARL typically train their model, especially how to sample experience.Some possible ideas may be:

  1. each player only learns on his own experience
  2. each player leans on experience of all players

Which one is commonly used? Or both? Or there are other paradigms? This should be cleared by some experts in MARL area.

youkaichao commented 4 years ago

Issue #136 is a great discussion on multi-agent rl with simultaneous move. The conclusion is that, it can be dealt with, without modifying the core of Tianshou. Just to inherit some class and re-implement one function with several lines, depending on the specific scenario.

p-veloso commented 4 years ago

I am not an expert in MARL and I have just discovered Tianshou. With that said, here are some thoughts based on the papers I have been reading recently. There are many workflows for MARL with different training (centralized/decentralized), execution (centralized/decentralized), type of agents (homogeneous/heterogeneous), task setting (cooperative/competitive/mixed), types of reward (individual/global), etc. Here are some common MARL types:

  1. independent policies (- -)
  2. shared individual policy (parameter sharing, see #136) (+ +)
  3. centralized training and decentralized execution (+ -)
  4. centralized training and execution (- +)

The problem with 1 is that it tends not to converge and might require managing different networks in training and execution (heterogeneous agents). Type 2 and Type 3 are strong trends in MARL. Type 2 can use the algorithms already implemented in Tianshou with small changes in the policy or collector. Type 3 would require implementing specific algorithms (ex: QMIX, MADDPG or COMA), like in RLlib. I think type 4 can be implemented using Tianshou with no change, but in practice it is hard to scale, as the joint action space grows exponentially in the number of agents.

Therefore, type 2 might be a good start. If there is a large interest in making Tianshou a MARL library, maybe it is worth developing 3.

youkaichao commented 4 years ago

There are many workflows for MARL with different training (centralized/decentralized), execution (centralized/decentralized), type of agents (homogeneous/heterogeneous), task setting (cooperative/competitive/mixed), types of reward (individual/global), etc.

Thank you for your comment and the overall description of MARL algorithms. It seems MARL has many variants and it is difficult to support them once at all. We have to support MARL step by step.

youkaichao commented 4 years ago

Some updates:

  1. to support centeralized training and decenteralized execution, one can inherit the tianshou.policy.MultiAgentPolicyManager class to implement the train and evalfunction to act differently in different mode.

  2. allow agents to see the state of other agents during training: wrap the environment to return the state of other agents in info.

jkterry1 commented 3 years ago

You guys might want to take a look at this: https://github.com/PettingZoo-Team/PettingZoo

p-veloso commented 3 years ago

I noticed that the cheat sheet for rl states that tianshou supports simultaneous move (case 1):

"for simultaneous move, the solution is simple: we can just add a num_agent dimension to state, action, and reward. Nothing else is going to change".

**Question: Is it possible to use the MultiAgentPolicyManager to work with

Last year (#136) I had to "tweak" the policy and net classes by

Trinkle23897 commented 3 years ago

varied number of agents (each simulation has a different number of agents)

Sorry about that, that is actually beyond the current scope. I haven't come up with a good design choice for this kind of requirement...

p-veloso commented 3 years ago

Isn't that just the case of adding a dimension of n_agent to the actions and observations, like I did last year and adapting the operations in the policy to that? When you reset the environment, it will set a new number of agents for that episode.

Trinkle23897 commented 3 years ago

hmm yep, you're right

p-veloso commented 3 years ago

Assuming that I can fix the number of agents for a certain period of training, does the class MultiAgentPolicyManager support parameter sharing (single policy for multiple agents) and simultaneous actions? If not, are there other classes that can support it?

Trinkle23897 commented 3 years ago

Can you pass the same reference into MAPM, i.e., MultiAgentPolicyManager([policy_1, policy_1, policy_2])? I think that would be fine to some extend.

p-veloso commented 3 years ago

That might work, but I think it will train the same network with separate batches, right?

        results = {}
        for policy in self.policies:
            data = batch[f"agent_{policy.agent_id}"]
            if not data.is_empty():
                out = policy.learn(batch=data, **kwargs)
                for k, v in out.items():
                    results["agent_" + str(policy.agent_id) + "/" + k] = v
        return results
Trinkle23897 commented 3 years ago

Exactly, that should be a tiny issue but I think it would be fine for agent to learn, though it is a little bit inefficient.

p-veloso commented 3 years ago

Thanks for the quick reply, @Trinkle23897 . As I mentioned before, last year I changed the code of the policies and networks to manage the extra agent dimension in the batch. The problem of that approach is that I had to change the code for the specific algorithm, so it might be tricky to compare multiple algorithms. Do you think that customizing the MultiAgentPolicyManager would be a more general solution in my case? Or would I still have to deal with specific changes in the policy and network classes?

Trinkle23897 commented 3 years ago

I had to change the code for the specific algorithm

I don't quite understand. Do you mean you use different algorithms in the same MAPM? Current implementation only accepts a list of on-policy algorithms or a list of off-policy algorithms. It would be no code changes if you use something like [dqn_agent, c51_agent, c51_agent].

p-veloso commented 3 years ago

No. I am using a single policy.

What I mean is that last year I changed the policy of PPO and the neural network to deal with parameter sharing and simultaneous actions. I changed the code of tianshou to manage the additional dimension in my setting.

The environment sends observations, rewards, etc. with the number of agents as the first dimension (n_agents, ?) This results in a batch with (batch dim, n agents, ?) The policy and networks use (batch x n_agents, ?) for updates and learning The resulting actions are then reshaped to (batch, n_agents, ?) to be returned to the environments

These changes were made in 1) PPO: compute_episodic_return: change how m is calculated learn: change how ratio and u are calculated

2) NETWORK forward: merge batch and agent dims, do the forward pass, unmerge batch and agents dimensions

So, my point is that it would be tricky to do that for multiple algorithms. So, I am curious if I could address these changes only by customizing my own multi agent policy manager (similar to the MultiAgentPolicyManager, but with one policy and the changes mentioned above done directly in its methods).

Trinkle23897 commented 3 years ago

Yeah, you can definitely do that. In MAPM the only thing need to do is to re-organize the Batch to buffer-style data (reshape or flatten to let the 1st dim be n_agent*bsz, also pay attention to done flag) and that would be the same as PPO single-agent.

jkterry1 commented 3 years ago

Could I offer a thought? You might want to consider using the PettingZoo parallel API (it has two). The parallel API isn't significantly different what I understand you're proposing your 1.0 one to be, and this way you're using a standard instead of a custom one. A bunch of third party libraries for PettingZoo already exist (e.g. p-veloso above has one), and RLlib and stable baselines interface with it, as do several more minor RL libraries.

Trinkle23897 commented 3 years ago

Could I offer a thought? You might want to consider using the PettingZoo parallel API (it has two). The parallel API isn't significantly different what I understand you're proposing your 1.0 one to be, and this way you're using a standard instead of a custom one. A bunch of third party libraries for PettingZoo already exist (e.g. p-veloso above has one), and RLlib and stable baselines interface with it, as do several more minor RL libraries.

That's pretty cool! I'm wondering if you have any interest in integrating this standard library into tianshou.

jkterry1 commented 3 years ago

Sure we can do that! It'll probably take a few weeks though

p-veloso commented 3 years ago

Can you pass the same reference into MAPM, i.e., MultiAgentPolicyManager([policy_1, policy_1, policy_2])? I think that would be fine to some extend.

@Trinkle23897, For now, I gave up on my previous approach (manually changing the shape of the batch), because it would require tweaking some of the algorithms that rely on the order of the observations (e.g., GAE, multi-step value prediction, etc.). I am trying the approach that you cited above, but there is a problem:

Trinkle23897 commented 3 years ago

I go through the above discussion again. So if the environment produces all agent's step simultaneously and uses only one policy, there's no need to use/follow MAPM. Instead, treat this environment as a normal single-agent environment, e.g.,

and then use normal way of collector/buffer (a transition in buffer stores several agent's obs/act/rew/... in this timestep), one thing you need to do is to customize your network to squash the second dimension (num_agent) into the first dimension (batch size). Since this reward is an array instead of scalar, you should pass reward_metric into trainer (and I'm not sure if any of the existing code should be tuned, like GAE, but nstep supports this feature, see #266 and https://github.com/thu-ml/tianshou/pull/266/commits/3695f126b1b8a340b1c72801fc13c69e26b33522).

Please let me know if there's anything unclear (or maybe I misunderstood some parts lol)

p-veloso commented 3 years ago

one thing you need to do is to customize your network to squash the second dimension (num_agent) into the first dimension (batch size)

Yes. In a previous version of tianshou I tried to fix that with the following changes:

NETWORK forward: merge batch and agent dims, do the forward pass, unmerge batch and agents dimensions

    def forward(self, s, state=None, info={}):
        shape = s.shape
        s = to_torch(s, device=self.device, dtype=torch.float)
        edit = len(s.shape) == 5
        if edit:
            s = s.view(shape[0] * shape[1], shape[2], shape[3], shape[4]).cuda()
        logits = self.model(s)
        if edit:
            logits = logits.view(shape[0], shape[1], -1)
        return logits, state

But that is not enough, because the single agent algorithms also assume that shape, so I have to change the batch or change them directly, such as

PPO:

As I mentioned in another post, the problems with this approach are:

That is why I was looking for a more general approach outside of the policies. I tried your idea of repeating the same policy in the policy manager for each agent, which is nice because each policy will only deal with a batch of one of the agents separately. However, that

in BaseVectorEnv: assert len(self.data) == len(ready_env_ids) in Collector: assert len(action) == len(id)

Trinkle23897 commented 3 years ago
  • compute_episodic_return: change how m is calculated
  • learn: change how ratio and u are calculated
  • I would have to change every algorithm

Yep, that's what I was doing previously. ~But I don't think there's a free lunch that can use single-agent codebase to support multi-agent with little modifying and no performance decrease (for wall-time).~ But ... wait

in BaseVectorEnv: assert len(self.data) == len(ready_env_ids) in Collector: assert len(action) == len(id)

Yeah I thought about this approach last night. Let's say if you have 4 envs and each env needs 5 agent, so that you need a VectorReplayBuffer(buffer_num=20) (num_agents x num_env). However, currently the high-level module such as collector doesn't know the exact format of this given batch (e.g., how to split a batch with batch_size==4 into 20 buffer slots).

So this comes to one natural way: construct another vector env that inherit existing BaseVectorEnv, where:

therefore you can execute only num_env envs but get num_env x num_agent results at each step without modifying the agent's code and without using MAPM.

p-veloso commented 3 years ago

@Trinkle23897

I spent this last day trying the different approaches. I have just solved the "policy approach" for DQN in the latest version of the Tianshou, but I would definitely prefer a higher-level modification that can work with all the original policies. I think your suggestion is similar to what supersuit does ... but I had no idea how to do that in Tianshou. Thanks for the suggestion.

Just for clarification, according to your current idea, would I still need to change other parts, such as the forward pass of the neural network?

Trinkle23897 commented 3 years ago

Just for clarification, according to your current idea, would I still need to change other parts, such as the forward pass of the neural network?

None of them I think.

p-veloso commented 3 years ago

It works! Thanks again.

benblack769 commented 3 years ago

@p-veloso I just saw this, but while the supersuit example here: https://github.com/PettingZoo-Team/SuperSuit#parallel-environment-vectorization is for stable baselines, all it does is translate the parallel environment into a vector environment. Since tianshou support vector environments out of box for all algorithms, you should just be able to use supersuit's environment vectorization, rather than your own custom code. If there is some reason it doesn't work out of box, feel free to raise an issue with supersuit asking for support for tianshou.