强化学习实践

0.杂谈:

前文发了几篇阅读笔记,大家可能只是觉得我只读了论文,就是个理论学派。

怕被大家瞧不起,所以跟大家分享一下我的学习过程,实际项目做了啥就不透露了:

  1. 先参考了Pytorch的教学自己改成项目所需的任务(DDQN之类的方法)。
  2. 觉得不行,到处都是一知半解的,所以开始查找了将近20多篇相关论文,把整体脉络,原理都梳理了一遍,发现好像DDPG方法好像挺行的。
  3. 把论文学完了就去搞了个DDPG,好像有点用,但好像项目有点急,然后就不做了,本着积累经验,把代码放这供自己参考。

正题开始:

1.强化学习

就我个人来看,强化学习只需要两件事:环境智能体

笔者所用的编程语言Python,也不是只会这一门语言,理由就不说了,主要是方便。

那么从Python编程的角度来说,我们所需要的就是建立两个类,一个就是环境class,一个就是智能体class,可以扔到两个包里面导入,这样写起来比较清晰。

接下来就是清楚地知道要在这两个类里面写些什么东西:

环境Class:

class Env:
    #必须的函数
    def __init__():#初始化
        self.info="write something which let someone else think you are professional"
        #定义环境状态
        self.state=None
    
    def step(action)#进行一步动作,如果是自定义的环境,就需要和智能体商量一下,怎么动作。
    def reset()#重置环境,便于重启一轮新的训练过程
    def reward()#定义奖励函数,设定自己的奖励方法,多步的,单步的,随机的,咋奖励都行,只是会影响训练结果罢了,炼丹,啥都得试试。
    
    
    #非必须的
    def display()#展示一下环境
    def update()#更新环境,实际上step函数里面就可以更新


    

智能体Class:

class AI_Player():
    def __init__():
        self.info="same thing"
    def select_action()#选动作。
    def memory()#记忆,便于自己训练。
    def train_self()#训练自己。

所谓DQN、DDQN、DDPG等方法,实际上就是智能体的内核不同,这一内核体现在网络结构、训练方法以及动作方法的不同,这几种方法我都尝试过,现在来给大家讲解一下。

2.DQN方法

Deep Q learning(DQN)方法是一种基于Q表的方法,即状态-动作-价值表,即在不同状态下采取不同动作所带来的价值。由于实际问题中的状态一般都是连续的,非离散的,所以常规的Q学习无法有效解决这类问题。但使用神经网络代替Q表却可以很好地解决这个问题,DQN方法的输入就是连续的状态,输出就是离散动作空间中的各动作的价值。

因此,我们可以首先构建一个DQN神经网络。

class DQN(nn.Module):
    def __init__(self,state_dim,num_actions):#初始化网络
        super(DQN,self).__init__()
        self.net=nn.Sequential(
            nn.Linear(state_dim,200),
            nn.ReLU(),
            nn.Linear(200,300),
            nn.ReLU(),
            nn.Linear(300,200),
            nn.ReLU(),
            nn.Linear(200,num_actions),
        )
    def forward(self,x):#前向传播
        result=self.net(x)
        return result

构建了神经网络还需要训练样本,而这一样本的产生过程就是强化学习了,即使用智能体与环境不断交互,产生样本,然后进行学习,下面写一下强化学习过程

#实例化一个我们的环境类和AI类,其中包含了各种方法。
env=Env()
AI=AI_player()
#重置一下环境
state,info=env.reset()

#定义AI强化学习过程的次数
num_episodes=2000

for i_episode in range(num_episodes):
    state,info=env.reset()
    for t in count():
        #AI给出动作
        action_id=AI.select_action(state)
        action=AI.actions[int(action_id)]
        #这一动作作用给环境,给出动作奖励。
        observation,reward,done=env.step(action)
        reward=torch.tensor([reward],device=device)
        #记录后续动作
        if done:
            next_state=None
        else:
            next_state=observation
        #记忆
        AI.cache(state,action_id,next_state,reward)
        
        state=next_state
        
        AI.optimize_model()
        
        #用旧网络参数预测下一步的价值,可以起到很好的防止乐观估计的作用。
        target_net_state_dict=AI.target_net.state_dict()
        policy_net_state_dict=AI.policy_net.state_dict()
        for key in policy_net_state_dict:
            target_net_state_dict[key] = policy_net_state_dict[key]*AI.TAU + target_net_state_dict[key]*(1-AI.TAU)
        AI.target_net.load_state_dict(target_net_state_dict)
        #环境给出终止条件。
        if done:
            break

