1. 使用PyTorch的tensor,手动在网络中实现前向传播和反向传播:
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device(“cuda:0”)#取消注释以在GPU上运行

# N是批量大小; D_in是输入维度;
# H是隐藏的维度; D_out是输出维度。
N, D_in, H, D_out = 64, 1000, 100, 10

#创建随机输入和输出数据
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 随机初始化权重
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)

learning_rate = 1e-6
for t in range(500):
    # 前向传递:计算预测y
    h = x.mm(w1)
    h_relu = h.clamp(min=0)
    y_pred = h_relu.mm(w2)

    # 计算和打印损失
    loss = (y_pred - y).pow(2).sum().item()
    print(t, loss)

    # Backprop计算w1和w2相对于损耗的梯度
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.t().mm(grad_y_pred)
    grad_h_relu = grad_y_pred.mm(w2.t())
    grad_h = grad_h_relu.clone()
    grad_h[h < 0] = 0
    grad_w1 = x.t().mm(grad_h)

    # 使用梯度下降更新权重
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
  1. 自动求导
    使用自动微分来自动计算神经网络中的后向传递。 PyTorch中的 autograd包提供了这个功能。当使用autograd时,网络前向传播将定义一个计算图;图中的节点是tensor,边是函数, 这些函数是输出tensor到输入tensor的映射。这张计算图使得在网络中反向传播时梯度的计算十分简单。

这听起来很复杂,在实践中使用起来非常简单。 如果我们想计算某些的tensor的梯度,我们只需要在建立这个tensor时加入这么一句:requires_grad=True。这个tensor上的任何PyTorch的操作都将构造一个计算图,从而允许我们稍后在图中执行反向传播。如果这个tensor x的requires_grad=True,那么反向传播之后x.grad将会是另一个张量,其为x关于某个标量值的梯度。

有时可能希望防止PyTorch在requires_grad=True的张量执行某些操作时构建计算图;例如,在训练神经网络时,我们通常不希望通过权重更新步骤进行反向传播。在这种情况下,我们可以使用**torch.no_grad()**上下文管理器来防止构造计算图。

import torch

dtype = torch.float
device = torch.device("cpu")
# device = torch.device(“cuda:0”)#取消注释以在GPU上运行

# N是批量大小; D_in是输入维度;
# H是隐藏的维度; D_out是输出维度。
N, D_in, H, D_out = 64, 1000, 100, 10

# 创建随机Tensors以保持输入和输出。
# 设置requires_grad = False表示我们不需要计算渐变
# 在向后传球期间对于这些Tensors。
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 为权重创建随机Tensors。
# 设置requires_grad = True表示我们想要计算渐变
# 在向后传球期间尊重这些张贴。
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    # 前向传播:使用tensors上的操作计算预测值y; 
      # 由于w1和w2有requires_grad=True,涉及这些张量的操作将让PyTorch构建计算图,
    # 从而允许自动计算梯度。由于我们不再手工实现反向传播,所以不需要保留中间值的引用。
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

    # 使用Tensors上的操作计算和打印丢失。
    # loss是一个形状为()的张量
    # loss.item() 得到这个张量对应的python数值
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())

    # 使用autograd计算反向传播。这个调用将计算loss对所有requires_grad=True的tensor的梯度。
    # 这次调用后,w1.grad和w2.grad将分别是loss对w1和w2的梯度张量。
    loss.backward()

    # 使用梯度下降更新权重。对于这一步,我们只想对w1和w2的值进行原地改变;不想为更新阶段构建计算图,
    # 所以我们使用torch.no_grad()上下文管理器防止PyTorch为更新构建计算图
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # 反向传播后手动将梯度设置为零
        w1.grad.zero_()
        w2.grad.zero_()

TensorFlow里,有类似Keras,TensorFlow-Slim和TFLearn这种封装了底层计算图的高度抽象的接口,这使得构建网络十分方便。

