提交 e71cf9ac 编写于 作者: W wizardforcel

2021-01-22 10:48:22

上级 5392bfc4
...@@ -156,7 +156,7 @@ TorchVision 的`datasets`模块附带了许多受欢迎的数据集; 如果机 ...@@ -156,7 +156,7 @@ TorchVision 的`datasets`模块附带了许多受欢迎的数据集; 如果机
在本秘籍中,我们将扩展在先前秘籍“定义神经网络类”中定义的类。 在“定义神经网络类”秘籍中,我们仅创建了所需架构的组件; 现在我们将把所有这些部分捆绑在一起,以建立一个明智的网络。 我们各层的进度将从 784 个单位增加到 256 个,然后是 128 个,最后是 10 个单位的输出层。 在本秘籍中,我们将扩展在先前秘籍“定义神经网络类”中定义的类。 在“定义神经网络类”秘籍中,我们仅创建了所需架构的组件; 现在我们将把所有这些部分捆绑在一起,以建立一个明智的网络。 我们各层的进度将从 784 个单位增加到 256 个,然后是 128 个,最后是 10 个单位的输出层。
在本秘籍中,我们将使用类的构造函数中定义的组件来研究网络架构。 然后,我们将完成网络类定义并创建其对象。 在本秘籍中,我们将使用类的构造中定义的组件来研究网络架构。 然后,我们将完成网络类定义并创建其对象。
# 操作步骤 # 操作步骤
...@@ -228,7 +228,7 @@ TorchVision 的`datasets`模块附带了许多受欢迎的数据集; 如果机 ...@@ -228,7 +228,7 @@ TorchVision 的`datasets`模块附带了许多受欢迎的数据集; 如果机
# 工作原理 # 工作原理
在秘籍中,通过建立前向网络来完成网络,其中我们将构造函数中定义的网络组件捆绑在一起。 用`nn.Module`定义的网络需要定义`forward()`方法。 它采用输入张量,并按照正向方法中定义的操作顺序,将其通过网络类中`__init__()`方法中定义的网络组件。 在秘籍中,通过建立前向网络来完成网络,其中我们将构造中定义的网络组件捆绑在一起。 用`nn.Module`定义的网络需要定义`forward()`方法。 它采用输入张量,并按照正向方法中定义的操作顺序,将其通过网络类中`__init__()`方法中定义的网络组件。
当传递输入时,将引用模型对象的名称自动调用 forward 方法。 `nn.Module`自动创建将在正向方法中使用的权重和偏差张量。 线性单元本身定义了线性函数,例如`xW + B`; 要具有非线性功能,我们需要插入非线性激活函数,在这里我们使用最流行的激活函数之一 ReLU,尽管您可以在 PyTorch 中使用其他可用的激活函数。 当传递输入时,将引用模型对象的名称自动调用 forward 方法。 `nn.Module`自动创建将在正向方法中使用的权重和偏差张量。 线性单元本身定义了线性函数,例如`xW + B`; 要具有非线性功能,我们需要插入非线性激活函数,在这里我们使用最流行的激活函数之一 ReLU,尽管您可以在 PyTorch 中使用其他可用的激活函数。
...@@ -252,7 +252,7 @@ TorchVision 的`datasets`模块附带了许多受欢迎的数据集; 如果机 ...@@ -252,7 +252,7 @@ TorchVision 的`datasets`模块附带了许多受欢迎的数据集; 如果机
让我们定义损失函数: 让我们定义损失函数:
1. 首先,我们将从网络构造函数中的`__init__`方法开始,将现有的网络架构修改为`softmax`而不是`softmax`的输出日志: 1. 首先,我们将从网络构造中的`__init__`方法开始,将现有的网络架构修改为`softmax`而不是`softmax`的输出日志:
```py ```py
>>self.log_softmax = nn.LogSoftmax() >>self.log_softmax = nn.LogSoftmax()
......
...@@ -676,7 +676,7 @@ CNN( ...@@ -676,7 +676,7 @@ CNN(
# 工作原理 # 工作原理
此秘籍的工作方式与第 2 章,“处理神经网络”时非常相似,当我们研究一个全连接神经网络时。 我们从`__init__()`方法和父类的构造函数开始,定义了从 PyTorch 中的`nn.Module`继承的 CNN 类。 之后,我们通过传入与每一层相关的参数来定义 CNN 中的各个层。 对于我们的第一卷积层,输入通道的数量为 3(RGB),输出通道的数量定义为 16,其平方核大小为 3。第二卷积层采用上一层的张量,并具有 16 个输入通道和 32 个输出通道,内核尺寸为`3 x 3`。类似地,第三卷积层具有 32 个输入通道和 64 个输出通道,内核尺寸为`3 x 3`。 我们还需要一个最大池化层,并使用 2 的内核大小和 2 的步幅。我们使用`.view()`将张量的三个维度展平为一个维度,以便可以将其传递到全连接网络中。 `view`函数中的 -1 通过确保`view`函数之前和之后的元素数量保持相同(在本例中为批量大小)来确保将正确的尺寸自动分配给该尺寸。 此秘籍的工作方式与第 2 章,“处理神经网络”时非常相似,当我们研究一个全连接神经网络时。 我们从`__init__()`方法和父类的构造开始,定义了从 PyTorch 中的`nn.Module`继承的 CNN 类。 之后,我们通过传入与每一层相关的参数来定义 CNN 中的各个层。 对于我们的第一卷积层,输入通道的数量为 3(RGB),输出通道的数量定义为 16,其平方核大小为 3。第二卷积层采用上一层的张量,并具有 16 个输入通道和 32 个输出通道,内核尺寸为`3 x 3`。类似地,第三卷积层具有 32 个输入通道和 64 个输出通道,内核尺寸为`3 x 3`。 我们还需要一个最大池化层,并使用 2 的内核大小和 2 的步幅。我们使用`.view()`将张量的三个维度展平为一个维度,以便可以将其传递到全连接网络中。 `view`函数中的 -1 通过确保`view`函数之前和之后的元素数量保持相同(在本例中为批量大小)来确保将正确的尺寸自动分配给该尺寸。
对于第一个全连接层,我们有 1,024 个输入(通过将最大池后的`64 x 4 x 4`张量展平而获得)和 512 个输出。 对于最后一个全连接层,我们有 512 个输入和 10 个输出,代表输出类别的数量。 我们还为全连接层定义了一个丢弃层,概率为 0.3。 对于第一个全连接层,我们有 1,024 个输入(通过将最大池后的`64 x 4 x 4`张量展平而获得)和 512 个输出。 对于最后一个全连接层,我们有 512 个输入和 10 个输出,代表输出类别的数量。 我们还为全连接层定义了一个丢弃层,概率为 0.3。
......
...@@ -464,7 +464,7 @@ return self.fc(hidden) ...@@ -464,7 +464,7 @@ return self.fc(hidden)
# 工作原理 # 工作原理
我们使用`torch.nn`模块创建了从`torch.nn.Module`继承的模型类`LSTMClassifier`,并初始化了基类构造函数。 然后,我们定义嵌入层,其中输入维与词汇量大小相同,输出为嵌入维,然后将嵌入层输出传递到 LSTM 层,其中输入维为嵌入维,然后 定义隐藏状态维度。 我们使用`torch.nn`模块创建了从`torch.nn.Module`继承的模型类`LSTMClassifier`,并初始化了基类构造。 然后,我们定义嵌入层,其中输入维与词汇量大小相同,输出为嵌入维,然后将嵌入层输出传递到 LSTM 层,其中输入维为嵌入维,然后 定义隐藏状态维度。
然后,我们定义了全连接层和丢弃层。 接下来,我们定义`forward()`方法,该方法接受输入序列,并将其传递给嵌入层,从而产生尺寸为`embedding_dim`的输出,该输出是输入序列的嵌入向量。 然后将这个字向量传递到 LSTM 层,该层输出三个状态-输出状态,隐藏状态和单元状态。 然后,我们定义了全连接层和丢弃层。 接下来,我们定义`forward()`方法,该方法接受输入序列,并将其传递给嵌入层,从而产生尺寸为`embedding_dim`的输出,该输出是输入序列的嵌入向量。 然后将这个字向量传递到 LSTM 层,该层输出三个状态-输出状态,隐藏状态和单元状态。
...@@ -533,7 +533,7 @@ class MultiLSTMClassifier(nn.Module): ...@@ -533,7 +533,7 @@ class MultiLSTMClassifier(nn.Module):
# 工作原理 # 工作原理
在此秘籍中,我们在构造函数中添加了`num_layers`和参数以控制模型中 LSTM 的层数,并将其作为关键字参数`num_layers`传递给 LSTM 定义。 在此秘籍中,我们在构造中添加了`num_layers`和参数以控制模型中 LSTM 的层数,并将其作为关键字参数`num_layers`传递给 LSTM 定义。
然后,在`forward()`方法中,由于隐藏状态的形状是`[num_layers * num_directions, batch, hidden_dim]`(默认情况下`num_direction``1`),因此我们仅使用`hidden[-1]`从最后一个 LSTM 层获取了隐藏状态。 这意味着`hidden[-1]`给出了最后一层的隐藏状态。 通过这样做,我们可以选择`num_layers`作为超参数。 来自较低层的隐藏状态输出作为较高状态的输入传递。 然后,在`forward()`方法中,由于隐藏状态的形状是`[num_layers * num_directions, batch, hidden_dim]`(默认情况下`num_direction``1`),因此我们仅使用`hidden[-1]`从最后一个 LSTM 层获取了隐藏状态。 这意味着`hidden[-1]`给出了最后一层的隐藏状态。 通过这样做,我们可以选择`num_layers`作为超参数。 来自较低层的隐藏状态输出作为较高状态的输入传递。
......
...@@ -116,7 +116,7 @@ def forward(self, input): ...@@ -116,7 +116,7 @@ def forward(self, input):
在此秘籍中,我们进行了变换以将图像转换为张量并对其进行归一化,就像在第 3 章,“用于计算机视觉的卷积神经网络”中所做的一样。 然后,我们确定了机器上的设备:CPU 或 GPU。 然后,我们定义了从`nn.Module`类继承的`Generator_model`类,就像在所有以前的架构中所做的一样。 在此秘籍中,我们进行了变换以将图像转换为张量并对其进行归一化,就像在第 3 章,“用于计算机视觉的卷积神经网络”中所做的一样。 然后,我们确定了机器上的设备:CPU 或 GPU。 然后,我们定义了从`nn.Module`类继承的`Generator_model`类,就像在所有以前的架构中所做的一样。
在构造函数中,我们传递了`z_dim`参数,这是我们的噪声向量大小。 然后,我们定义了一个全连接单元`self.fc`,我们将噪声向量传递给该单元,并为其提供了`256 * 7 * 7`输出。 然后,我们定义了一个称为`self.gen``nn.Sequential`单元,其中包含用于定义生成器的关键组件。 我们使用 PyTorch 中提供的`nn.ConvTranspose2d``nn.BatchNorm2d``nn.LeakyReLU`使用一组反卷积,批量规范化和激活层。 `ConvTranspose2d`接受输入通道,输出通道,内核大小,步幅和填充等参数。 `BatchNorm2d`接受上一层的要素/通道数作为其参数,而 LeakyReLU 接受负斜率的角度。 在构造中,我们传递了`z_dim`参数,这是我们的噪声向量大小。 然后,我们定义了一个全连接单元`self.fc`,我们将噪声向量传递给该单元,并为其提供了`256 * 7 * 7`输出。 然后,我们定义了一个称为`self.gen``nn.Sequential`单元,其中包含用于定义生成器的关键组件。 我们使用 PyTorch 中提供的`nn.ConvTranspose2d``nn.BatchNorm2d``nn.LeakyReLU`使用一组反卷积,批量规范化和激活层。 `ConvTranspose2d`接受输入通道,输出通道,内核大小,步幅和填充等参数。 `BatchNorm2d`接受上一层的要素/通道数作为其参数,而 LeakyReLU 接受负斜率的角度。
与 ReLU 不同,LeakyReLU 允许传递小的梯度信号以获取负值。 它使来自判别器的梯度流入发生器。 我们在输出层中使用了 tanh 激活,但是从 DCGAN 论文中我们观察到,使用有界激活可以使模型学会快速饱和并覆盖训练分布的色彩空间。 tanh 的对称性在这里可能是一个优势,因为网络应该以对称方式处理较深的颜色和较浅的颜色。 与 ReLU 不同,LeakyReLU 允许传递小的梯度信号以获取负值。 它使来自判别器的梯度流入发生器。 我们在输出层中使用了 tanh 激活,但是从 DCGAN 论文中我们观察到,使用有界激活可以使模型学会快速饱和并覆盖训练分布的色彩空间。 tanh 的对称性在这里可能是一个优势,因为网络应该以对称方式处理较深的颜色和较浅的颜色。
......
...@@ -7,12 +7,12 @@ RL 是**人工智能**(**AI**)的领域,与我们在前面各章中介绍 ...@@ -7,12 +7,12 @@ RL 是**人工智能**(**AI**)的领域,与我们在前面各章中介绍
在本章中,我们将介绍以下秘籍: 在本章中,我们将介绍以下秘籍:
* OpenAI Gym 简介– CartPole * OpenAI Gym 简介– CartPole
* 引入 DQN * DQN 简介
* 实现 DQN 类 * 实现 DQN 类
* 训练 DQN * 训练 DQN
* 引入深度 GA * 深度 GA 简介
* 生成代理 * 生成智能体
* 选择代理商 * 选择智能体
* 使智能体突变 * 使智能体突变
* 训练深度 GA * 训练深度 GA
...@@ -162,7 +162,7 @@ Q 值可以更新如下: ...@@ -162,7 +162,7 @@ Q 值可以更新如下:
>>import torch.nn as nn >>import torch.nn as nn
``` ```
2. 接下来,定义一个函数返回模型: 2. 接下来,定义一个函数返回模型:
```py ```py
def cartpole_model(observation_space, action_space): def cartpole_model(observation_space, action_space):
...@@ -216,7 +216,7 @@ def cartpole_model(observation_space, action_space): ...@@ -216,7 +216,7 @@ def cartpole_model(observation_space, action_space):
>>class DQN: >>class DQN:
``` ```
3. 然后,我们将定义构造函数 3. 然后,我们将定义构造
```py ```py
>>def __init__(self, observation_space, action_space): >>def __init__(self, observation_space, action_space):
...@@ -300,7 +300,7 @@ loss.backward() ...@@ -300,7 +300,7 @@ loss.backward()
self.optimizer.step() self.optimizer.step()
``` ```
2. 我们还将更新探率: 2. 我们还将更新探率:
```py ```py
if not self.explore_limit: if not self.explore_limit:
...@@ -314,7 +314,7 @@ if not self.explore_limit: ...@@ -314,7 +314,7 @@ if not self.explore_limit:
# 工作原理 # 工作原理
在本秘籍中,我们完成了 DQN 类,并添加了所有必需的功能来训练 DQN。 在构造函数中,我们初始化了探索的初始状态,观察空间和动作空间,然后定义了一个存储单元来保存 DQN 的经验。 我们创建了称为`policy_net``target_net`的软骨模型的两个实例。 我们需要两个网络,因为在训练的每个步骤中,Q 网络的值都会移动,并且如果我们使用不断变化的目标值来调整我们的网络,则该网络可能会由于陷入此变化的目标与估计的 Q 值之间的反馈回路而变得不稳定。 网络值。 如果发生这种情况,价值估计将失去控制。 因此,我们使用了两个网络并将`target_net`保持在`eval`模式。 然后,我们使用`MSELoss()`作为损失函数以及`Adam`优化器来更新权重。 在本秘籍中,我们完成了 DQN 类,并添加了所有必需的功能来训练 DQN。 在构造中,我们初始化了探索的初始状态,观察空间和动作空间,然后定义了一个存储单元来保存 DQN 的经验。 我们创建了称为`policy_net``target_net`的软骨模型的两个实例。 我们需要两个网络,因为在训练的每个步骤中,Q 网络的值都会移动,并且如果我们使用不断变化的目标值来调整我们的网络,则该网络可能会由于陷入此变化的目标与估计的 Q 值之间的反馈回路而变得不稳定。 网络值。 如果发生这种情况,价值估计将失去控制。 因此,我们使用了两个网络并将`target_net`保持在`eval`模式。 然后,我们使用`MSELoss()`作为损失函数以及`Adam`优化器来更新权重。
`load_memory()`方法中,我们从环境中存储了状态,操作,奖励,下一个状态和终端,以用于训练网络。 我们使用的下一个方法是`predict_action`。 在此方法中,我们使用`np.random.rand()`选择了`random_number`,这为我们提供了`[0,1)`的值。 如果此`random_number`小于当前的`exploration_rate`,则我们选择一个随机动作,该动作由`exploration_rate`控制。 这就是我们合并探索的方式。 但是,如果`random_number`大于`exploration_rate`,则`target_net`会预测`q_values`并选择具有最大 Q 值的动作。 `load_memory()`方法中,我们从环境中存储了状态,操作,奖励,下一个状态和终端,以用于训练网络。 我们使用的下一个方法是`predict_action`。 在此方法中,我们使用`np.random.rand()`选择了`random_number`,这为我们提供了`[0,1)`的值。 如果此`random_number`小于当前的`exploration_rate`,则我们选择一个随机动作,该动作由`exploration_rate`控制。 这就是我们合并探索的方式。 但是,如果`random_number`大于`exploration_rate`,则`target_net`会预测`q_values`并选择具有最大 Q 值的动作。
...@@ -555,7 +555,7 @@ Rewards: 160.0 ...@@ -555,7 +555,7 @@ Rewards: 160.0
module.bias.data.fill_(0.00) module.bias.data.fill_(0.00)
``` ```
2. 现在,我们将定义一个将创建代理的函数: 2. 现在,我们将定义一个将创建智能体的函数:
```py ```py
>>def create_agents(num_agents, observation_space, action_space): >>def create_agents(num_agents, observation_space, action_space):
...@@ -570,7 +570,7 @@ for _ in range(num_agents): ...@@ -570,7 +570,7 @@ for _ in range(num_agents):
agent.apply(init_weight) agent.apply(init_weight)
``` ```
4. 我们将关闭每个代理层的梯度: 4. 我们将关闭智能体的每个层的梯度:
```py ```py
for param in agent.parameters(): for param in agent.parameters():
...@@ -580,7 +580,7 @@ agent.eval() ...@@ -580,7 +580,7 @@ agent.eval()
agents.append(agent) agents.append(agent)
``` ```
5. 最后,我们将退回代理商 5. 最后,我们将返回智能体
```py ```py
return agents return agents
...@@ -634,7 +634,7 @@ for _ in range(MAX_STEP): ...@@ -634,7 +634,7 @@ for _ in range(MAX_STEP):
return total_reward return total_reward
``` ```
4. 然后,我们需要定义平均座席得分: 4. 然后,我们需要定义智能体的平均得分:
```py ```py
>>def agent_score(agent, env, runs): >>def agent_score(agent, env, runs):
...@@ -645,7 +645,7 @@ return total_reward ...@@ -645,7 +645,7 @@ return total_reward
return score/runs return score/runs
``` ```
5. 最后,我们评估所有代理商的分数: 5. 最后,我们评估所有智能体的分数:
```py ```py
>>def all_agent_score(agents, env, runs): >>def all_agent_score(agents, env, runs):
...@@ -686,7 +686,7 @@ return total_reward ...@@ -686,7 +686,7 @@ return total_reward
child_agent = copy.deepcopy(agent) child_agent = copy.deepcopy(agent)
``` ```
3. 接下来,我们将遍历代理的参数: 3. 接下来,我们将遍历智能体的参数:
```py ```py
for param in agent.parameters(): for param in agent.parameters():
...@@ -712,7 +712,7 @@ return child_agent ...@@ -712,7 +712,7 @@ return child_agent
top_id = None top_id = None
``` ```
6. 接下来,找到`elite`代理 6. 接下来,找到`elite`智能体
```py ```py
for agent_id in selected_elites: for agent_id in selected_elites:
...@@ -724,7 +724,7 @@ for agent_id in selected_elites: ...@@ -724,7 +724,7 @@ for agent_id in selected_elites:
return copy.deepcopy(agents[top_id]) return copy.deepcopy(agents[top_id])
``` ```
7. 获取子代理 7. 获取子智能体
```py ```py
>>def child_agents(agents, top_parents_id, env, elite_id=None): >>def child_agents(agents, top_parents_id, env, elite_id=None):
...@@ -741,7 +741,7 @@ return copy.deepcopy(agents[top_id]) ...@@ -741,7 +741,7 @@ return copy.deepcopy(agents[top_id])
return child_agents, elite_id return child_agents, elite_id
``` ```
8. 获取顶级父 8. 获取顶级父
```py ```py
>>def top_parents(scores, num_top_parents): >>def top_parents(scores, num_top_parents):
...@@ -791,13 +791,13 @@ return copy.deepcopy(agents[top_id]) ...@@ -791,13 +791,13 @@ return copy.deepcopy(agents[top_id])
>>env = gym.make(ENV_NAME) >>env = gym.make(ENV_NAME)
``` ```
4. 现在,创建代理 4. 现在,创建智能体
```py ```py
>>agents = create_agents(num_agents, env.observation_space.shape[0], env.action_space.n) >>agents = create_agents(num_agents, env.observation_space.shape[0], env.action_space.n)
``` ```
5. 接下来,遍历几代 5. 接下来,遍历几代:
```py ```py
>>print(f'| Generation | Score |') >>print(f'| Generation | Score |')
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* 使用 Flask 部署模型 * 使用 Flask 部署模型
* 创建一个 TorchScript * 创建一个 TorchScript
* 出至 ONNX * 出至 ONNX
# 技术要求 # 技术要求
...@@ -105,7 +105,7 @@ pip install flask ...@@ -105,7 +105,7 @@ pip install flask
>>model = create_model() >>model = create_model()
``` ```
0. 现在,让我们创建路线 0. 现在,让我们创建路
```py ```py
>>@app.route('/predict', methods=['POST']) >>@app.route('/predict', methods=['POST'])
...@@ -238,7 +238,7 @@ tensor([[ 0.4238, -0.0524, 0.5719, 0.4747], ...@@ -238,7 +238,7 @@ tensor([[ 0.4238, -0.0524, 0.5719, 0.4747],
grad_fn=<DifferentiableGraphBackward>) grad_fn=<DifferentiableGraphBackward>)
``` ```
6. 我们可以使用以下代码访问图 6. 我们可以使用以下代码访问图:
```py ```py
>>traced_cell.graph >>traced_cell.graph
......
...@@ -40,8 +40,8 @@ print(torch.__version__) ...@@ -40,8 +40,8 @@ print(torch.__version__)
首先定义一个简单的`Module``Module`是 PyTorch 中组成的基本单位。 它包含: 首先定义一个简单的`Module``Module`是 PyTorch 中组成的基本单位。 它包含:
1. 构造函数,为调用准备模块 1. 构造,为调用准备模块
2. 一组`Parameters`和子`Modules`。 这些由构造函数初始化,并且可以在调用期间由模块使用。 2. 一组`Parameters`和子`Modules`。 这些由构造初始化,并且可以在调用期间由模块使用。
3. `forward`函数。 这是调用模块时运行的代码。 3. `forward`函数。 这是调用模块时运行的代码。
我们来看一个小例子: 我们来看一个小例子:
...@@ -76,7 +76,7 @@ print(my_cell(x, h)) ...@@ -76,7 +76,7 @@ print(my_cell(x, h))
因此,我们已经: 因此,我们已经:
1. 创建了一个子类`torch.nn.Module`的类。 1. 创建了一个子类`torch.nn.Module`的类。
2. 定义一个构造函数。 构造函数没有做很多事情,只是调用`super`的构造函数 2. 定义一个构造器。 构造器没有做很多事情,只是调用`super`的构造器
3. 定义了`forward`函数,该函数具有两个输入并返回两个输出。 `forward`函数的实际内容并不是很重要,但它是一种伪造的 [RNN 单元](https://colah.github.io/posts/2015-08-Understanding-LSTMs/),即,该函数应用于循环。 3. 定义了`forward`函数,该函数具有两个输入并返回两个输出。 `forward`函数的实际内容并不是很重要,但它是一种伪造的 [RNN 单元](https://colah.github.io/posts/2015-08-Understanding-LSTMs/),即,该函数应用于循环。
我们实例化了该模块,并制作了`x``y`,它们只是`3x4`随机值矩阵。 然后,我们使用`my_cell(x, h)`调用该单元格。 这依次调用我们的`forward`函数。 我们实例化了该模块,并制作了`x``y`,它们只是`3x4`随机值矩阵。 然后,我们使用`my_cell(x, h)`调用该单元格。 这依次调用我们的`forward`函数。
......
...@@ -195,7 +195,7 @@ struct Net : torch::nn::Module { ...@@ -195,7 +195,7 @@ struct Net : torch::nn::Module {
``` ```
就像在 Python 中一样,我们定义了一个名为`Net`的类(为简单起见,这里是`struct`而不是`class`),然后从模块基类派生它。 在构造函数内部,我们使用`torch::randn`创建张量,就像在 Python 中使用`torch.randn`一样。 一个有趣的区别是我们如何注册参数。 在 Python 中,我们用`torch.nn.Parameter`类包装了张量,而在 C++ 中,我们不得不通过`register_parameter`方法传递张量。 这样做的原因是 Python API 可以检测到属性为`torch.nn.Parameter`类型并自动注册此类张量。 在 C++ 中,反射非常受限制,因此提供了一种更传统(且不太神奇)的方法。 就像在 Python 中一样,我们定义了一个名为`Net`的类(为简单起见,这里是`struct`而不是`class`),然后从模块基类派生它。 在构造内部,我们使用`torch::randn`创建张量,就像在 Python 中使用`torch.randn`一样。 一个有趣的区别是我们如何注册参数。 在 Python 中,我们用`torch.nn.Parameter`类包装了张量,而在 C++ 中,我们不得不通过`register_parameter`方法传递张量。 这样做的原因是 Python API 可以检测到属性为`torch.nn.Parameter`类型并自动注册此类张量。 在 C++ 中,反射非常受限制,因此提供了一种更传统(且不太神奇)的方法。
#### 注册子模块并遍历模块层次结构 #### 注册子模块并遍历模块层次结构
...@@ -251,7 +251,7 @@ struct Net : torch::nn::Module { ...@@ -251,7 +251,7 @@ struct Net : torch::nn::Module {
您可以在[`torch::nn`命名空间的文档](https://pytorch.org/cppdocs/api/namespace_torch__nn.html)中找到可用的内置模块的完整列表,例如`torch::nn::Linear``torch::nn::Dropout``torch::nn::Conv2d` 您可以在[`torch::nn`命名空间的文档](https://pytorch.org/cppdocs/api/namespace_torch__nn.html)中找到可用的内置模块的完整列表,例如`torch::nn::Linear``torch::nn::Dropout``torch::nn::Conv2d`
关于上述代码的一个微妙之处在于,为什么在构造函数的初始值设定项列表中创建子模块,而在构造函数的主体内部创建参数。 这是有充分的理由的,我们将在下面有关“C++ 前端所有权模型”的部分中对此进行介绍。 但是,最终结果是,就像 Python 中一样,我们可以递归访问模块树的参数。 调用`parameters()`返回一个`std::vector<torch::Tensor>`,我们可以对其进行迭代: 关于上述代码的一个微妙之处在于,为什么在构造器的初始值设定项列表中创建子模块,而在构造器的主体内部创建参数。 这是有充分的理由的,我们将在下面有关“C++ 前端所有权模型”的部分中对此进行介绍。 但是,最终结果是,就像 Python 中一样,我们可以递归访问模块树的参数。 调用`parameters()`返回一个`std::vector<torch::Tensor>`,我们可以对其进行迭代:
```py ```py
int main() { int main() {
...@@ -422,7 +422,7 @@ TORCH_MODULE(Linear); ...@@ -422,7 +422,7 @@ TORCH_MODULE(Linear);
``` ```
简而言之:该模块不是`Linear`,而是`LinearImpl`。 然后,宏`TORCH_MODULE`定义了实际的`Linear`类。 这个“生成的”类实际上是`std::shared_ptr<LinearImpl>`的包装。 它是一个包装器,而不是简单的`typedef`,因此,除其他事项外,构造函数仍可按预期工作,即,您仍然可以编写`torch::nn::Linear(3, 4)`而不是`std::make_shared<LinearImpl>(3, 4)`。 我们将由宏创建的类称为模块*所有者*。 与(共享)指针一样,您可以使用箭头运算符(例如`model->forward(...)`)访问基础对象。 最终结果是一个所有权模型,该模型非常类似于 Python API。 引用语义成为默认语义,但是没有额外输入`std::shared_ptr``std::make_shared`。 对于我们的`Net`,使用模块持有人 API 如下所示: 简而言之:该模块不是`Linear`,而是`LinearImpl`。 然后,宏`TORCH_MODULE`定义了实际的`Linear`类。 这个“生成的”类实际上是`std::shared_ptr<LinearImpl>`的包装。 它是一个包装器,而不是简单的`typedef`,因此,除其他事项外,构造仍可按预期工作,即,您仍然可以编写`torch::nn::Linear(3, 4)`而不是`std::make_shared<LinearImpl>(3, 4)`。 我们将由宏创建的类称为模块*所有者*。 与(共享)指针一样,您可以使用箭头运算符(例如`model->forward(...)`)访问基础对象。 最终结果是一个所有权模型,该模型非常类似于 Python API。 引用语义成为默认语义,但是没有额外输入`std::shared_ptr``std::make_shared`。 对于我们的`Net`,使用模块持有人 API 如下所示:
```py ```py
struct NetImpl : torch::nn::Module {}; struct NetImpl : torch::nn::Module {};
...@@ -437,7 +437,7 @@ int main() { ...@@ -437,7 +437,7 @@ int main() {
``` ```
这里有一个微妙的问题值得一提。 默认构造的`std::shared_ptr`为“空”,即包含空指针。 什么是默认构造的`Linear``Net`? 好吧,这是一个棘手的选择。 我们可以说它应该是一个空(`null``std::shared_ptr<LinearImpl>`。 但是,请记住`Linear(3, 4)``std::make_shared<LinearImpl>(3, 4)`相同。 这意味着如果我们已确定`Linear linear;`应该为空指针,则将无法构造不采用任何构造函数参数或都不使用所有缺省构造函数的模块。 因此,在当前的 API 中,默认构造的模块持有人(如`Linear()`)将调用基础模块的默认构造函数(`LinearImpl()`)。 如果基础模块没有默认构造函数,则会出现编译器错误。 要构造空持有人,可以将`nullptr`传递给持有人的构造函数 这里有一个微妙的问题值得一提。 默认构造的`std::shared_ptr`为“空”,即包含空指针。 什么是默认构造的`Linear``Net`? 好吧,这是一个棘手的选择。 我们可以说它应该是一个空(`null``std::shared_ptr<LinearImpl>`。 但是,请记住`Linear(3, 4)``std::make_shared<LinearImpl>(3, 4)`相同。 这意味着如果我们已确定`Linear linear;`应该为空指针,则将无法构造不采用任何构造器参数或都不使用所有缺省构造器的模块。 因此,在当前的 API 中,默认构造的模块持有人(如`Linear()`)将调用基础模块的默认构造器(`LinearImpl()`)。 如果基础模块没有默认构造器,则会出现编译器错误。 要构造空持有人,可以将`nullptr`传递给持有人的构造器
实际上,这意味着您可以使用如先前所示的子模块,在*初始化程序列表*中注册并构造该模块: 实际上,这意味着您可以使用如先前所示的子模块,在*初始化程序列表*中注册并构造该模块:
...@@ -451,7 +451,7 @@ struct Net : torch::nn::Module { ...@@ -451,7 +451,7 @@ struct Net : torch::nn::Module {
``` ```
或者,您可以先使用空指针构造持有人,然后在构造函数中为其分配值(Python 爱好者更熟悉): 或者,您可以先使用空指针构造持有人,然后在构造中为其分配值(Python 爱好者更熟悉):
```py ```py
struct Net : torch::nn::Module { struct Net : torch::nn::Module {
...@@ -815,7 +815,7 @@ torch::Device device(torch::kCUDA) ...@@ -815,7 +815,7 @@ torch::Device device(torch::kCUDA)
``` ```
现在,所有张量都将驻留在 GPU 上,并调用快速 CUDA 内核进行所有操作,而无需我们更改任何下游代码。 如果我们想指定一个特定的设备索引,则可以将其作为第二个参数传递给`Device`构造函数。 如果我们希望不同的张量驻留在不同的设备上,则可以传递单独的设备实例(例如,一个在 CUDA 设备 0 上,另一个在 CUDA 设备 1 上)。 我们甚至可以动态地进行此配置,这通常对于使我们的训练脚本更具可移植性很有用: 现在,所有张量都将驻留在 GPU 上,并调用快速 CUDA 内核进行所有操作,而无需我们更改任何下游代码。 如果我们想指定一个特定的设备索引,则可以将其作为第二个参数传递给`Device`构造。 如果我们希望不同的张量驻留在不同的设备上,则可以传递单独的设备实例(例如,一个在 CUDA 设备 0 上,另一个在 CUDA 设备 1 上)。 我们甚至可以动态地进行此配置,这通常对于使我们的训练脚本更具可移植性很有用:
```py ```py
torch::Device device = torch::kCPU; torch::Device device = torch::kCPU;
......
...@@ -61,7 +61,7 @@ TorchScript 编译器了解固定数量的类型。 只有这些类型可以用 ...@@ -61,7 +61,7 @@ TorchScript 编译器了解固定数量的类型。 只有这些类型可以用
``` ```
我们正在调用 [OpenCV `Mat`类的构造函数](https://docs.opencv.org/trunk/d3/d63/classcv_1_1Mat.html#a922de793eabcec705b3579c5f95a643e),将张量转换为`Mat`对象。 我们向其传递原始`image`张量的行数和列数,数据类型(在此示例中,我们将其固定为`float32`),最后是指向基础数据的原始指针– `float*``Mat`类的此构造函数的特殊之处在于它不会复制输入数据。 取而代之的是,它将简单地引用此存储器来执行`Mat`上的所有操作。 如果在`image_mat`上执行原地操作,这将反映在原始`image`张量中(反之亦然)。 即使我们实际上将数据存储在 PyTorch 张量中,这也使我们能够使用库的本机矩阵类型调用后续的 OpenCV 例程。 我们重复此过程将`warp` PyTorch 张量转换为`warp_mat` OpenCV 矩阵: 我们正在调用 [OpenCV `Mat`类的构造](https://docs.opencv.org/trunk/d3/d63/classcv_1_1Mat.html#a922de793eabcec705b3579c5f95a643e),将张量转换为`Mat`对象。 我们向其传递原始`image`张量的行数和列数,数据类型(在此示例中,我们将其固定为`float32`),最后是指向基础数据的原始指针– `float*``Mat`类的此构造器的特殊之处在于它不会复制输入数据。 取而代之的是,它将简单地引用此存储器来执行`Mat`上的所有操作。 如果在`image_mat`上执行原地操作,这将反映在原始`image`张量中(反之亦然)。 即使我们实际上将数据存储在 PyTorch 张量中,这也使我们能够使用库的本机矩阵类型调用后续的 OpenCV 例程。 我们重复此过程将`warp` PyTorch 张量转换为`warp_mat` OpenCV 矩阵:
```py ```py
cv::Mat warp_mat(/*rows=*/warp.size(0), cv::Mat warp_mat(/*rows=*/warp.size(0),
......
...@@ -383,8 +383,8 @@ $ ./infer ...@@ -383,8 +383,8 @@ $ ./infer
也可能需要将自定义类从自定义 C++ 类实例移入或移出`IValue`, such as when you take or return IValues from TorchScript methods or you want to instantiate a custom class attribute in C++. For creating an IValue: 也可能需要将自定义类从自定义 C++ 类实例移入或移出`IValue`, such as when you take or return IValues from TorchScript methods or you want to instantiate a custom class attribute in C++. For creating an IValue:
* `torch::make_custom_class<T>()`提供类似于`c10::intrusive_ptr<T>`的 API,因为它将采用您提供给它的任何参数集,调用与该参数集匹配的`T`的构造函数,并包装该实例 然后退回 但是,它不仅返回指向自定义类对象的指针,还返回包装对象的`IValue`。 然后,您可以将此`IValue`直接传递给 TorchScript。 * `torch::make_custom_class<T>()`提供类似于`c10::intrusive_ptr<T>`的 API,因为它将采用您提供给它的任何参数集,调用与该参数集匹配的`T`的构造,并包装该实例 然后退回 但是,它不仅返回指向自定义类对象的指针,还返回包装对象的`IValue`。 然后,您可以将此`IValue`直接传递给 TorchScript。
* 如果您已经有一个指向类的`intrusive_ptr`,则可以使用构造函数`IValue(intrusive_ptr<T>)`直接从其构造`IValue` * 如果您已经有一个指向类的`intrusive_ptr`,则可以使用构造`IValue(intrusive_ptr<T>)`直接从其构造`IValue`
要将`IValue`转换回自定义类: 要将`IValue`转换回自定义类:
......
...@@ -73,7 +73,7 @@ print(example(torch.ones([]))) ...@@ -73,7 +73,7 @@ print(example(torch.ones([])))
注意 注意
当我们初始化一个空的期货列表时,我们需要在`futures`上添加一个显式类型注释。 在 TorchScript 中,空容器默认假定它们包含张量值,因此我们将列表构造函数#注释为`List[torch.jit.Future[torch.Tensor]]`类型 当我们初始化一个空的期货列表时,我们需要在`futures`上添加一个显式类型注释。 在 TorchScript 中,空容器默认假定它们包含张量值,因此我们将列表构造#注释为`List[torch.jit.Future[torch.Tensor]]`类型
本示例使用`fork()`启动函数`foo`的 100 个实例,等待 100 个任务完成,然后对结果求和,返回`-100.0` 本示例使用`fork()`启动函数`foo`的 100 个实例,等待 100 个任务完成,然后对结果求和,返回`-100.0`
......
...@@ -706,7 +706,7 @@ Global sparsity: 20.00% ...@@ -706,7 +706,7 @@ Global sparsity: 20.00%
## 使用自定义剪裁函数扩展`torch.nn.utils.prune` ## 使用自定义剪裁函数扩展`torch.nn.utils.prune`
要实现自己的剪裁功能,可以通过继承`BasePruningMethod`基类的子类来扩展`nn.utils.prune`模块,这与所有其他剪裁方法一样。 基类为您实现以下方法:`__call__``apply_mask``apply``prune``remove`。 除了一些特殊情况外,您无需为新的剪裁技术重新实现这些方法。 但是,您将必须实现`__init__`(构造函数)和`compute_mask`(有关如何根据剪裁技术的逻辑为给定张量计算掩码的说明)。 另外,您将必须指定此技术实现的剪裁类型(支持的选项为`global``structured``unstructured`)。 需要确定在迭代应用剪裁的情况下如何组合蒙版。 换句话说,当剪裁预剪裁的参数时,当前的剪裁技术应作用于参数的未剪裁部分。 指定`PRUNING_TYPE`将使`PruningContainer`(处理剪裁掩码的迭代应用)正确识别要剪裁的参数。 要实现自己的剪裁功能,可以通过继承`BasePruningMethod`基类的子类来扩展`nn.utils.prune`模块,这与所有其他剪裁方法一样。 基类为您实现以下方法:`__call__``apply_mask``apply``prune``remove`。 除了一些特殊情况外,您无需为新的剪裁技术重新实现这些方法。 但是,您将必须实现`__init__`(构造)和`compute_mask`(有关如何根据剪裁技术的逻辑为给定张量计算掩码的说明)。 另外,您将必须指定此技术实现的剪裁类型(支持的选项为`global``structured``unstructured`)。 需要确定在迭代应用剪裁的情况下如何组合蒙版。 换句话说,当剪裁预剪裁的参数时,当前的剪裁技术应作用于参数的未剪裁部分。 指定`PRUNING_TYPE`将使`PruningContainer`(处理剪裁掩码的迭代应用)正确识别要剪裁的参数。
例如,假设您要实现一种剪裁技术,以剪裁张量中的所有其他条目(或者-如果先前已剪裁过张量,则剪裁张量的其余未剪裁部分)。 这将是`PRUNING_TYPE='unstructured'`,因为它作用于层中的单个连接,而不作用于整个单元/通道(`'structured'`),或作用于不同的参数(`'global'`)。 例如,假设您要实现一种剪裁技术,以剪裁张量中的所有其他条目(或者-如果先前已剪裁过张量,则剪裁张量的其余未剪裁部分)。 这将是`PRUNING_TYPE='unstructured'`,因为它作用于层中的单个连接,而不作用于整个单元/通道(`'structured'`),或作用于不同的参数(`'global'`)。
......
...@@ -71,7 +71,7 @@ def cleanup(): ...@@ -71,7 +71,7 @@ def cleanup():
``` ```
现在,让我们创建一个玩具模块,将其与 DDP 封装在一起,并提供一些虚拟输入数据。 请注意,由于 DDP 会将模型状态从等级 0 进程广播到 DDP 构造函数中的所有其他进程,因此您不必担心不同的 DDP 进程从不同的模型参数初始值开始。 现在,让我们创建一个玩具模块,将其与 DDP 封装在一起,并提供一些虚拟输入数据。 请注意,由于 DDP 会将模型状态从等级 0 进程广播到 DDP 构造中的所有其他进程,因此您不必担心不同的 DDP 进程从不同的模型参数初始值开始。
```py ```py
class ToyModel(nn.Module): class ToyModel(nn.Module):
...@@ -115,7 +115,7 @@ def run_demo(demo_fn, world_size): ...@@ -115,7 +115,7 @@ def run_demo(demo_fn, world_size):
## 带偏差的处理速度 ## 带偏差的处理速度
在 DDP 中,构造函数,正向传递和反向传递都是分布式同步点。 预期不同的进程将启动相同数量的同步,并以相同的顺序到达这些同步点,并在大致相同的时间进入每个同步点。 否则,快速流程可能会提早到达,并在等待流浪者时超时。 因此,用户负责平衡流程之间的工作负载分配。 有时,由于例如网络延迟,资源争夺,不可预测的工作量峰值,不可避免地会出现处理速度偏差。 为了避免在这种情况下超时,请在调用[`init_process_group`](https://pytorch.org/docs/stable/distributed.html#torch.distributed.init_process_group)时传递足够大的`timeout`值。 在 DDP 中,构造,正向传递和反向传递都是分布式同步点。 预期不同的进程将启动相同数量的同步,并以相同的顺序到达这些同步点,并在大致相同的时间进入每个同步点。 否则,快速流程可能会提早到达,并在等待流浪者时超时。 因此,用户负责平衡流程之间的工作负载分配。 有时,由于例如网络延迟,资源争夺,不可预测的工作量峰值,不可避免地会出现处理速度偏差。 为了避免在这种情况下超时,请在调用[`init_process_group`](https://pytorch.org/docs/stable/distributed.html#torch.distributed.init_process_group)时传递足够大的`timeout`值。
## 保存和加载检查点 ## 保存和加载检查点
......
...@@ -105,7 +105,7 @@ class Observer: ...@@ -105,7 +105,7 @@ class Observer:
``` ```
agent 的代码稍微复杂一点,我们将其分成多个部分。 在此示例中,代理既充当训练者又充当主角色,以便它向多个分布式观察者发送命令以运行情节,并且还记录本地的所有动作和奖励,这些动作和奖赏将在每个情节之后的训练阶段使用。 下面的代码显示了`Agent`构造函数,其中大多数行都在初始化各种组件。 最后的循环在其他工作者上远程初始化观察者,并在本地将`RRefs`保留给这些观察者。 代理稍后将使用那些观察者`RRefs`发送命令。 应用无需担心`RRefs`的寿命。 每个`RRef`的所有者维护一个参考计数图以跟踪其生命周期,并保证只要该`RRef`的任何活动用户都不会删除远程数据对象。 有关详细信息,请参考`RRef` [设计文档](https://pytorch.org/docs/master/notes/rref.html) agent 的代码稍微复杂一点,我们将其分成多个部分。 在此示例中,代理既充当训练者又充当主角色,以便它向多个分布式观察者发送命令以运行情节,并且还记录本地的所有动作和奖励,这些动作和奖赏将在每个情节之后的训练阶段使用。 下面的代码显示了`Agent`构造,其中大多数行都在初始化各种组件。 最后的循环在其他工作者上远程初始化观察者,并在本地将`RRefs`保留给这些观察者。 代理稍后将使用那些观察者`RRefs`发送命令。 应用无需担心`RRefs`的寿命。 每个`RRef`的所有者维护一个参考计数图以跟踪其生命周期,并保证只要该`RRef`的任何活动用户都不会删除远程数据对象。 有关详细信息,请参考`RRef` [设计文档](https://pytorch.org/docs/master/notes/rref.html)
```py ```py
import gym import gym
...@@ -307,7 +307,7 @@ Solved! Running reward is now 475.3163778435275! ...@@ -307,7 +307,7 @@ Solved! Running reward is now 475.3163778435275!
在本节中,我们将使用 RNN 模型来展示如何使用 RPC API 构建分布式模型并行训练。 示例 RNN 模型非常小,可以轻松地放入单个 GPU 中,但是我们仍将其层划分为两个不同的工人来演示这一想法。 开发人员可以应用类似的技术在多个设备和机器上分布更大的模型。 在本节中,我们将使用 RNN 模型来展示如何使用 RPC API 构建分布式模型并行训练。 示例 RNN 模型非常小,可以轻松地放入单个 GPU 中,但是我们仍将其层划分为两个不同的工人来演示这一想法。 开发人员可以应用类似的技术在多个设备和机器上分布更大的模型。
RNN 模型设计是从 PyTorch [示例](https://github.com/pytorch/examples/tree/master/word_language_model)存储库中的词语言模型中借用的,该存储库包含三个主要组件,一个嵌入表,一个`LSTM`层和一个解码器。 下面的代码将嵌入表和解码器包装到子模块中,以便它们的构造函数可以传递给 RPC API。 在`EmbeddingTable`子模块中,我们有意将`Embedding`层放在 GPU 上以涵盖用例。 在 v1.4 中,RPC 始终在目标工作线程上创建 CPU 张量参数或返回值。 如果函数使用 GPU 张量,则需要将其显式移动到适当的设备。 RNN 模型设计是从 PyTorch [示例](https://github.com/pytorch/examples/tree/master/word_language_model)存储库中的词语言模型中借用的,该存储库包含三个主要组件,一个嵌入表,一个`LSTM`层和一个解码器。 下面的代码将嵌入表和解码器包装到子模块中,以便它们的构造可以传递给 RPC API。 在`EmbeddingTable`子模块中,我们有意将`Embedding`层放在 GPU 上以涵盖用例。 在 v1.4 中,RPC 始终在目标工作线程上创建 CPU 张量参数或返回值。 如果函数使用 GPU 张量,则需要将其显式移动到适当的设备。
```py ```py
class EmbeddingTable(nn.Module): class EmbeddingTable(nn.Module):
...@@ -336,7 +336,7 @@ class Decoder(nn.Module): ...@@ -336,7 +336,7 @@ class Decoder(nn.Module):
``` ```
使用上述子模块,我们现在可以使用 RPC 将它们组合在一起以创建 RNN 模型。 在下面的代码中,`ps`代表参数服务器,该服务器托管嵌入表和解码器的参数。 构造函数使用[远程](https://pytorch.org/docs/master/rpc.html#torch.distributed.rpc.remote) API 在参数服务器上创建`EmbeddingTable`对象和`Decoder`对象,并在本地创建`LSTM`子模块。 在前进过程中,训练器使用`EmbeddingTable` `RRef`查找远程子模块,然后使用 RPC 将输入数据传递到`EmbeddingTable`,并获取查找结果。 然后,它通过本地`LSTM`层运行嵌入,最后使用另一个 RPC 将输出发送到`Decoder`子模块。 通常,要实现分布式模型并行训练,开发人员可以将模型分为多个子模块,调用 RPC 远程创建子模块实例,并在必要时使用`RRef`查找它们。 正如您在下面的代码中看到的那样,它看起来与单机模型并行训练非常相似。 主要区别是用 RPC 功能替换了`Tensor.to(device)` 使用上述子模块,我们现在可以使用 RPC 将它们组合在一起以创建 RNN 模型。 在下面的代码中,`ps`代表参数服务器,该服务器托管嵌入表和解码器的参数。 构造使用[远程](https://pytorch.org/docs/master/rpc.html#torch.distributed.rpc.remote) API 在参数服务器上创建`EmbeddingTable`对象和`Decoder`对象,并在本地创建`LSTM`子模块。 在前进过程中,训练器使用`EmbeddingTable` `RRef`查找远程子模块,然后使用 RPC 将输入数据传递到`EmbeddingTable`,并获取查找结果。 然后,它通过本地`LSTM`层运行嵌入,最后使用另一个 RPC 将输出发送到`Decoder`子模块。 通常,要实现分布式模型并行训练,开发人员可以将模型分为多个子模块,调用 RPC 远程创建子模块实例,并在必要时使用`RRef`查找它们。 正如您在下面的代码中看到的那样,它看起来与单机模型并行训练非常相似。 主要区别是用 RPC 功能替换了`Tensor.to(device)`
```py ```py
class RNNModel(nn.Module): class RNNModel(nn.Module):
......
...@@ -83,7 +83,7 @@ class ResNetBase(nn.Module): ...@@ -83,7 +83,7 @@ class ResNetBase(nn.Module):
``` ```
现在,我们准备定义两个模型碎片。 对于构造函数,我们只需将所有 ResNet50 层分为两部分,然后将每个部分移至提供的设备中。 两个分片的`forward`函数获取输入数据的`RRef`,在本地获取数据,然后将其移至所需的设备。 将所有层应用于输入后,它将输出移至 CPU 并返回。 这是因为当调用方和被调用方中的设备数量不匹配时,RPC API 要求张量驻留在 CPU 上,以避免无效的设备错误。 现在,我们准备定义两个模型碎片。 对于构造,我们只需将所有 ResNet50 层分为两部分,然后将每个部分移至提供的设备中。 两个分片的`forward`函数获取输入数据的`RRef`,在本地获取数据,然后将其移至所需的设备。 将所有层应用于输入后,它将输出移至 CPU 并返回。 这是因为当调用方和被调用方中的设备数量不匹配时,RPC API 要求张量驻留在 CPU 上,以避免无效的设备错误。
```py ```py
class ResNetShard1(ResNetBase): class ResNetShard1(ResNetBase):
...@@ -138,7 +138,7 @@ class ResNetShard2(ResNetBase): ...@@ -138,7 +138,7 @@ class ResNetShard2(ResNetBase):
## 第 2 步:将 ResNet50 模型片段拼接到一个模块中 ## 第 2 步:将 ResNet50 模型片段拼接到一个模块中
然后,我们创建一个`DistResNet50`模块来组装两个分片并实现流水线并行逻辑。 在构造函数中,我们使用两个`rpc.remote`调用分别将两个分片放在两个不同的 RPC 工作器上,并保持`RRef`到两个模型部分,以便可以在正向传递中引用它们。 `forward`函数将输入批量分为多个微批量,并将这些微批量以流水线方式馈送到两个模型部件。 它首先使用`rpc.remote`调用将第一个分片应用于微批量,然后将返回的中间输出`RRef`转发到第二个模型分片。 之后,它将收集所有微输出的`Future`,并在循环后等待所有它们。 请注意,`remote()``rpc_async()`都立即返回并异步运行。 因此,整个循环是非阻塞的,并将同时启动多个 RPC。 中间输出`y_rref`保留了两个模型零件上一个微批量的执行顺序。 微批量的执行顺序无关紧要。 最后,正向函数将所有微批量的输出连接到一个单一的输出张量中并返回。 `parameter_rrefs`函数是简化分布式优化器构造的助手,将在以后使用。 然后,我们创建一个`DistResNet50`模块来组装两个分片并实现流水线并行逻辑。 在构造中,我们使用两个`rpc.remote`调用分别将两个分片放在两个不同的 RPC 工作器上,并保持`RRef`到两个模型部分,以便可以在正向传递中引用它们。 `forward`函数将输入批量分为多个微批量,并将这些微批量以流水线方式馈送到两个模型部件。 它首先使用`rpc.remote`调用将第一个分片应用于微批量,然后将返回的中间输出`RRef`转发到第二个模型分片。 之后,它将收集所有微输出的`Future`,并在循环后等待所有它们。 请注意,`remote()``rpc_async()`都立即返回并异步运行。 因此,整个循环是非阻塞的,并将同时启动多个 RPC。 中间输出`y_rref`保留了两个模型零件上一个微批量的执行顺序。 微批量的执行顺序无关紧要。 最后,正向函数将所有微批量的输出连接到一个单一的输出张量中并返回。 `parameter_rrefs`函数是简化分布式优化器构造的助手,将在以后使用。
```py ```py
class DistResNet50(nn.Module): class DistResNet50(nn.Module):
......
...@@ -130,7 +130,7 @@ class Trainer(object): ...@@ -130,7 +130,7 @@ class Trainer(object):
## 批量 CartPole 求解器 ## 批量 CartPole 求解器
本节以 [OpenAI Gym](https://gym.openai.com/) 中的 CartPole-v1 为例,说明批量 RPC 的性能影响。 请注意,我们的目标是演示[`@rpc.functions.async_execution`](https://pytorch.org/docs/master/rpc.html#torch.distributed.rpc.functions.async_execution)的用法,而不是构建最佳的 CartPole 求解器或解决大多数不同的 RL 问题,我们使用非常简单的策略和奖励计算策略,并将重点放在 多观察者单代理批量 RPC 实现。 我们使用与前面的教程类似的`Policy`模型,如下所示。 与上一教程相比,不同之处在于其构造函数使用了一个附加的`batch`参数来控制`F.softmax``dim`参数,因为进行批量时,`forward`函数中的`x`参数包含来自多个观察者的状态 因此尺寸需要适当更改。 其他所有内容保持不变。 本节以 [OpenAI Gym](https://gym.openai.com/) 中的 CartPole-v1 为例,说明批量 RPC 的性能影响。 请注意,我们的目标是演示[`@rpc.functions.async_execution`](https://pytorch.org/docs/master/rpc.html#torch.distributed.rpc.functions.async_execution)的用法,而不是构建最佳的 CartPole 求解器或解决大多数不同的 RL 问题,我们使用非常简单的策略和奖励计算策略,并将重点放在 多观察者单代理批量 RPC 实现。 我们使用与前面的教程类似的`Policy`模型,如下所示。 与上一教程相比,不同之处在于其构造使用了一个附加的`batch`参数来控制`F.softmax``dim`参数,因为进行批量时,`forward`函数中的`x`参数包含来自多个观察者的状态 因此尺寸需要适当更改。 其他所有内容保持不变。
```py ```py
import argparse import argparse
...@@ -165,7 +165,7 @@ class Policy(nn.Module): ...@@ -165,7 +165,7 @@ class Policy(nn.Module):
``` ```
`Observer`的构造函数也会相应地进行调整。 它还带有`batch`参数,该参数控制用于选择动作的`Agent`函数。 在批量模式下,它将调用`Agent`上的`select_action_batch`函数,该函数将很快出现,并且该函数将以[`@rpc.functions.async_execution`](https://pytorch.org/docs/master/rpc.html#torch.distributed.rpc.functions.async_execution)装饰。 `Observer`的构造也会相应地进行调整。 它还带有`batch`参数,该参数控制用于选择动作的`Agent`函数。 在批量模式下,它将调用`Agent`上的`select_action_batch`函数,该函数将很快出现,并且该函数将以[`@rpc.functions.async_execution`](https://pytorch.org/docs/master/rpc.html#torch.distributed.rpc.functions.async_execution)装饰。
```py ```py
import gym import gym
...@@ -220,7 +220,7 @@ class Observer: ...@@ -220,7 +220,7 @@ class Observer:
``` ```
`Agent`的构造函数还采用`batch`参数,该参数控制如何对动作概率进行批量。 在批量模式下,`saved_log_probs`包含一张张量列表,其中每个张量包含一个步骤中所有观察者的动作抢夺。 如果不进行批量,则`saved_log_probs`是字典,其中的键是观察者 ID,值是该观察者的动作概率列表。 `Agent`的构造还采用`batch`参数,该参数控制如何对动作概率进行批量。 在批量模式下,`saved_log_probs`包含一张张量列表,其中每个张量包含一个步骤中所有观察者的动作抢夺。 如果不进行批量,则`saved_log_probs`是字典,其中的键是观察者 ID,值是该观察者的动作概率列表。
```py ```py
import threading import threading
......
...@@ -439,7 +439,7 @@ import gym ...@@ -439,7 +439,7 @@ import gym
让我们讨论一下 DQN 类,其中包含深度 Q 网络的架构: 让我们讨论一下 DQN 类,其中包含深度 Q 网络的架构:
* `__init__``self``learning_rate``gamma``n_features``n_actions``epsilon``parameter_changing_pointer``memory_size`):默认构造函数,用于分配超参数,例如: * `__init__``self``learning_rate``gamma``n_features``n_actions``epsilon``parameter_changing_pointer``memory_size`):默认构造,用于分配超参数,例如:
* `learning_rate` * `learning_rate`
* 伽马,即折现因子 * 伽马,即折现因子
* `n_feature`:状态中的要素数,即状态中的尺寸数 * `n_feature`:状态中的要素数,即状态中的尺寸数
...@@ -472,7 +472,7 @@ if __name__ == "__main__": ...@@ -472,7 +472,7 @@ if __name__ == "__main__":
.... ....
``` ```
`__init__`:在以下代码片段中解释了默认构造函数以及注释: `__init__`:在以下代码片段中解释了默认构造以及注释:
```py ```py
def __init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,memory_size): def __init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,memory_size):
...@@ -939,7 +939,7 @@ if __name__ == "__main__": ...@@ -939,7 +939,7 @@ if __name__ == "__main__":
让我们讨论 DQN 类及其参数,它包含一个深度 Q 网络的架构: 让我们讨论 DQN 类及其参数,它包含一个深度 Q 网络的架构:
* `__init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,memory_size)`:分配超参数的默认构造函数,例如: * `__init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,memory_size)`:分配超参数的默认构造,例如:
* `learning_rate` * `learning_rate`
* `gamma`:即折扣系数 * `gamma`:即折扣系数
* `n_feature`:状态下的要素数量,即状态下的尺寸数 * `n_feature`:状态下的要素数量,即状态下的尺寸数
...@@ -956,7 +956,7 @@ if __name__ == "__main__": ...@@ -956,7 +956,7 @@ if __name__ == "__main__":
* `fit(self)`:用于训练我们的深度 Q 网络 * `fit(self)`:用于训练我们的深度 Q 网络
* `epsilon_greedy(self,obs)`:它可以帮助我们针对给定的观察状态选择正确的操作,即按照现有策略利用操作或随机探索新操作 * `epsilon_greedy(self,obs)`:它可以帮助我们针对给定的观察状态选择正确的操作,即按照现有策略利用操作或随机探索新操作
现在,使用以下代码定义`__init__ default`构造函数 现在,使用以下代码定义`__init__ default`构造
```py ```py
def __init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer, def __init__(self,learning_rate,gamma,n_features,n_actions,epsilon,parameter_changing_pointer,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册