然后就是我们具体AI的代码内容了:

#命名数组
Transition = namedtuple('Transition',('state', 'action', 'next_state', 'reward'))

#定义记忆类
class ReplayMemory(object):

    def __init__(self, capacity):#初始化
        self.memory = deque([], maxlen=capacity)

    def push(self, *args):#增加一个样本
        """Save a transition"""
        self.memory.append(Transition(*args))

    def sample(self, batch_size):#随机选取案例
        return random.sample(self.memory, batch_size)

    def __len__(self):#输出长度
        return len(self.memory)

#定义AI智能体类:
class AI_player():
    def __init__(self,state_dims=500,memory_length=10000,Train=True):#策略网络、价值网络和记忆容量
        self.info="something make no sense"
        self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
        
        self.state_dims=state_dims#定义卷积输出的数量
        self.conv2d=[]
        self.actions={
            0:[0.1,0,0],
            1:[-0.1,0,0],
            2:[0,0.1,0],
            3:[0,-0.1,0],
            4:[0,0,0.1],
            5:[0,0,-0.1],}#定义动作空间,表示三维空间的移动比例。
        
        #policy网络用于直接给出动作价值,而target网络则是预测后一步的动作价值。
        self.policy_net=DQN(self.state_dims,len(self.actions)).to(self.device)#初始化策略网络
        self.target_net=DQN(self.state_dims,len(self.actions)).to(self.device)#初始化价值网络
        self.target_net.load_state_dict(self.policy_net.state_dict())
        
        self.steps_done=0         #已进行的步骤数
        self.BATCH_SIZE = 256       #训练时所使用的样本规模
        self.GAMMA = 0.99         #未来价值权重值
        self.EPS_START = 0.5     #开始时随机尝试概率
        self.EPS_END = 0.05       #最低随机尝试概率
        self.EPS_DECAY = 10       #降低速率 
        self.TAU = 0.005          #价值网络更新速率
        self.LR = 1e-4          #学习率
        if Train!=True:
            self.EPS_START = 0      
            self.EPS_END = 0        
        #定义AI的记忆和大脑容量
        self.memory_length=memory_length
        self.memory=ReplayMemory(self.memory_length)
        self.optimizer = optim.AdamW(self.policy_net.parameters(), lr=self.LR, amsgrad=True)#定义优化器

上面是记忆结构体定义和智能体初始化的代码内容,下面则是智能体的动作选择函数和训练自己的各种方法:

    #选择动作,有随机选择的概率,不过随着步数增多,随机性减弱
    def select_action(self,state):
        state=self.pretreat_data(state)
        sample=random.random()
        eps_threshold=self.EPS_END+(self.EPS_START-self.EPS_END)*\
        math.exp(-1*self.steps_done/self.EPS_DECAY)
        self.steps_done+=1
        if sample>eps_threshold:
            with torch.no_grad():
                return torch.tensor([[torch.argmax(self.policy_net(state))]])
        else:
            return torch.tensor([[random.randint(0,len(self.actions)-1)]])
        
    def pretreat_data(self,state):
        t=[]
        for i in range(len(state)):         
            if len(state[i])<self.out_facets:
                tmp=torch.tensor(state[i],dtype=torch.float)
                zero=torch.zeros([self.out_facets-len(state[i]),3,3],dtype=torch.float)
                t+=[torch.cat([tmp,zero]).view(-1)]
   
            else:
                tmp=torch.tensor(state[i],dtype=torch.float)[torch.randperm(self.out_facets)]
                t+=[tmp.view(-1)]
        data=torch.cat(t).to(self.device)
        data=(data-torch.min(data))/(torch.max(data)-torch.min(data))
        return data
    #存储记忆
    def cache(self,state,action_id,next_state,reward):
        if next_state==None:
            self.memory.push(self.pretreat_data(state),action_id,next_state,reward)
        else:
            self.memory.push(self.pretreat_data(state),action_id,self.pretreat_data(next_state),reward)
    def optimize_model(self):
        if len(self.memory)<self.BATCH_SIZE:
            return
        #提取记忆进行训练
        transitions=self.memory.sample(self.BATCH_SIZE)
        
        batch=Transition(*zip(*transitions))

        non_final_mask = torch.tensor(tuple(map(lambda s: s is not None,
                                              batch.next_state)), device=self.device, dtype=torch.bool)
        non_final_next_states = torch.cat([s for s in batch.next_state
                                                    if s is not None]).reshape(-1,self.state_dims)


        state_batch = torch.cat(batch.state).reshape(-1,self.state_dims)
        action_batch = torch.cat(batch.action).to(self.device)
        reward_batch = torch.cat(batch.reward).to(self.device)
        
        state_action_values=self.policy_net(state_batch).gather(1,action_batch)
        next_state_values=torch.zeros(self.BATCH_SIZE,device=self.device)

        #计算价值,包括了后续价值的计算,具体原理可以查贝尔曼方程。
        with torch.no_grad():
            next_state_values[non_final_mask] = self.target_net(non_final_next_states).max(1)[0]
            expected_state_action_values = (next_state_values * self.GAMMA) + reward_batch

        #损失函数
        #criterion = nn.SmoothL1Loss()
        criterion = nn.MSELoss()
        loss = criterion(state_action_values, expected_state_action_values.unsqueeze(1)) 
        self.optimizer.zero_grad()
        loss.backward()
        #梯度裁剪
        #torch.nn.utils.clip_grad_value_(self.policy_net.parameters(), 100)
        self.optimizer.step()