在PyTorch中,包nn完成了同样的功能。nn包中定义一组大致等价于层的模块。一个模块接受输入的tesnor,计算输出的tensor,而且 还保存了一些内部状态比如需要学习的tensor的参数等。nn包中也定义了一组损失函数(loss functions),用来训练神经网络。

import torch

# N是批大小;D是输入维度
# H是隐藏层维度;D_out是输出维度
N, D_in, H, D_out = 64, 1000, 100, 10

# 产生随机输入和输出张量
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 使用nn包定义模型和损失函数
model = torch.nn.Sequential(
          torch.nn.Linear(D_in, H),
          torch.nn.ReLU(),
          torch.nn.Linear(H, D_out),
        )
loss_fn = torch.nn.MSELoss(reduction='sum')

# 使用optim包定义优化器(Optimizer)。Optimizer将会为我们更新模型的权重。
# 这里我们使用Adam优化方法;optim包还包含了许多别的优化算法。
# Adam构造函数的第一个参数告诉优化器应该更新哪些张量。
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

for t in range(500):

    # 前向传播:通过像模型输入x计算预测的y
    y_pred = model(x)

    # 计算并打印loss
    loss = loss_fn(y_pred, y)
    print(t, loss.item())

    # 在反向传播之前,使用optimizer将它要更新的所有张量的梯度清零(这些张量是模型可学习的权重)
    optimizer.zero_grad()

    # 反向传播:根据模型的参数计算loss的梯度
    loss.backward()

    # 调用Optimizer的step函数使它所有参数更新
    optimizer.step()

有时候需要指定比现有模块序列更复杂的模型;对于这些情况,可以通过继承nn.Module并定义forward函数,这个forward函数可以 使用其他模块或者其他的自动求导运算来接收输入tensor,产生输出tensor。

详见https://pytorch123.com/ThirdSection/LearningPyTorch/

pytorch实现DQN

import gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import collections
import random
from torch import optim
# from tqdm import tqdm

class DQN(nn.Module):
    def __init__(self, n_actions, n_inputs, lr=0.01):
        super(DQN, self).__init__()
        self.fc1 = nn.Linear(n_inputs, 64)  # 输入维度为4
        self.fc2 = nn.Linear(64, 128)
        self.fc3 = nn.Linear(128, n_actions)  # 输出维度为2

        self.optimizer = optim.Adam(self.parameters(), lr=lr)
        self.device = 'cpu'
        self.loss = nn.MSELoss()
        self.to(self.device)

    def forward(self, state):
        x = F.relu(self.fc1(state))
        x = F.relu(self.fc2(x))
        actions = self.fc3(x)
        return actions


