- 使用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
- 自动求导
使用自动微分来自动计算神经网络中的后向传递。 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)