MARL算法系列(1):IQL【原理+代码实现】

news2025/1/8 5:49:15
  • 原文题目:Multiagent cooperation and competition with deep reinforcement learning
  • 作者:Tampuu, Ardi and Matiisen, Tambet and Kodelja, Dorian等
  • 发表时间:2017年
  • 主要内容:相互独立的两个DQN智能体,竞争任务下学会了相互竞争的策略,合作任务下学会了合作策略。

文章目录

  • 1 论文基本原理
    • 1.1 Abstract
    • 1.2 Introduction
    • 1.3 Methods
    • 1.4 实验结果
  • 实验与代码分析


1 论文基本原理

1.1 Abstract

相互独立的两个DQN智能体,竞争任务下学会了相互竞争的策略,合作任务下学会了合作策略。

1.2 Introduction

第一段,简要介绍什么是强化学习。

第二段,简要介绍强化学习算法历史,重点介绍DQN。

第三段,属于relative study,介绍了博弈论领域的多智能体合作与竞争,动物群体的合作与竞争。

第三段、第四段,简要介绍如何在雅达利游戏Pong中训练两个DQN智能体,并发现不同奖励下智能体之间会学会竞争或者合作的策略。

1.3 Methods

先介绍什么是DQN。

然后介绍如何将DQN用到Multi-agent问题中,并表示最直接的方法就是,智能体把其他智能体喝环境看作整体,每个智能体之间的决策相互独立(用原文的话说就是each agent is controlled by an independent Deep Q-Network)。

介绍针对原始DQN代码的一些修改。

介绍为什么选择Pong游戏。

介绍奖励设置:完全竞争、完全合作、既有竞争又有合作
场景奖励设置
完全竞争
完全合作
竞争+合作

介绍训练过程,50个epoch,每个epoch执行250000个step。探索率在一百万步内从1降到0.05然后不变。

介绍游戏的一些统计指标:平均拍数,平均撞到墙壁的次数,待机时间。

1.4 实验结果

在这里插入图片描述
在这里插入图片描述
这种直观的解释还是很有说服力的。

实验与代码分析

代码,这是参考这个实现:https://github.com/starry-sky6688/MARL-Algorithms。

import torch
import os
from network.base_net import RNN