class Agent:
    def __init__(self, env, n_actions, state_n_dim, gamma=0.99, epsilon=1):
        self.env = env
        self.n_actions = n_actions
        self.state_n_dim = state_n_dim
        self.gamma = gamma  # 未来值影响参数
        self.epsilon = epsilon  # 探索策略
        self.eps_min = 0.05  # epsilon会衰减,但不低于eps_min
        self.eps_dec = 5e-4  # 每次衰减0.0005
        self.iter_count = 0 #记录学习次数
        self.policy_net = DQN(self.n_actions, self.state_n_dim)    #待学习的网络
        self.target_net = DQN(self.n_actions, self.state_n_dim)  # 目标网络
        self.target_net.load_state_dict(self.policy_net.state_dict())#初始目标网络参数同待学习的网络
        self.replay_memory = collections.deque(maxlen=10000)#经验池
        self.min_replay_memory_size = 100  # 经验池至少要100个transition
        self.batch_size = 64    #每次从经验池中取64个trainsition学习
        self.update_target = 10  # 每学习10次更新一次target网络
        self.scores = []#记录每个episode的得分
    def update_replay_memory(self, obs):
        self.replay_memory.append(obs)

    def choose_action(self, state):
        if np.random.random() > self.epsilon:
            q = self.policy_net(torch.tensor([state], dtype=torch.float32))
            action = torch.argmax(q).item()  # 值最大的下标
            # print('根据网络输出,选择最大值对应的下标,作为action:', action)
        else:
            action = self.env.action_space.sample()
            # print('随机采样得到action:', action)
        return action

    def train(self):
        len_replay_memory = len(self.replay_memory)
        if len_replay_memory < self.batch_size:
            # print('经验池内的transition不够,目前:', len_replay_memory, '条!')
            return
        self.policy_net.optimizer.zero_grad()
        batch = random.sample(self.replay_memory, self.batch_size)  # 随机采样结果[(state, action, reward, next_state, done),(),..,()]
        states, actions, rewards, next_states, dones = [trans[0] for trans in batch], [trans[1] for trans in batch], [
            trans[2] for trans in batch], [trans[3] for trans in batch], [trans[4] for trans in batch]
        state_batch = torch.tensor(states, dtype=torch.float32)
        next_state_batch = torch.tensor(next_states, dtype=torch.float32)
        action_batch = torch.tensor(actions)  # tensor([0, 1, 1, 1, 0,...])
        reward_batch = torch.tensor(rewards)
        done_batch = torch.tensor(dones)  # tensor([False, False,False,...,False,False,True,False,False,True,...])
        batch_index = np.arange(self.batch_size, dtype=np.int32)  # [0 1 2 3 ...]

        pred_list = self.policy_net(state_batch)[batch_index, action_batch]  # 预测值tensor([0.8900, 0.9800, 0.7500])
        next_action_list = self.target_net(next_state_batch)  # [[0.02,0,89],[0.98,0.01],[0.75,-0.12],...]
        next_action_list[done_batch] = 0.0  # 下一步结束,y = r;其他,y = r + gamma * maxQ(next_s,a)-->[[0.02,0,89],[0.98,0.01],[0.0,0.0],...]
        new_q = reward_batch + self.gamma * torch.max(next_action_list, dim=1)[0]  # 取下一个状态动作对应价值最大的

        loss = self.policy_net.loss(pred_list, torch.as_tensor(new_q, dtype=torch.float32))  # as_tensor()修改数组值,张量值也会变
        loss.backward()  # 反向计算梯度
        self.policy_net.optimizer.step()  # 梯度传播,更新参数
        self.iter_count += 1

    def step(self):  # 完成一个episode
        done = False
        state = self.env.reset()  # [-0.00797301  0.02779841 -0.04731911  0.03995738]
        # print('游戏开始,得到一个状态state:', state)
        score = 0  # 记录一个episode的总得分
        while not done:
            action = self.choose_action(state)  # 返回最大值对应的下标,作为action,如 0
            next_state, reward, done, _ = self.env.step(action)
            # print('环境交互得到next_s:', next_state, 'reward: ', reward, 'done: ', done)
            score += reward
            self.update_replay_memory((state, action, reward, next_state, done))  # trainsition放入经验池
            if not self.iter_count % self.update_target:  # 每学习10次更新一次target网络
                self.target_net.load_state_dict(self.policy_net.state_dict())
            self.epsilon = self.epsilon - self.eps_dec if self.epsilon > self.eps_min else self.eps_min
            # print('完成一个step,epsilon更新为:', self.epsilon)
            state = next_state
        self.scores.append(score)


if __name__ == '__main__':
    round_count = 5  # 跑5次的结果取平均
    round_all_score = 0
    env = gym.make('CartPole-v0')
    n_actions = env.action_space.n  # 可选动作有2个
    state_n_dims = env.observation_space.shape[0]  # 状态的维度为4
    for i in range(round_count):
        agent = Agent(env, n_actions, state_n_dims)
        episodes = 900  # 每次跑900个回合
        for episode in range(episodes):
            agent.step()  # 完成一个episode,将每个step的trainsition放入经验池
            agent.train()  # 利用经验池中的trainsition学习
            # print('Episode: ', episode, '| reward: ', agent.scores[episode])
        avg_score = np.mean(agent.scores)  # 900个episodes的平均分
        print('Round: ', i, '| Average score: ', int(avg_score))
        round_all_score += avg_score
        agent.env.close()
    print('run ', round_count, 'rounds,the score is: ', int(round_all_score / round_count))