然后,就让智能体和环境进行交互,再让环境给出奖励即可完成强化学习的过程,如你所见,强化学习就是这么简单。

至于本文的环境部分,为了避免惹上不必要的麻烦,就暂不贴出了,如有疑惑可以私下解答。

3.DDQN方法

所谓DDQN方法与DQN方法的不同,就是DDQN全程叫做Double Deep Q learning 方法,而我当时学习的时候就有这样的疑惑,DQN方法也有两个Q网络啊,这啥意思啊。

后来在论文里才明白,人家两个网络的使用方式是不同的。DDQN和DQN的区别就在于Target Net的使用方法,DQN是直接让Target网络给出最大q值,而DDQN则是让Policy网络选出下一步动作,然后让Target网络评估这个动作的价值作为后一阶段的价值,这么一来就有效降低了乐观估计的方法,在论文里提到,这个改动非常的小,也非常的简单,只需要将后续价值的计算方式一改就可以了,不再取最大值,而是预测一下下一步的动作,然后再让Target网络评估一下,懒得写了,对这个问题也没啥用,所以就给大家当参考了。

4.至于为啥DQN这类Value-based方法对我们这个问题严重不适用呢,是因为我们这个问题根本就不是一个离散动作空间能解决的问题,而是一个连续的动作空间问题,我一直以为强化学习解决不了连续空间的问题,直到阅读了大量文献之后,才发现了终极大杀器,DPG,Deterministic Policy Gradient 确定型策略梯度。这就解决了连续空间的强化学习问题,通过这一策略梯度法,我们就可以训练神经网络了,即诞生了Deep DPG (DDPG) ,它还有另一个响当当的名字,Actor-Critic(AC)算法,还有多个版本,分为了AC、A2C、A3C。

这个东西有多好玩呢,它可以直接输出连续的动作值,比如让AI玩游戏,DQN的AI只会一次走一步、两步、三步,这些步数都是写好的,不会边,因此需要n个动作输出。但是DDPG的AI就可以只有一个输出,这个输出直接就是走1.234235235米,走3.1413412312米,看到没有,直接输出的就是连续值,这相当离谱。

然后就有人问了,你给它个奖励,它也没法训练啊,这个时候就可以看看DDPG算法的原理了,AI实际上分为了两个,一个叫做Critic,一个叫做Actor,Actor负责根据现状给出连续的动作价值,而Critic负责根据现在的状态和Actor的动作进行打分,而这个打分就和奖励值有关了。

Critic根据环境给出的奖励优化自身,从而变得越来越专业,然后它就可以告诉Actor如何改进自己的动作,即给出Critic网络的梯度上升方向。

大概原理就是这样,废话不多说了,直接上代码:

首先是Actor和Critic两个网络的定义:

#Actor网络
class Actor(nn.Module):
    def __init__(self,state_dims,action_dims,max_value):
        self.max_value=max_value
        super(Actor,self).__init__()
        self.policy_net=nn.Sequential(
            nn.Linear(state_dims,500),
            nn.ReLU(),
            nn.Linear(500,500),
            nn.ReLU(),
            nn.Linear(500,500),
            nn.ReLU(),
            nn.Linear(500,action_dims),
            nn.Tanh(),
        )
    def forward(self,x):
        result=self.max_value*self.policy_net(x)
        return result
