24/8/15算法笔记 DQN算法

DQN(Deep Q-Network)算法是一种结合了深度学习和强化学习的技术,用于解决具有高维观测空间的强化学习问题。DQN算法通过使用深度神经网络来近似Q函数(也称为动作价值函数),在许多领域取得了突破性的成果,特别是在游戏和机器人控制方面。以下是DQN算法的关键特点:

  1. 深度Q网络:DQN使用一个深度神经网络来学习状态-动作对的价值函数,即 Q(s,a)Q(s,a)。

  2. 经验回放:DQN算法使用一个经验回放缓冲区来存储过去的转换(状态、动作、奖励、下一个状态),这有助于提高数据的利用率并减少训练过程中的方差。

  3. 目标网络:DQN使用两个相同的神经网络,一个用于预测 QQ 值(主网络),另一个作为目标网络。目标网络的参数定期从主网络复制过来,以稳定训练过程。

  4. Bellman方程:DQN算法通过最小化Bellman方程的误差来更新网络权重。

  5. 损失函数:DQN的损失函数通常使用均方误差(MSE)来衡量预测的 QQ 值和目标 QQ 值之间的差异。

  6. 探索与利用:DQN通常使用ε-贪心策略来平衡探索和利用。

  7. 训练过程

    • 从环境中获取状态 ss 并选择动作 aa。
    • 执行动作并观察奖励 rr 和下一个状态 s′s′。
    • 使用 s,a,r,s′s,a,r,s′ 更新经验回放缓冲区。
    • 从缓冲区中随机采样一批数据来计算损失并更新网络。
  8. 稳定性:DQN算法通过目标网络和经验回放来提高训练过程的稳定性。

  9. 应用:DQN算法已被成功应用于各种游戏和模拟环境中,例如Atari 2600游戏和一些3D游戏。

  10. 变体:DQN算法有多种变体,如Double DQN、Dueling DQN和Prioritized Experience Replay,这些变体在不同方面改进了原始DQN算法的性能。

import gym
from matplotlib import pyplot as plt

#创建环境
env = gym.make('CartPole-v0')
env.reset()
#打印游戏
def show():
    plt.imshow(env.render(mode='rgb_array'))
    plt.show()
    
show()
#测试游戏环境
def test_env():
    state = env.reset()
    print('state=',state)

    print('env.action_space=',env.action_space)

    print('随机一个动作')
    action = env.action_space.sample()
    print('action',action)

    print('执行一个动作,得到下一个状态,奖励,是否结束')
    state,reward,over,_=env.step(action)
    print('state=',state)

    print('reward',reward)
    print('over',over)
test_env()
#创建神经网络模型,动作模型,经验模型
import torch
#计算动作的模型,也是真正要用的模型
model = torch.nn.Sequential(
    torch.nn.Linear(4,128),
    torch.nn.ReLu(),
    torch.nn.Linear(128,2),
)
#经验网络,用于评估一个状态的分数
next_model = torch.nn.Sequential(
    torch.nn.Linear(4,128),
    torch.nn.ReLU(),
    torch.nn.Linear(128,2),
)
#把model的参数复制给next_model
next_model.load_state_dict(model.state_dict())#load_state_dict() 方法用于加载模型的参数。当你想要将一个模型的参数复制给另一个模型时,可以使用这个方法。

model,next_model
import random
#得到一个动作
def get_action(state):
    if random.random()<0.01:
        return random.choice([0,1])
    
    #走神经网络,得到一个动作
    state = torch.FloatTensor(state).reshape(1,4)

    return model(state).argmax().item()
get_action([0.0012=3847,-0.01194451,0.04260966,0.00688801])

  1. 探索概率

    • if random.random() < 0.01:这行代码检查一个随机数是否小于0.01,如果是,智能体将进行探索。0.01是探索的概率阈值。
  2. 随机选择动作

    • return random.choice([0, 1]):如果智能体选择探索,它将随机选择一个动作,这里的动作集合是 [0, 1]。这意味着动作空间被限制为两个可能的动作。
  3. 利用策略

    • 如果随机数大于或等于0.01,智能体将利用其当前的知识来选择最佳动作。
  4. 状态转换

    • state = torch.FloatTensor(state).reshape(1, 4):将当前状态转换为一个 PyTorch 浮点张量,并重塑为尺寸为 (1, 4) 的张量。这通常是为了匹配神经网络的输入尺寸。
  5. 神经网络预测

    • return model(state).argmax().item():将状态输入到神经网络 model 中,获取每个可能动作的预测值,然后选择具有最高预测值的动作。argmax().item() 返回具有最大值的动作索引。
#更新数据样本池函数,责备离线学习
#样本池
datas=[]

