📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在👉强化学习专栏:

       【强化学习】(37)---《自监督强化学习:随机网络蒸馏(RND)方法》

自监督强化学习:随机网络蒸馏(RND)方法

目录

1. 引言

2. RND 的核心概念

2.1 随机网络

2.2 目标网络

3. 内在奖励的计算

4. RND 的学习过程

5. RND 与强化学习框架的结合

[Python] RND算法实现(pytorch)

[Notice]  说明

6. RND 的优势与局限

7. 实验结果与效果验证

8. 总结


1. 引言

        随机网络蒸馏(RND)是一种自监督学习方法,旨在提高强化学习中的探索效率。该算法由 Chesney et al. 在论文《Random Network Distillation as a Method for Intrinsic Motivation》提出,RND 利用随机神经网络的输出与环境状态的真实特征之间的差异来生成内在奖励,鼓励智能体探索未见过的状态。这种方法尤其适用于外部奖励稀疏的环境。


2. RND 的核心概念

        RND 的基本思想是通过比较一个固定的随机网络与另一个可学习的网络之间的输出,来为智能体的探索提供内在奖励。

2.1 随机网络

        RND 中使用的随机网络是一个不可训练的网络,其参数在训练开始时随机初始化并保持不变。这个网络的输出是对于输入状态的某种表示。

2.2 目标网络

        与随机网络相对应的是一个可学习的网络(称为目标网络),这个网络会根据接收到的状态进行训练,以尽量模仿随机网络的输出。


3. 内在奖励的计算

        RND 通过计算可学习网络的输出与随机网络的输出之间的差异来生成内在奖励。具体来说,对于给定的状态( s ),内在奖励( r_{\text{int}}(s) )可以通过以下步骤计算:

  1. 输入状态( s )到随机网络,得到输出( \phi(s) )
  2. 输入同一状态( s )到可学习网络,得到输出( \hat{\phi}(s) )
  3. 计算二者之间的均方误差(MSE)作为内在奖励:

[ r_{\text{int}}(s) = | \phi(s) - \hat{\phi}(s) |^2 ]

其中,( r_{\text{int}}(s) )是智能体在状态( s )下的内在奖励,( | \cdot |^2 ) 表示二范数的平方。

        这个内在奖励鼓励智能体去探索那些输出差异较大的状态,因为这些状态代表着模型尚未学习到的部分。


4. RND 的学习过程

        RND 的学习过程包括两个部分:随机网络的输出生成和可学习网络的训练。

4.1 随机网络

        随机网络的参数固定不变。在整个训练过程中,它的输出仅用于生成内在奖励。

4.2 可学习网络

        可学习网络的目标是最小化输出与随机网络输出之间的差异。通过最小化均方误差(MSE),可学习网络会学习到与随机网络相似的输出。

        训练过程中的损失函数 ( L ) 定义为:

[ L = \frac{1}{N} \sum_{i=1}^{N} | \phi(s_i) - \hat{\phi}(s_i) |^2 ]

其中,( N )是样本数量,( s_i )是输入的状态。


5. RND 与强化学习框架的结合

        RND 可以与多种强化学习算法结合使用,如 DQN、A3C 等。具体流程如下:

  1. 状态获取:智能体在环境中执行动作,获得当前状态( s_t ) 和下一个状态 ( s_{t+1} )
  2. 内在奖励计算:计算内在奖励( r_{\text{int}}(s_t) )
  3. 策略更新:使用内在奖励( r_{\text{int}}(s_t) )和外部奖励( r_{\text{ext}}(s_t) ) 共同更新策略。
  4. 可学习网络训练:在每次环境交互后,更新可学习网络以减少预测误差。

        通过将内在奖励与外部奖励结合,RND 能够引导智能体探索更多的状态,从而提升学习效率。


[Python] RND算法实现(pytorch)

        下面是给出了结合随机网络蒸馏(RND)和A3C算法在Cartpole环境中实现的PyTorch代码。该代码包含了RND的核心实现,用于激励智能体探索。

        🔥若是下面代码复现困难或者有问题,欢迎评论区留言;需要以整个项目形式的代码,请在评论区留下您的邮箱📌,以便于及时分享给您(私信难以及时回复)。

环境准备:

首先确保安装了所需的库:

pip install gym torch torchvision numpy

参数设置: 

"""《 RND 算法简单实现》
    时间:2024.10.28
    环境:cartpole
    作者:不去幼儿园
"""
import numpy as np  # 导入NumPy库,用于数组和数学操作
import gym  # 导入OpenAI Gym库,用于环境模拟
import torch  # 导入PyTorch库,用于深度学习
import torch.nn as nn  # 导入PyTorch的神经网络模块
import torch.optim as optim  # 导入PyTorch的优化器模块
import torch.nn.functional as F  # 导入PyTorch的功能模块(激活函数等)
from torch.distributions import Categorical  # 导入Categorical分布,用于策略选择

网络配置:

# RND网络(随机网络)
class RandomNetwork(nn.Module):  # 定义一个随机网络类,继承自nn.Module
    def __init__(self, input_size, output_size):  # 初始化方法
        super(RandomNetwork, self).__init__()  # 调用父类初始化方法
        self.fc1 = nn.Linear(input_size, 128)  # 第一层全连接层,输入大小为input_size,输出128
        self.fc2 = nn.Linear(128, output_size)  # 第二层全连接层,输入128,输出output_size
        self.random_weights = nn.Parameter(torch.randn(1, output_size))  # 随机权重参数

    def forward(self, x):  # 前向传播方法
        x = F.relu(self.fc1(x))  # 通过第一层并应用ReLU激活
        return self.fc2(x) + self.random_weights  # 通过第二层并加上随机权重

class PredictorNetwork(nn.Module):  # 定义一个预测网络类
    def __init__(self, input_size, output_size):  # 初始化方法
        super(PredictorNetwork, self).__init__()  # 调用父类初始化方法
        self.fc1 = nn.Linear(input_size, 128)  # 第一层全连接层
        self.fc2 = nn.Linear(128, output_size)  # 第二层全连接层

    def forward(self, x):  # 前向传播方法
        x = F.relu(self.fc1(x))  # 通过第一层并应用ReLU激活
        return self.fc2(x)  # 通过第二层

# A3C代理
class A3CAgent:  # 定义A3C代理类
    def __init__(self, input_size, action_size):  # 初始化方法
        self.policy_net = nn.Sequential(  # 策略网络
            nn.Linear(input_size, 128),  # 输入层
            nn.ReLU(),  # 激活函数
            nn.Linear(128, action_size),  # 输出层
            nn.Softmax(dim=-1)  # Softmax函数
        )
        self.value_net = nn.Sequential(  # 价值网络
            nn.Linear(input_size, 128),  # 输入层
            nn.ReLU(),  # 激活函数
            nn.Linear(128, 1)  # 输出层,值为一个标量
        )
        self.optimizer = optim.Adam(self.policy_net.parameters(), lr=1e-3)  # 策略网络优化器
        self.optimizer_value = optim.Adam(self.value_net.parameters(), lr=1e-3)  # 价值网络优化器

        self.rnd = RandomNetwork(input_size, output_size=1)  # 随机网络实例
        self.predictor = PredictorNetwork(input_size, output_size=1)  # 预测网络实例
        self.optimizer_rnd = optim.Adam(list(self.rnd.parameters()) + list(self.predictor.parameters()), lr=1e-3)  # 随机网络优化器

    def select_action(self, state):  # 选择动作方法
        state = torch.FloatTensor(state).unsqueeze(0)  # 将状态转换为张量并增加一个维度
        probs = self.policy_net(state)  # 通过策略网络计算动作概率
        distribution = Categorical(probs)  # 创建Categorical分布
        action = distribution.sample()  # 根据分布采样动作
        return action.item(), distribution.log_prob(action)  # 返回动作及其对数概率

    def update(self, rewards, log_probs, values, next_value):  # 更新方法
        # 计算回报
        returns = []  # 初始化回报列表
        R = next_value  # 初始化R为下一个值
        for r in rewards[::-1]:  # 反向遍历奖励
            R = r + 0.99 * R  # 计算当前回报
            returns.insert(0, R)  # 将回报插入列表开头

        # 计算损失
        returns = torch.FloatTensor(returns).view(-1, 1)  # 转换回报为张量并调整形状
        log_probs = torch.stack(log_probs)  # 堆叠对数概率
        values = torch.stack(values)  # 堆叠价值

        advantage = returns - values.detach()  # 计算优势

        policy_loss = -log_probs * advantage.detach()  # 策略损失
        value_loss = F.mse_loss(values.view(-1, 1), returns)  # 价值损失,确保形状一致

        self.optimizer.zero_grad()  # 清空优化器梯度
        policy_loss.mean().backward()  # 反向传播策略损失
        self.optimizer.step()  # 更新策略网络

        self.optimizer_value.zero_grad()  # 清空价值优化器梯度
        value_loss.backward()  # 反向传播价值损失
        self.optimizer_value.step()  # 更新价值网络

    def compute_intrinsic_reward(self, state):  # 计算内在奖励
        state_tensor = torch.FloatTensor(state).unsqueeze(0)  # 状态转换为张量
        with torch.no_grad():  # 在不计算梯度的情况下进行前向传播
            rnd_output = self.rnd(state_tensor)  # 获取随机网络输出
            pred_output = self.predictor(state_tensor)  # 获取预测网络输出

        intrinsic_reward = F.mse_loss(rnd_output, pred_output)  # 计算内在奖励
        return intrinsic_reward.item()  # 返回内在奖励的数值

算法训练:

# 训练过程
def train(agent, env, num_episodes):  # 训练函数
    for episode in range(num_episodes):  # 遍历每个回合
        state, _ = env.reset()  # 重置环境并获取初始状态
        log_probs = []  # 初始化对数概率列表
        values = []  # 初始化价值列表
        rewards = []  # 初始化奖励列表
        intrinsic_rewards = []  # 初始化内在奖励列表

        done = False  # 初始化done标志
        while not done:  # 在未完成时循环
            action, log_prob = agent.select_action(state)  # 选择动作
            next_state, reward, done, _, _ = env.step(action)  # 执行动作并获取下一个状态及奖励

            intrinsic_reward = agent.compute_intrinsic_reward(state)  # 计算内在奖励
            intrinsic_rewards.append(intrinsic_reward)  # 将内在奖励添加到列表中

            log_probs.append(log_prob)  # 添加对数概率
            value = agent.value_net(torch.FloatTensor(state).unsqueeze(0))  # 计算当前状态的价值
            values.append(value)  # 添加价值

            rewards.append(reward + intrinsic_reward)  # 添加总奖励(外在 + 内在)
            state = next_state  # 更新状态

        next_value = agent.value_net(torch.FloatTensor(state).unsqueeze(0))  # 计算下一个状态的价值
        agent.update(rewards, log_probs, values, next_value)  # 更新代理
        print(f"Episode {episode}: Total Reward = {sum(rewards)}")  # 打印回合总奖励


# 主程序
env = gym.make("CartPole-v1")  # 创建CartPole环境
agent = A3CAgent(input_size=env.observation_space.shape[0], action_size=env.action_space.n)  # 实例化代理
train(agent, env, num_episodes=100)  # 训练代理
env.close()  # 关闭环境

算法测试:

# 测试阶段显示动画
def test_agent(agent, env, num_episodes=5):  # 测试函数
    for episode in range(num_episodes):  # 遍历每个测试回合
        state, _ = env.reset()  # 重置环境
        total_reward = 0  # 初始化总奖励
        done = False  # 初始化done标志
        while not done:  # 在未完成时循环
            env.render()  # 显示动画
            action, log_prob = agent.select_action(state)  # 选择动作
            state, reward, done, _, _ = env.step(action)  # 执行动作并获取下一个状态及奖励
            intrinsic_reward = agent.compute_intrinsic_reward(state)  # 计算内在奖励
            total_reward += reward + intrinsic_reward  # 更新总奖励
        print(f"Test Episode {episode}: Total Reward = {total_reward}")  # 打印测试回合总奖励
    env.close()  # 关闭环境


# 测试模型
env_test = gym.make('CartPole-v1', render_mode='human')  # 创建测试环境
test_agent(agent, env_test)  # 测试代理

[Notice]  说明

  1. 随机网络(RND): 用于生成自监督的内在奖励。RND网络的参数是随机初始化的,而预测网络则通过训练来适应环境。

  2. A3C代理: 该类包含选择动作、更新策略和计算内在奖励的方法。

  3. 训练过程: 通过与环境交互收集状态、动作、奖励和价值,并利用这些数据更新策略。

运行此代码后,智能体将在Cartpole环境中学习如何平衡杆子。

env = gym.make("Taxi-v3", render_mode="human")  # 使用人类可视化的方式

        由于博文主要为了介绍相关算法的原理应用的方法,缺乏对于实际效果的关注,算法可能在上述环境中的效果不佳,一是算法不适配上述环境,二是算法未调参和优化,三是等等。上述代码用于了解和学习算法足够了,但若是想直接将上面代码应用于实际项目中,还需要进行修改。


6. RND 的优势与局限

优势:
  • 增强探索能力:RND 能有效提高智能体在稀疏奖励环境中的探索能力,智能体可以通过内在奖励主动寻找有价值的状态。
  • 自适应性:由于随机网络的固定性,RND 能够自适应不同的环境,适用于多种任务。
  • 简单易用:RND 的实现相对简单,可以无缝集成到现有的强化学习框架中。
局限:
  • 计算开销:需要同时维护两个网络,增加了计算和存储的开销。
  • 收敛速度:在某些复杂环境中,内在奖励可能导致智能体的收敛速度变慢。
  • 不稳定性:可学习网络的训练可能会受到随机网络输出的影响,导致学习过程中的不稳定性。

7. 实验结果与效果验证

        在论文中,RND 被应用于多种基准测试环境,包括 Atari 游戏和 MuJoCo 机器人任务。实验结果显示,RND 能显著提升智能体的性能,特别是在外部奖励稀疏的情况下,智能体通过内在奖励进行有效探索,最终实现了更高的得分。

        例如,在 Atari 游戏中,RND 的引入使得智能体在完全缺乏外部奖励的情况下,依然能够学会有效的策略,表现出明显的探索行为。


8. 总结

        随机网络蒸馏(RND)通过引入自监督的内在奖励机制,有效解决了强化学习中探索不足的问题。其核心思想是利用随机网络与可学习网络之间的输出差异来激励智能体探索,尤其适合外部奖励稀疏的场景。尽管 RND 在提高学习效率方面表现出色,但其计算开销和训练不稳定性仍需进一步研究和优化。随着强化学习技术的不断进步,RND 未来有望在更多实际应用中展现其潜力。

参考论文Exploration by Random Network Distillation, ICML 2019.


     文章若有不当和不正确之处,还望理解与指出。由于部分文字、图片等来源于互联网,无法核实真实出处,如涉及相关争议,请联系博主删除。如有错误、疑问和侵权,欢迎评论留言联系作者,或者关注VX公众号:Rain21321,联系作者。✨

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