#Critic网络
class Critic(nn.Module):
    def __init__(self,state_dims,action_dims):
        super(Critic,self).__init__()
        self.value_net=nn.Sequential(
            nn.Linear(state_dims+action_dims,500),
            nn.ReLU(),
            nn.Linear(500,500),
            nn.ReLU(),
            nn.Linear(500,500),
            nn.ReLU(),
            nn.Linear(500,1),
            #nn.Tanh(),
            #nn.Sigmoid(),
        )
    def forward(self,x):
        return self.value_net(x)

然后给大家加个餐,还可以再加一个网络,观测网络,如果大家了解过Transformer模型的话,就知道,这俩基本没啥关系,而是和里面的Emebedding层有关系,本文这个问题包含了一个大型的三维01矩阵,所以使用一个观测网络降个维度不过分吧。

#embedding 网络
class Eye(nn.Module):
    def __init__(self,voxel_scale,out_features):
        super(Eye,self).__init__()
        self.net=nn.Sequential(
            nn.Conv3d(1, 64, kernel_size=(4,4,4), stride=4, padding=4),
            nn.MaxPool3d((4, 4, 4), stride=(4, 4, 4)),
            nn.Conv3d(64, 128, kernel_size=(4,4,4), stride=4, padding=4),
            #nn.MaxPool3d((4, 4, 4), stride=(4, 4, 4)),
            nn.Flatten(),
            nn.Linear(1024,1000),
            nn.ReLU(),
            nn.Linear(1000,1000),
            nn.ReLU(),
            nn.Linear(1000,1000),
            nn.ReLU(),
            nn.Linear(1000,out_features),
        )
    def forward(self,x):
        return self.net(x)

然后就是智能体了,智能体的动作要改改了,这次直接输出了动作的具体值,直接跟环境进行交互就得了,写得多了,有点懒了,还有其他事情要忙,我猜也没什么人看,也猜不出来我们是在干啥。我就直接贴代码了。