#向样本池添加N条数据,删除M条最古老的数据
def update_date():
    old_count = len(datas)

    #玩到新增了N个数据为止
    while len(datas) - old_count<200:
        #初始化游戏
        state = env.reset()

        #玩到游戏结束为止
        over = False
        while not over:
            #根据当前状态得到一个动作
            action = get_action(state)

            #执行动作,得到反馈
            next_state,reward,over,_=env.step(action)

            #记录数据样本
            datas.append((state,action,reward,next_state,over))

            #更新游戏状态,开始下一个动作
            state = next_state
    update_count = len(datas)-old_count
    drop_count = max(len(datas)-10000,0)

    #数据上限,超出时从最古老的开始删除
    while len(datas)>10000:
        datas.pop(0)
    return update_count,drop_count
#数据采样函数
#获取一批数据样本
#get_sample 函数是一个用于从数据集 datas 中随机抽取样本的函数,并将这些样本转换成适合深度学习模型输入的 PyTorch 张量格式
def get_sample():
    samples = random.sample(datas,64)
    #[b,4]
    state = torch.FloatTensor([i[0]for i in samples]).reshape(-1,4)
    #[b,1]
    action = torch.LongTensor([i[1]for i in samples]).reshape(-1,1)
    #[b,1]
    reward = torch.FloatTensor([i[2]for i in samples]).reshape(-1,1)
    #[b,4]
    next_state = torch.FloatTensor([i[3]for i in samples]).reshape(-1,4)
    #[b,1]
    over = torch.LongTensor([i[4]for i in samples]).reshape(-1,1)

    return state,action,reward,next_state,over

state,action,reward,next_state,over=get_sample()

state,action,reward,next_state,over
#计算value函数
def get_value(state,action):
    #使用状态计算出动作的logits
    #[b,4]->[b,2]
    value = model(state)

    #根据实际使用的action取出每一个值
    #这个值就是模型评估的在该状态下,执行动作的分数
    #在执行动作前,显然并不知道会得到的反馈和next_state
    #所以这里不能也不需要考虑next_state和reward
    #[b,2]->[b,1]
    value = value.gather(dim = 1,index=action)

    return value
get_value(state,action).shape
  1. 模型预测

    • value = model(state):使用模型对状态 state 进行预测,得到一个包含每个可能动作的值的张量。假设模型输出的尺寸是 [b, 2],其中 b 是批量大小,2 是动作的数量。
  2. 索引选择

    • value = value.gather(dim=1, index=action):使用 gather 方法根据 action 索引从模型输出中选择对应的值。这里假设 action 是一个长整型张量,其尺寸为 [b, 1]
#计算target函数
def get_target(reward,next_state,over):
    #上面已经把模型认为的状态下执行动作的分数给评估出来了
    #下面使用next_state和reward计算真实的分数
    #针对一个状态,它到底应该多少分,可以使用以往模型积累的经验评估
    #这也是没办法的办法,因为显然没有精确解,这里使用延迟更新的next_model评估

    #使用next_state计算下一个状态的分数
    #[b,4}->[b,2]
    with torch.no_grad():
        target = next_model(next_state)

    #取所有动作中分数最大的
    #[b,2]-[b,1]
    target = target.max(dim=1)[0]
    target = target.reshape(-1,1)

    #下一个状态的分数乘以一个系数,相当于权重
    target*=0.98

    #如果next_state已经游戏结束,则next_state分数是0
    #因为如果下一步已经游戏结束,显然不需要再继续下去,也就不需要考虑next_state了
    #[b,1]*[b,1]->[b,1]
    target *=(1-over)

    #加上reward就是最终的分数
    #[b,1]+[b,1]->[b,1]
    target+=reward 
    return target

  1. 使用 next_model 预测下一个状态的值

    • target = next_model(next_state):使用 next_model 对下一个状态 next_state 进行预测,得到每个可能动作的值。
  2. 选择最佳动作的值

    • target = target.max(dim=1)[0]:从预测的值中选择每个状态的最大动作值。max(dim=1) 返回最大值和索引,[0] 选择最大值。
  3. 重塑目标张量

    • target = target.reshape(-1, 1):将目标张量重塑为二维张量,每行一个值。
  4. 应用折扣因子

    • target *= 0.98:将目标值乘以折扣因子 0.98,这是强化学习中的常见做法,用于计算未来奖励的当前价值。
  5. 处理游戏结束的情况

    • target *= (1 - over):如果 over 是一个表示游戏是否结束的二进制张量(1 表示结束,0 表示未结束),这行代码将结束状态的目标值设置为 0。
  6. 计算最终的目标值

    • target += reward:将即时奖励加到目标值上,得到最终的TD目标。
  7. 返回值

    • 函数返回 target,这是每个样本的TD目标值。

测试函数