帮助理解代码:
1,deque是双边队列,超过maxlen后最早入队的出队

replay_memory = collections.deque(maxlen=5)
    replay_memory.append([2,4])
    replay_memory.append([6,8])
    replay_memory.append(10)
    replay_memory.append(11)
    replay_memory.append(12)
    replay_memory.append(13)
    print(replay_memory)
    print(replay_memory[1])
    replay_memory.appendleft(0)
    print(replay_memory[1])

输出

deque([[6, 8], 10, 11, 12, 13], maxlen=5)
10
[6, 8]

2,

a = [[1,3,2,3],[1,2,3,4],[4,5,3,5]]
    a_max = torch.max(torch.tensor(a),dim=1)
    print(a_max)
    print(a_max[0])

输出

torch.return_types.max(
values=tensor([3, 4, 5]),
indices=tensor([1, 3, 1]))
tensor([3, 4, 5])

3,

output = torch.tensor([[0.02,0.89],[0.98,-0.12],[-0.01,0.75]])
    batch_index = np.arange(3)#[0,1,2]
    action_batch = torch.tensor([1,0,1])
    pred_list = output[batch_index,action_batch]
    print(pred_list)

输出:

tensor([0.8900, 0.9800, 0.7500])

4,

output = torch.tensor([[0.02,0.89],[0.98,-0.12],[-0.01,0.75]])
    done_batch = torch.tensor([False,True,False])
    output[done_batch] = 0.0
    print(output)
    output[False] = 0.0
    print(output)
    output[True] = 0.0
    print(output)

输出:

tensor([[ 0.0200,  0.8900],
        [ 0.0000,  0.0000],
        [-0.0100,  0.7500]])
tensor([[ 0.0200,  0.8900],
        [ 0.0000,  0.0000],
        [-0.0100,  0.7500]])
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])

5,

env1 = gym.make('CliffWalking-v0')
    env1_N_ACTIONS = env1.action_space.n#4个动作
    env1_N_STATES = env1.observation_space.n#48个状态
    print(env1_N_ACTIONS)
    print(env1_N_STATES)
    env2 = gym.make('CartPole-v0')  # 小车立杆
    N_ACTIONS = env2.action_space.n#2个动作
    N_STATES_1 = env2.observation_space
    N_STATES_2 = env2.observation_space.shape
    N_STATES_3 = env2.observation_space.shape[0]#每个状态有4个维度
    print(N_ACTIONS)
    print(N_STATES_1)
    print(N_STATES_2)
    print(N_STATES_3)

输出:

4
48
2
Box([-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], [4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38], (4,), float32)
(4,)
4

6,如有需要转one-hot编码,可执行如下代码:

action = torch.tensor([0,1,1,0,0,0,0,0,1,1,1,1])
action_one_hot = F.one_hot(action,num_classes=2)
action_one_hot = torch.tensor(action_one_hot,dtype=torch.float32)
print(action_one_hot)

输出:

tensor([[1., 0.],
        [0., 1.],
        [0., 1.],
        [1., 0.],
        [1., 0.],
        [1., 0.],
        [1., 0.],
        [1., 0.],
        [0., 1.],
        [0., 1.],
        [0., 1.],
        [0., 1.]])

7,此外,F.cross_entropy(pre,target)会自动执行

soft_pre = F.softmax(pre)
log_soft_pre = torch.log(soft_pre)
loss = F.nll_loss(log_soft_pre, target)

因此,如果网络输出pre是经过softmax的,则可如下处理:

log_soft_pre = torch.log(pre)
loss = F.nll_loss(log_soft_pre, target)