#AI_Player
class AI_Player():
    def __init__(self,out_features=500,memory_length=10000,voxel_scale=40,Train=True):#感知特征数量,记忆容量、体素网格大小、是否训练
        self.info="something useless"
        self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
      
        self.out_features = out_features#感知层输出特征数量
        self.voxel_scale = voxel_scale#体素网格大小
        self.move_size=0
        self.actions={
            #移动Policy_gradient
            0:"左右移",
            1:"上下移",
            2:"前后移",
            #3:"旋转value_method"
        }#定义动作空间。
        
        
        
        self.eye=Eye(self.voxel_scale,self.out_features).to(self.device)
        self.actor=Actor(self.out_features,len(self.actions),self.voxel_scale).to(self.device)
        self.critic=Critic(self.out_features,len(self.actions)).to(self.device)
        
        
        self.steps_done=0         #已进行的步骤数
        self.MIN_BATCH=40
        self.MAX_BATCH=512
        self.BATCH_SIZE = 40       #训练时所使用的样本规模
        self.GAMMA = 0.99         #未来价值权重值
        self.EPS_START = 0.95    #开始时随机尝试概率
        self.EPS_END = 0.05       #最低随机尝试概率
        self.EPS_DECAY = 100       #降低速率 
        self.TAU = 0.005          #价值网络更新速率
        self.LR = 1e-4          #学习率
        if Train!=True:
            self.EPS_START = 0      
            self.EPS_END = 0 
        
        #定义AI的记忆和大脑容量
        self.memory_length=memory_length
        self.memory=ReplayMemory(self.memory_length)

        #定义优化器,优化两个网络同时优化感知网络,但后来发现,实际上两个网络的评价方法不同,还是让Critic网络来反向传播优化得了,毕竟在实际问题中,比如跳水比赛、体操比赛啥的,管你选手自己咋看,评委打分。
        #self.actor_optimizer = optim.AdamW([{'params': self.eye.parameters()},{'params': self.actor.parameters()}], lr=self.LR, amsgrad=True)
        self.actor_optimizer = optim.AdamW([{'params': self.actor.parameters()}], lr=self.LR, amsgrad=True)
        self.critic_optimizer= optim.AdamW([{'params': self.eye.parameters()},{'params': self.critic.parameters()}], lr=self.LR, amsgrad=True)
        
    def reset():
        self.steps_done=0
        
    def select_action(self,state):
        state=self.pretreat_data(state).to(self.device)#预处理体素化
        state=self.eye(state)#进入网络进行感知
        sample=random.random()#生成随机数判定是否需要随机产生策略
        eps_threshold=self.EPS_END+(self.EPS_START-self.EPS_END)*\
        math.exp(-1*self.steps_done/self.EPS_DECAY)#随机策略发生率递减
        self.steps_done+=1
        if sample>eps_threshold:
            with torch.no_grad():
                #print(state)
                return self.actor(state).view(-1)
        else:
            return torch.tensor([self.voxel_scale*random.randint(-100,100)/100 for i in range(3)])
        
        
        
        
    def pretreat_data(self,state):
        #在此处进行体素化
        data=norm_voxelized(state,self.voxel_scale)
        #print(data)
        t=torch.tensor(np.array(data[0])).view(1,1,self.voxel_scale,self.voxel_scale,self.voxel_scale).to(torch.float32)
        self.move_size=data[1]
        return t
    
    
    def cache(self,state,action_id,next_state,reward):
        if reward<1e-8:
            return 
        if next_state==None:
            self.memory.push(self.pretreat_data(state),action_id,next_state,reward)
        else:
            self.memory.push(self.pretreat_data(state),action_id,self.pretreat_data(next_state),reward)    
        
        
    
    
    def optimize_model(self):
        if len(self.memory)<self.MIN_BATCH:
            return 0
        transitions=self.memory.sample(min(len(self.memory),self.MAX_BATCH))
        
        
        batch=Transition(*zip(*transitions))
        #print(batch)
        non_final_mask = torch.tensor(tuple(map(lambda s: s is not None,
                                              batch.next_state)), device=self.device, dtype=torch.bool)
        non_final_next_states = torch.cat([s for s in batch.next_state if s is not None])\
        .view(-1,1,self.voxel_scale,self.voxel_scale,self.voxel_scale).to(self.device)
        
        
        state_batch=torch.cat(batch.state).view(-1,1,self.voxel_scale,self.voxel_scale,self.voxel_scale).to(self.device)
        #action_batch=torch.cat(batch.action).view(-1,len(self.actions)).to(self.device)
        
        reward_batch=torch.cat(batch.reward).to(self.device)
        
        
        #首先使用数据在观测网络里面跑一遍,把这个观测结果放给Critic网络,然后让Critic网络跑一遍,这个时候Critic网络的损失函数使用-Mean(q)就可以给出梯度上升方向,所以就是Actor网络的优化方向,由于神经网络的数据计算过程中,梯度都已经保存在网络参数里面了,可以理解为数据流过网络一次,就会留下一次权重,给出损失函数直接优化即可。
        observ=self.eye(state_batch)
        action_batch=self.actor(observ)
        action_state_batch=torch.cat([action_batch,observ],dim=1)
        actor_loss=-torch.mean(self.critic(action_state_batch))
        self.actor_optimizer.zero_grad()
        actor_loss.backward()
        
        #torch.nn.utils.clip_grad_value_(self.actor.parameters(), self.move_size)
        
        
        self.actor_optimizer.step()
        
        #优化Critic网络。
        
        observ=self.eye(state_batch)
        action_batch=torch.cat(batch.action).view(-1,len(self.actions)).to(self.device)
        action_state_batch=torch.cat([action_batch,observ],dim=1)
        state_action_values=self.critic(action_state_batch)
        criterion = nn.SmoothL1Loss()
        critic_loss=criterion(state_action_values,reward_batch.unsqueeze(1))
        
        self.critic_optimizer.zero_grad()
        critic_loss.backward()
        #torch.nn.utils.clip_grad_value_(self.critic.parameters(), 10)
        self.critic_optimizer.step()
        #print(f"actor_loss={actor_loss.item()} , critic_loss={critic_loss.item()}")
        return critic_loss.item()

最后,受算力限制和种种条件限制,我们的AC算法没有取得应有的效果,不过这个技术路线基本上是行得通的,本文所提出两种方法也是基本方法,如果环境和智能体训练方法和网络结构合理的话,基本上可以解决80%以上的需要强化学习的问题。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值