from IPython import display
def test(play):
    #初始化游戏
    state = env.reset()

    #记录反馈值的和,这个值越大越好
    reward_sum = 0

    #玩到游戏结束为止
    over = False
    while not over:
        #根据当前状态得到一个动作
        action = get_action(state)

        #执行动作,得到反馈
        state,reward,over,_ env.step(action)
        reward_sum+=reward

        #打印动画
        if play and random.random()<0.1:#跳帧
            display.clear_output(wait = True)
            show()
    return reward_sum

训练函数

def train():
    model.train()
    optimizer = torch.optim.Adam(model.parameters(),lr = 2e-3)
    loss_fn = torch.nn.MSELoss()

    #训练N次
    for epoch in range(500):
        #更新N条数据
        update_count,drop_count = update_data()

        #每次更新过数据后,学习N次
        for i in range(200):
            #采样一批数据
            state, action,reward,next_state,over = get_sample()

            #计算一批样本的value和target
            value = get_value(state,action)
            target = get_target(reward,next_state,over)

            #更新参数
            loss = loss_fn(value,target)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            #把model参数复制给next_model
            if(i+1)%10==0:
                next_model.load_state_dict(model.state_dict())

        if epoch % 50==0:
            test_result =sum([test(play = False)for _ in range(20)])/20
            print(spoch.len(datas),update_count,drop_count,test_result)

DQN改进算法doubleDQN

def get_target(reward,next_state,over):
    #上面已经把模型认为的状态下执行动作的分数给评估出来了
    #下面使用next_state和reward计算真实的分数
    #针对一个状态,它到底应该多少分,可以使用以往模型积累的经验评估
    #这也是没办法的办法,因为显然没有精确解,这里使用延迟更新的next_model评估

    #使用next_state计算下一个状态的分数
    #[b,4}->[b,2]
    with torch.no_grad():
        target = next_model(next_state)
    """以下是主要的DoubleDQN和DQN的区别"""
    #取所有动作中分数最大的
    #[b,11]-[b]
    #target = target.max(dim=1)[0]
    #使用model计算下一个状态的分数
    #[b,3]->[b,11]
    with torch.no_grad():
        model_target = model(next_state)
    
    #取分数最高的下标
    #[b,11]->[b,1]
    model_target = model_target.max(dim=1)[1]
    model_target = model_target.reshape(-1,1)

    #以这个下标取next_value当中的值
    #[b,11]->[b]
    target = target.gather(dim=1,index=model_target)
    """以上是区别"""
    #下一个状态的分数乘以一个系数,相当于权重
    target*=0.98

    #如果next_state已经游戏结束,则next_state分数是0
    #因为如果下一步已经游戏结束,显然不需要再继续下去,也就不需要考虑next_state了
    #[b,1]*[b,1]->[b,1]
    target *=(1-over)

    #加上reward就是最终的分数
    #[b,1]+[b,1]->[b,1]
    target+=reward 
    return target

DuelingDQN改进

class Vent(torch.nn.Model):
    def __init__(self):
        super().__init__()

        self.fc = torch.nn.Sequential(
            torch.nn.Linear(3,128),
            torch.nn.ReLU(),
        )

        self.fc_A = torch.nn.Linear(128,11)
        self.fc_V = torch.nn.Linear(128,1)

    def forward(self,x):
        #[5,11]->[5,128]->[5,11]
        A = self.fc_A(self.fc(x))

        #[5,11]->[5,128]->[5,1]
        V = self.fc_V(self.fc(x))
        
        #[5,11]->[5]->[5,1]
        A_mean = A.mean(dim=1).reshape(-1,1)

        #[5,11]->[5,128]->[5,11]
        A -=A_mean

        #Q值由V值计算得到
        #[5,11]+[5,1]=[5,11]
        Q = A+V
        return Q
  1. 初始化 (__init__ 方法):

    • 调用基类的初始化方法。
    • 定义一个顺序模型 self.fc,包含一个线性层和ReLU激活函数,用于特征提取。
  2. 动作价值层 (self.fc_A):

    • 一个线性层,将输入特征从128维映射到11维,对应每个动作的价值。
  3. 状态价值层 (self.fc_V):

    • 一个线性层,将输入特征从128维映射到1维,对应当前状态的全局价值。
  4. 前向传播 (forward 方法):

    • 输入 x 通过 self.fc 进行特征提取,然后分别通过 self.fc_A 和 self.fc_V 得到动作价值和状态价值。
  5. 动作价值的均值 (A_mean):

    • 计算 self.fc_A 输出的均值,并重塑以匹配原始形状。
  6. 中心化动作价值

    • 从原始动作价值中减去均值,进行中心化处理。
  7. 计算Q值

    • 将中心化后的动作价值与状态价值相加,得到最终的Q值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值