MAML-RL Pytorch 代码解读 (15) – maml_rl/episode.py


文章目录

  • MAML-RL Pytorch 代码解读 (15) -- maml_rl/episode.py
  • 基本介绍
  • 源码链接
  • 文件路径
  • `import` 包
  • `BatchEpisodes()` 类


基本介绍

在网上看到的元学习 MAML 的代码大多是跟图像相关的,强化学习这边的代码比较少。

因为自己的思路跟 MAML-RL 相关,所以打算读一些源码。

MAML 的原始代码是基于 tensorflow 的,在 Github 上找到了基于 Pytorch 源码包,学习这个包。

文件路径

./maml_rl/episode.py

import

import numpy as np
import torch
import torch.nn.functional as F

BatchEpisodes()

class BatchEpisodes:

    #### 主要是对一批任务跑完之后,存在的很多(数量=batch_size)的回合信息做处理的类。首先初始化batch_size也就是批处理的大;设置折扣率gamma=0.95;设置运算设备默认是'cpu'。
	def __init__(self, batch_size, gamma=0.95, device='cpu'):
		self.batch_size = batch_size
		self.gamma = gamma
		self.device = device

		#### 以列表的数据结构存储数据,每个列表里面包含batch_size个空列表。
        # [[], [],...batch_size of []]
		self._observations_list = [[] for _ in range(batch_size)]
		self._actions_list = [[] for _ in range(batch_size)]
		self._rewards_list = [[] for _ in range(batch_size)]
		self._mask_list = []

        #### 默认设置观测信息、动作信息、奖励信息、回报信息(_returns)和掩码(_mask)。
		self._observations = None
		self._actions = None
		self._rewards = None
		self._returns = None
		self._mask = None

	@property
	def observations(self):
        
        #### 如果self._observations标记是有"None",则从self._observations_list的第一个子列表的第一个元素中获得状态大小。len(self)未知是啥,可能是受property装饰器的影响。生成了一个大小是[len(self),self.batch_size,observation_shape]的数据池。
		if self._observations is None:
			observation_shape = self._observations_list[0][0].shape
			observations = np.zeros((len(self), self.batch_size)
			                        + observation_shape, dtype=np.float32)
            
            #### 获得每个子集里面回合的长度,将列表里面第i个子集的任务的观测信息存储在observations池中
			for i in range(self.batch_size):
				length = len(self._observations_list[i])
				observations[:length, i] = np.stack(self._observations_list[i], axis=0)
                
            #### 转换成张量数据结构。
			self._observations = torch.from_numpy(observations).to(self.device)
		return self._observations

	@property
	def actions(self):
        
        #### 如果self._actions标记是有"None",则从self._actions_list的第一个子列表的第一个元素中获得动作大小。len(self)未知是啥,可能是受property装饰器的影响。生成了一个大小是[len(self),self.batch_size,action_shape]的数据池。
		if self._actions is None:
			action_shape = self._actions_list[0][0].shape
			actions = np.zeros((len(self), self.batch_size)
			                   + action_shape, dtype=np.float32)
            
            #### 获得每个子集里面回合的长度,将列表里面第i个子集的任务的动作信息存储在actions池中
			for i in range(self.batch_size):
				length = len(self._actions_list[i])
				actions[:length, i] = np.stack(self._actions_list[i], axis=0)
                
            #### 转换成张量数据结构。
			self._actions = torch.from_numpy(actions).to(self.device)
		return self._actions

	@property
	def rewards(self):
        
        #### 如果self._rewards标记是有"None",则生成了一个大小是[len(self),self.batch_size]的数据池。因为奖励函数是标量。
		if self._rewards is None:
			rewards = np.zeros((len(self), self.batch_size), dtype=np.float32)
            
            #### 获得每个子集里面回合的长度,将列表里面第i个子集的任务的奖励信息存储在rewards池中
			for i in range(self.batch_size):
				length = len(self._rewards_list[i])
				rewards[:length, i] = np.stack(self._rewards_list[i], axis=0)
                
            #### 转换成张量数据结构。
			self._rewards = torch.from_numpy(rewards).to(self.device)
		return self._rewards

	@property
	def returns(self):
        
        #### 如果self._returns标记是有"None",则生成了一个大小是[len(self),self.batch_size]的数据池。因为回报函数是标量。将rewards从torch的张量结构转变为numpy的数据结构,将mask从torch的张量结构转变为numpy的数据结构。
		if self._returns is None:
			return_ = np.zeros(self.batch_size, dtype=np.float32)
			returns = np.zeros((len(self), self.batch_size), dtype=np.float32)
			rewards = self.rewards.cpu().numpy()
			mask = self.mask.cpu().numpy()
            
            #### 对这批任务里面的所有位置都计算回报,记录到returns的numpy数组当中。
			for i in range(len(self) - 1, -1, -1):
				return_ = self.gamma * return_ + rewards[i] * mask[i]
				returns[i] = return_
            
            #### 最后再将numpy结构转变成了torch结构
			self._returns = torch.from_numpy(returns).to(self.device)
		return self._returns

	@property
	def mask(self):
        
        #### 如果self._mask标记是有"None",则生成了一个大小是[len(self),self.batch_size]的数据池。因为掩码是标量。对每个掩码都做做处理,设置成数值1.0。转换成张量数据结构。
		if self._mask is None:
			mask = np.zeros((len(self), self.batch_size), dtype=np.float32)
			for i in range(self.batch_size):
				length = len(self._actions_list[i])
				mask[:length, i] = 1.0
			self._mask = torch.from_numpy(mask).to(self.device)
		return self._mask

	def gae(self, values, tau=1.0):
        
        #### 这个库用于处理很长的episode时,后面的状态的估计处理。
		"""

		:param values: [200, 20, 1], tensor
		:param tau:
		:return:
		"""
		# Add an additional 0 at the end of values for
		# the estimation at the end of the episode
        
        #### torch.nn.functional.pad是PyTorch内置的矩阵填充函数,第一个参数input:需要进行填充的Tensor数据;第二个参数pad:进行pad的元组(注意这里版本变了以后建议用列表类型),元组中元素个数小于等于input维度的2倍。如:input是2维的,则pad可以最多有4个元素;input是5维的,pad可以最多有10个元素。
		values = values.squeeze(2).detach() # [200, 20]
		values = F.pad(values * self.mask, (0, 0, 0, 1)) # [201, 20]

        #### 计算价值误差。设置一个advantages数组,计算每次乘以折扣率和tau数值的个gae数据。最后返回advantage数据。
		deltas = self.rewards + self.gamma * values[1:] - values[:-1] # [200, 20]
		advantages = torch.zeros_like(deltas).float() # [200, 20]
		gae = torch.zeros_like(deltas[0]).float() # [20]
		for i in range(len(self) - 1, -1, -1):
			gae = gae * self.gamma * tau + deltas[i]
			advantages[i] = gae

		return advantages

    #### 将观测信息、动作信息、奖励信息和批号捆绑起来,传入各自的列表中。
	def append(self, observations, actions, rewards, batch_ids):
		for observation, action, reward, batch_id in zip(observations, actions, rewards, batch_ids):
			if batch_id is None:
				continue
			self._observations_list[batch_id].append(observation.astype(np.float32))
			self._actions_list[batch_id].append(action.astype(np.float32))
			self._rewards_list[batch_id].append(reward.astype(np.float32))

    #### 将长度属性设置奖励列表的长度最大值。
	def __len__(self):
		return max(map(len, self._rewards_list))