class IQL:
    def __init__(self, args):
        self.n_actions = args.n_actions
        self.n_agents = args.n_agents
        self.state_shape = args.state_shape
        self.obs_shape = args.obs_shape
        input_shape = self.obs_shape
        # 根据参数决定RNN的输入维度
        if args.last_action:
            input_shape += self.n_actions
        if args.reuse_network:
            input_shape += self.n_agents

        # 神经网络
        self.eval_rnn = RNN(input_shape, args)
        self.target_rnn = RNN(input_shape, args)
        self.args = args
        if self.args.cuda:
            self.eval_rnn.cuda()
            self.target_rnn.cuda()
        self.model_dir = args.model_dir + '/' + args.alg + '/' + args.map
        # 如果存在模型则加载模型
        if self.args.load_model:
            if os.path.exists(self.model_dir + '/rnn_net_params.pkl'):
                path_rnn = self.model_dir + '/rnn_net_params.pkl'
                map_location = 'cuda:0' if self.args.cuda else 'cpu'
                self.eval_rnn.load_state_dict(torch.load(path_rnn, map_location=map_location))
                print('Successfully load the model: {}'.format(path_rnn))
            else:
                raise Exception("No model!")

        # 让target_net和eval_net的网络参数相同
        self.target_rnn.load_state_dict(self.eval_rnn.state_dict())

        self.eval_parameters = list(self.eval_rnn.parameters())
        if args.optimizer == "RMS":
            self.optimizer = torch.optim.RMSprop(self.eval_parameters, lr=args.lr)

        # 执行过程中,要为每个agent都维护一个eval_hidden
        # 学习过程中,要为每个episode的每个agent都维护一个eval_hidden、target_hidden
        self.eval_hidden = None
        self.target_hidden = None
        print('Init alg IQL')

    def learn(self, batch, max_episode_len, train_step, epsilon=None):  # train_step表示是第几次学习,用来控制更新target_net网络的参数
        '''
        在learn的时候,抽取到的数据是四维的,四个维度分别为 1——第几个episode 2——episode中第几个transition
        3——第几个agent的数据 4——具体obs维度。因为在选动作时不仅需要输入当前的inputs,还要给神经网络输入hidden_state,
        hidden_state和之前的经验相关,因此就不能随机抽取经验进行学习。所以这里一次抽取多个episode,然后一次给神经网络
        传入每个episode的同一个位置的transition
        '''
        episode_num = batch['o'].shape[0]
        self.init_hidden(episode_num)
        for key in batch.keys():  # 把batch里的数据转化成tensor
            if key == 'u':
                batch[key] = torch.tensor(batch[key], dtype=torch.long)
            else:
                batch[key] = torch.tensor(batch[key], dtype=torch.float32)
        u, r, avail_u, avail_u_next, terminated = batch['u'], batch['r'].repeat(1, 1, self.n_agents),  batch['avail_u'], \
                                                  batch['avail_u_next'], batch['terminated'].repeat(1, 1, self.n_agents)
        mask = (1 - batch["padded"].float()).repeat(1, 1, self.n_agents)  # 用来把那些填充的经验的TD-error置0,从而不让它们影响到学习

        # 得到每个agent对应的Q值,维度为(episode个数, max_episode_len, n_agents, n_actions)
        q_evals, q_targets = self.get_q_values(batch, max_episode_len)
        if self.args.cuda:
            u = u.cuda()
            r = r.cuda()
            terminated = terminated.cuda()
            mask = mask.cuda()
        # 取每个agent动作对应的Q值,并且把最后不需要的一维去掉,因为最后一维只有一个值了
        q_evals = torch.gather(q_evals, dim=3, index=u).squeeze(3)

        # 得到target_q
        q_targets[avail_u_next == 0.0] = - 9999999
        q_targets = q_targets.max(dim=3)[0]

        targets = r + self.args.gamma * q_targets * (1 - terminated)

        td_error = (q_evals - targets.detach())
        masked_td_error = mask * td_error  # 抹掉填充的经验的td_error

        # 不能直接用mean,因为还有许多经验是没用的,所以要求和再比真实的经验数,才是真正的均值
        loss = (masked_td_error ** 2).sum() / mask.sum()
        # print('loss is ', loss)
        self.optimizer.zero_grad()
        loss.backward()
        torch.nn.utils.clip_grad_norm_(self.eval_parameters, self.args.grad_norm_clip)
        self.optimizer.step()

        if train_step > 0 and train_step % self.args.target_update_cycle == 0:
            self.target_rnn.load_state_dict(self.eval_rnn.state_dict())
        return loss

    def _get_inputs(self, batch, transition_idx):
        # 取出所有episode上该transition_idx的经验,u_onehot要取出所有,因为要用到上一条
        obs, obs_next, u_onehot = batch['o'][:, transition_idx], \
                                  batch['o_next'][:, transition_idx], batch['u_onehot'][:]
        episode_num = obs.shape[0]
        inputs, inputs_next = [], []
        inputs.append(obs)
        inputs_next.append(obs_next)
        # 给obs添加上一个动作、agent编号

        if self.args.last_action:
            if transition_idx == 0:  # 如果是第一条经验,就让前一个动作为0向量
                inputs.append(torch.zeros_like(u_onehot[:, transition_idx]))
            else:
                inputs.append(u_onehot[:, transition_idx - 1])
            inputs_next.append(u_onehot[:, transition_idx])
        if self.args.reuse_network:
            # 因为当前的obs三维的数据,每一维分别代表(episode编号,agent编号,obs维度),直接在dim_1上添加对应的向量
            # 即可,比如给agent_0后面加(1, 0, 0, 0, 0),表示5个agent中的0号。而agent_0的数据正好在第0行,那么需要加的
            # agent编号恰好就是一个单位矩阵,即对角线为1,其余为0
            inputs.append(torch.eye(self.args.n_agents).unsqueeze(0).expand(episode_num, -1, -1))
            inputs_next.append(torch.eye(self.args.n_agents).unsqueeze(0).expand(episode_num, -1, -1))
        # 要把obs中的三个拼起来,并且要把episode_num个episode、self.args.n_agents个agent的数据拼成40条(40,96)的数据,
        # 因为这里所有agent共享一个神经网络,每条数据中带上了自己的编号,所以还是自己的数据
        inputs = torch.cat([x.reshape(episode_num * self.args.n_agents, -1) for x in inputs], dim=1)
        inputs_next = torch.cat([x.reshape(episode_num * self.args.n_agents, -1) for x in inputs_next], dim=1)
        return inputs, inputs_next

    def get_q_values(self, batch, max_episode_len):
        episode_num = batch['o'].shape[0]
        q_evals, q_targets = [], []
        for transition_idx in range(max_episode_len):
            inputs, inputs_next = self._get_inputs(batch, transition_idx)  # 给obs加last_action、agent_id
            if self.args.cuda:
                inputs = inputs.cuda()
                inputs_next = inputs_next.cuda()
                self.eval_hidden = self.eval_hidden.cuda()
                self.target_hidden = self.target_hidden.cuda()
            q_eval, self.eval_hidden = self.eval_rnn(inputs, self.eval_hidden)  # inputs维度为(40,96),得到的q_eval维度为(40,n_actions)
            q_target, self.target_hidden = self.target_rnn(inputs_next, self.target_hidden)

            # 把q_eval维度重新变回(8, 5,n_actions)
            q_eval = q_eval.view(episode_num, self.n_agents, -1)
            q_target = q_target.view(episode_num, self.n_agents, -1)
            q_evals.append(q_eval)
            q_targets.append(q_target)
        # 得的q_eval和q_target是一个列表,列表里装着max_episode_len个数组,数组的的维度是(episode个数, n_agents,n_actions)
        # 把该列表转化成(episode个数, max_episode_len, n_agents,n_actions)的数组
        q_evals = torch.stack(q_evals, dim=1)
        q_targets = torch.stack(q_targets, dim=1)
        return q_evals, q_targets

    def init_hidden(self, episode_num):
        # 为每个episode中的每个agent都初始化一个eval_hidden、target_hidden
        self.eval_hidden = torch.zeros((episode_num, self.n_agents, self.args.rnn_hidden_dim))
        self.target_hidden = torch.zeros((episode_num, self.n_agents, self.args.rnn_hidden_dim))

    def save_model(self, train_step):
        num = str(train_step // self.args.save_cycle)
        if not os.path.exists(self.model_dir):
            os.makedirs(self.model_dir)
        torch.save(self.eval_rnn.state_dict(),  self.model_dir + '/' + num + '_rnn_net_params.pkl')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/118603.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022年威胁隐私和安全的数个“罪魁祸首”

随着互联网技术的不断发展,我们对网络的信任也在不断增加,甚至将自己的私人数据委托给各种在线平台,如个人数字身份信息、银行账户、各种机密信息。网络一方面的确带来变革型的进步,但另一方面,频频曝光的数据泄露事件…

VueJs中setup的使用(下)

前言在Vue当中,父组件想要向子组件传值,是通过在父组件标签上通过自定义属性实现的,而在子组件中是通过props进行接收在Vue2.0里面,在子组件中的选项式API配置项选项中props进行接收就可以了的,在子组件中的模板中可以直接使用但在Vue3里面与Vue2.0存在一些差异,这个主要是针对…

excel文件管理:如何进行密码保护和破解? 下篇

在上篇文章中,我们提到了设置工作簿的打开权限密码、修改权限密码、保护工作簿的密码、允许编辑区域的密码,并且讲到了两种破解excel密码的方式。今天,我们书接上回,继续讲解excel中常见的密码保护和破解方式,一起来看…

浅谈屏幕适配

文章目录1. 概述2. 屏幕尺寸3. 屏幕分辨率4. 屏幕像素密度5. dp、sp、px6. mdpi、hdpi、xdpi..7. 屏幕分辨率限定符8. 最小宽度限定符8.1 获取设计图最小宽度(dp)8.2 生成对应的dimens.xml文件8.3 尺寸限定符8.4 其它9. 今日头条相关9.1 系统状态栏获取不对问题9.2 autosize1. …

Elasticsearch8.X入门实战(二)Elasticsearch集群架构

Elasticsearch集群由一个或多个节点(服务器)组成,这些节点一起保存Elasticsearch的所有数据,并提供跨所有节点的联合索引和搜索功能。集群由一个唯一的名称来标识,该名称默认为“elasticsearch”(可以在配置文件中修改)。当某个节点被设置为相同的集群名称时,该节点才能…

Docker容器的简单介绍与使用

前言:大家好,我是小威,24届毕业生,曾经在某央企公司实习,目前入职某税务公司。本篇文章将记录和分享docker容器相关的知识点。 本篇文章记录的基础知识,适合在学Java的小白,也适合复习中&#x…

如何更好地进行 Android 组件化开发——路由原理篇

前言 组件化开发的会实现代码隔离,在开发时访问不到模块的代码,降低代码耦合度。那么如何跳转组件的页面、如何进行组件间的通信是个问题。这通常会使用到 ARouter、TheRouter、WMRouter 等路由框架。可能有不少人只知道怎么去调用,并不知道…

Pod内容详情梳理

本篇是笔者的一篇读书笔记,用于梳理pod的详情,方便理解和学习,也方便后续自己查询。一、Pod的概述Pod是k8s里面典型的CR,从它的元数据来看,具有所有CR的基本数据构成,分别是 version、kind,以及…

迅为RK3568开发板支持多屏同显/异显动态方案

iTOP-RK3568开发板采用四核Cortex-A55处理器,芯片内置VOP控制器,支持HDMI、LVDS、MIPI、EDP四种显示接口的多屏同显、异显和异触,可有效提高行业定制的拓展性。 三屏同显: 三屏异显: 双屏同显: 双屏异显&am…

Docker容器里进程的 pid 是如何申请出来的?

大家好,我是飞哥!如果大家有过在容器中执行 ps 命令的经验,都会知道在容器中的进程的 pid 一般是比较小的。例如下面我的这个例子。# ps -ef PID USER TIME COMMAND1 root 0:00 ./demo-ie13 root 0:00 /bin/bash21 root …

编程小技巧9-如何生成没有水印的代码图片(IDEA carbon-now-sh插件使用教程)

陈老老老板🦸👨‍💻本文专栏:快速变成小技巧(主要讲一些平时常用的、有助于提高开发素的内容)👨‍💻本文简述:本文讲一下使用carbon-now-sh插件生成图片超详细教程。&…

STM32

一:生成独立的他.h和.c文件 勾选后,生成单独的.h和.c文件。不勾选的话都在main里面。 二:常用。 1:电平输出。 HAL_GPIO_WritePin(PIN_LED_1_GPIO_Port, PIN_LED_1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(PIN_LED_1_GPIO_Port, …

django 登录流程实现

一、简介: 1、用户输入正确的用户名、密码、验证码点击登录即可跳转到管理员页面。 2、用户输入错误的用户名或者密码或者验证码需要错误信息提示(数据校验) 二、实现步骤 1、新建一个项目(创建项目过程和数据库略,…

签完三方后无法去实习,有什么可以弥补的吗?

作者:阿秀校招八股文学习网站:https://interviewguide.cn这是阿秀的第「228」篇原创你好,我是阿秀。2023届秋招已经步入尾声,很多小伙伴都已经找到工作&签约三方,慢慢结束了自己的秋招之旅,不过也有一些…

Local Attention和动态深度卷积间的关系

摘要 Local Vision Transformer 是分别在一个个小的局部窗口中进行注意力计算。 作者将局部注意力重新定义为通道级的局部连接层(channel-wise locally-connected layer),并4个方面进行分析:两种网络的正则化方式,稀疏…

C语言实现九大排序算法(建议收藏!)

文章目录排序算法稳定性1. 插入排序原理排序过程代码实现性能分析2. 希尔排序原理排序过程关于增量取值代码实现性能分析3. 选择排序原理排序过程代码实现性能分析4. 堆排序原理排序过程代码实现性能分析5. 冒泡排序原理排序过程代码实现性能分析6. 快速排序原理Hoare法挖坑法前…

Easy App Locker - 给你的 mac 应用加锁保护你的隐私

Easy App Locker - 给你的 mac 应用加锁保护你的隐私 Easy App Locker可以对Mac上的单个应用进行密码保护。维护Mac上的隐私。 像如果你的某个应用存在隐私数据就可以使用该软件将此应用上锁,这样当你的朋友使用你的 mac 时你就不用担心你的隐私被泄露了&#xff0…

Java中创建线程的五种方式

目录: 前言 1.进程与线程的区别? 2.进程是操作系统进行资源分配的基本单位,而操作系统是以线程为单位进行调度的。 3. Java操作多线程,依赖最核心的类Thread。 4.关于start和run的区别? 5.使用JDK自带的工具jcon…

ArcGIS基础实验操作100例--实验7分割多部分要素

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台:ArcGIS 10.6 实验数据:请访问实验1(传送门) 基础编辑篇--实验7 分割多部分要素 目录 一、实验背景 二、实验数据 (1)查看多…

第05讲:Redis主从复制

一、关于主从复制 1.1、什么是主从复制 主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主 1.2、主从复制的作用 读写分离,性能扩展容灾快速恢复 二、一主多从的实验 2.1、原…