怎么上传网站到ftp,网站备案没通过不了,小型私人会所装修设计,网站建设贰金手指科捷9Pytorch 入门笔记1. Pytorch下载与安装2. Pytorch的使用教程2.1 Pytorch设计理念及其基本操作2.2 使用torch.nn搭建神经网络2.3 创建属于自己的Dataset和DataLoader2.3.1 编写Dataset类2.3.2 编写Transform类2.3.3 将Transform融合到Dataset中去2.3.4 编写DataLoader类2.4 使用…
Pytorch 入门笔记1. Pytorch下载与安装2. Pytorch的使用教程2.1 Pytorch设计理念及其基本操作2.2 使用torch.nn搭建神经网络2.3 创建属于自己的Dataset和DataLoader2.3.1 编写Dataset类2.3.2 编写Transform类2.3.3 将Transform融合到Dataset中去2.3.4 编写DataLoader类2.4 使用Tensorboard来可视化训练过程2.4.1 Images可视化2.4.2 Graph可视化2.4.3 训练中的LossAccuracy可视化2.5 Pytorch实现DQN算法2.6 Pytorch实现Policy Gradient算法1. Pytorch下载与安装
在Pytorch官网进行官方提供的下载方法Pytorch官网打开官网后选择对应的操作系统和cuda版本。如果需要安装GPU版本的Pytorch则需要下载对应CUDA版本的Torch版本例如我装的是CUDA 10.1Python版本是3.7。 查看已安装CUDA、cuDNN版本的方法 Windows: 访问文件夹 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA 看该目录下文件夹名称如果是v10.0则代表10.0版本的CUDA访问C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\include\cudnn.h来查看cuDNN的版本。 Linux: 使用命令 cat /usr/local/cuda/version.txt 查看cuda版本使用命令 cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2 查看cuDNN版本。 如果没有安装cuda和cuDNN则需要先安装这两样东西再装Pytorch。先安装cuda进入cuda官网根据自己的操作系统选择对应版本version 一栏代表的是你Windows的操作系统版本 下载exelocal安装程序之后一路选择默认安装路径就好了。安装完成后再cmd窗口内输入nvcc -V若看到以下信息证明cuda安装成功 cuda安装好了之后下载对应的cnDNN进入cuDNN安装链接这里需要先注册一个Nvidia的账号并登录才能进行下一步登录后看到以下网址 根据安装的cuda版本选择对应的cuDNN的版本即可比如我们演示的是cuda 10.2版本这里就选择对应的7.6.5版本的cuDNN就好。下载好后是个压缩文件解压后得到3个文件夹binincludex64将这三个文件下的内容分别拷贝到安装的cuda目录对应的binincludex64文件夹下即可cuda的默认安装路径如下 完成了cuda和cuDNN的安装后我们就可以回到Pytorch的安装中去了看文章最开始的那张图找到对应的版本后复制图片最下方的pip命令在cmd窗口执行即可开始进行安装 pip install torch1.3.1 torchvision0.4.2 -f https://download.pytorch.org/whl/torch_stable.html -i https://pypi.tuna.tsinghua.edu.cn/simple 这里使用了清华源来加速下载等待一段时间后Pytorch就安装完毕了。
2. Pytorch的使用教程
2.1 Pytorch设计理念及其基本操作
Pytorch在设计的时候最大的亮点是在于其可以替代Numpy库numpy库中的array运算是依赖于将数组编译成机器码再到CPU运行进行加速的。而Torch是将array在Pytorch里面叫Tensor变成GPU上的变量进行运算的因此在进行大规模运算是Torch能更快速。
Tensor的运算
Tensor的定义和运算都和np.array()差不多很多操作都是一样的下面是一些基本操作的介绍
import torch 创建 Tensor 的方法
x torch.tensor([3, 3]) # 创建一个值为[3, 3]的Tensor
x torch.empty(5, 3) # 5x3空矩阵
x torch.ones(3, 3) # 值全为1的3x3矩阵
y torch.randn_like(x, dtypetorch.long) # 创建一个和x一样shape的随机矩阵并将每一个数字dtype变成long Tensor 的属性
print(x.size()) # 相当于np.array中的.shape属性x.size()本质是一个tuple可以使用tuple的全部属性 Resize Tensor
x torch.tensor([[1, 2], [3, 4]])
y x.view(-1, 4) # y ([1, 2, 3, 4]), 和np.reshape()是一样的 Tensor 和 Numpy 的互相转换
x_np np.arrary([1, 1])
x_tensor torch.from_numpy(x_np) # 从numpy转换到tensor
x_np_2 x_tensor.numpy() # 从tensor转换到numpy CPU 到 GPU 的转换
if torch.cuda.is_available():device torch.device(cuda)x torch.ones(5, 3)x_dev x.to(device) # CPU tensor 转 GPU tensorx_host x_dev.to(cpu, torch.doule) # GPU tensor 转 CPU tensor自动求导机制
torch.Tensor这个对象拥有自动求导的功能每个Tensor在被计算的时候都会自动记录这些运算过程在最后计算梯度的时候就能够追踪的这些计算过程快速的进行梯度计算。如果要打开计算追踪这个功能需要将Tensor.requires_grad这个属性设置为True默认是False的当该追踪计算功能打开后只需要调用.backward()就能够进行自动的梯度求导了。 在我们训练的时候通常会为Tenosor打开.requires_grad属性但在训练好一个模型后我们在使用这个模型的时候这时候我们就不需要计算梯度了因为不会再做反向传播了这时候我们就可以关闭.requires_grad这个属性来减少运算时间。使用with torch.no_grad():来关闭Tensor的计算追踪功能
with torch.no_grad(): # 整个过程中x, y, z三个tensor均不会开启计算追踪torch.add(x, y, outz) # 将tensor x和tensor y相加后放入到tensor z中2.2 使用torch.nn搭建神经网络
在Pytorch中搭建一个神经网络需要自己写一个类该类需要继承自torch.nn.Module类nn.Module主要包含以下两个特点
nn.Module类中已经预先定义好了多种类型的Layer例如Conv2DLinear等使用时直接调用即可。nn.Module中包含一个forward(input)方法该方法接收一个input输入并return一个output值通常这个forward()方法都需要用户自己来实现构建从输入到输出的网络结构。
通常训练一个网络模型的流程如下
首先定义所有的网络架构模型一共有几层每一层的类型是什么ConvolutionLinear可学习的参数有哪些weight bias。遍历所有的数据集输入一个 input通过 forward() 过程计算得到一个 output。比较 output 和 label 的值计算出 loss。根据梯度和 Loss 计算网络模型中所有可学习参数的梯度信息。进行参数更新 更新公式通常为weight weight - learning_rate * gradient
我们使用 torch.nn 来搭建一个卷积神经网络用于识别 32*32 的单通道灰度图。首先我们先来理一下整个网络的结构 1. Convolutional2D Shape Change IwI_wIw代表输入图层的widthOwO_wOw代表经过Conv2D层后图层的widthFwF_wFw代表卷积核Filter的widthSwS_wSw代表在width上横跨的步长PwP_wPw代表在width方向上的padding值则有以下公式Height方向上同理OwIw−Fw2PwSw1O_w\frac{I_w - F_w 2P_w}{S_w}1OwSwIw−Fw2Pw1 2. Maxpooling Shape Change IwI_wIw代表输入图层的widthOwO_wOw代表经过Maxpooling2D层后图层的widthMwM_wMw代表池化层的widthSwS_wSw代表在width上横跨的步长通常是等于池化层的宽度的则有以下公式Height方向上同理OwIw−MwSw1O_w\frac{I_w - M_w}{S_w}1OwSwIw−Mw1 因此整个网络架构如下 根据上述网络设计编写代码搭建一个神经网络需要编写
自己写一个类 net名字自取继承自torch.nn.Module()利用 torch.nn 中的 Layers 构建自己的Layer实现 net 类中的 forward() 方法
以下是代码实现
import torch
import torch.nn as nn
import torch.nn.functional as Fclass Net(nn.Module):def __init__(self):super(Net, self).__init__()self.Conv1 nn.Conv2d(1, 6, 3) # params (input_channal_num, out_channal_num, filter_size)self.Conv2 nn.Conv2d(6, 16, 3)self.fc1 nn.Linear(6*6*16, 120)self.fc2 nn.Linear(120, 80)self.fc3 nn.Linear(80, 10) # predict 10 classesdef forward(self, x):x F.max_pool2d(F.relu(self.Conv1(x)), 2)x F.max_pool2d(F.relu(self.Conv2(x)), 2)x x.view(-1, self.get_flatten_num(x))x F.relu(self.fc1(x))x F.relu(self.fc2(x))x F.relu(self.fc3(x))return xdef get_flatten_num(x):features_num x[1:] # drop out the batch size (which is x[0])flatten_num 1for feature_num in features_num:flatten_num * feature_numreturn flatten_numnet Net()
print(net)这样我们就定义好了一个神经网络打印一下这个神经网络可以看到 这样一来整个前向传播就写完了现在我们来写反向传播的过程首先我们需要计算 Loss 再反向梯度传播下图是一个简单的反向传播示意图 Input 层有两个神经元hidden layer 也有两个神经元最终 output 层有两个神经元。target 代表 Label 数据。假设我们网络内不存再bias只有 weight 是需要学习的那么我们需要根据 target 和神经网络的 output 之间的误差值Loss来修改 weight 值以达到学习的效果。在这里我们以w5w_5w5这个学习参数为例通过反向链式求导来计算它的梯度Loss 是指两个 output 值与 target 之间的 MSE Error。对w5w_5w5进行反向求导由于从h1h_1h1到o1o1o1是先经过LinearLinearLinear层FC层再经过一个sigmoidsigmoidsigmoid层再输出到o1o_1o1的。因此从o1o_1o1到h1h_1h1的反向求导需要先对sigmoidsigmoidsigmoid这个计算过程求导再对LinearLinearLinear计算过程求导这就是为什么上图中会先对Os1Os_1Os1求导再对O1O_1O1求导。 我们计算随机生成一个 target 让其与我们的 output 进行 Loss 计算
net.zero_grad() # 先清空网络中的梯度变量中的值target torch.rand(1, 10) # 随机生成target标签
criterion nn.MSELoss()
loss criterion(target, out) # 计算loss值
loss.backward() # 反向求导函数这时我们已经计算出所有的梯度了现在需要我们去 update 这些 weights 的值最简单的更新的方式是使用 SGD 的方法即 weightweight−gradient∗learningrateweight weight - gradient * learning_rateweightweight−gradient∗learningrate除此之外还有 AdamRMSProp等等Pytorch 将这些更新的算法称作“优化器”optimizer封装在了函数里我们只需要调用就可以了
import torch.optim as optimoptimizer optim.SGD(net.parameters(), lr0.01)# 每一轮训练的时候
optimizer.zero_grad() # 一定要记得清空优化器中的上一轮梯度信息
output net(input)
loss criterion(output, target)
loss.backward()
optimizer.step() # 更新梯度信息至此我们已经完成了 Pytorch 的安装并使用 torch 搭建了一个前向神经网络以及反向传播的全过程。
2.3 创建属于自己的Dataset和DataLoader
现在我们已经成功搭建了一个CNN网络了问题是我们的CNN网络只能接收数字类型的 Input而我们往往数据集都是图片类型的因此为了将图片数据转换为神经网络能够接收的数字类型我们需要写 Dataset 类和 DataLoader 类这两个类的作用分别为 Dataset 类的作用 Dataset 用于读取对应路径的数据集比如说某个特定文件夹下的图片并转换成神经网络能够接收的数据类型举例来说我们要用人脸识别数据集做训练那么我们就需要编写一个 FaceDataset() 来把人脸的图片转换向量数据。 DataLoader 类的作用 往往在我们训练神经网络时都会使用到 mini-batch 的方法一次读取多个样本而不是只读一个样本那么如何从数据集中去采样一个batch的数据样本就是 DataLoader 要实现的功能了。 我们使用 Face Landmark 的数据集来举例下载一个 Landmark 数据集这个数据集中包含【人脸图片】和【Landmark】两个数据Landmark 是指人脸上的68个特征点不清楚的可以查查dlib这个库。如下图所示 Landmark 就是指红色的68个点在数据集中存放的是这68个点的xy坐标。
2.3.1 编写Dataset类
我们现在来写一个 FaceLandmarksDataset() 类用于读取数据中的【人脸图片】和【Landmark】信息对于 FaceLandmarksDataset() 这个类我们继承自 torch 的 Dataset 类并且实现三个方法__init__, __len__, __getitem__。 __len__ 方法用于返回这个 Dataset 的数据集长度共有多少个。__getitem__ 可以让你在调用 dataset[i] 的时候直接调用这个方法非常方便。 import os
import torch
import pandas as pd
from skimage import io, transform
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils# Create a Dataset Class to transform the image data to array dataclass FaceLandmarksDataset(Dataset):def __init__(self, root, transformNone):self.root root # root 是指你存放数据集的路径self.transform transform # transform 是数据预处理的方法后面会讲这里先跳过data pd.read_csv(os.path.join(root, face_landmarks.csv))self.image_names data.iloc[:, 0].as_matrix() # 数据集的第一列是图片的名字self.land_marks data.iloc[:, 1:].as_matrix() # 数据集的后面列全是landmarksdef __len__(self):return len(self.image_names)def __getitem__(self, idx):assert isinstance(idx, int), Idx must be int.land_marks self.land_marks[idx, :].reshape(-1, 2)image_name self.image_names[idx]image io.imread(os.path.join(self.root, image_name))sample {image: image, landmarks: land_marks} # 定义sample的数据结构是个字典{人脸图片np.arraylandmarksnp.array}if self.transform:sample self.transform(sample)return sample这样我们就写好了一个读取自己数据集的 Dataset 类现在来尝试使用这个类
def show_landmarks(sample, ax):print(sample[image].shape)ax.imshow(sample[image])ax.scatter(sample[landmarks][:, 0], sample[landmarks][:, 1])face_dataset FaceLandmarksDataset(./faces) # 实例化
for i in range(5):sample face_dataset[i] # 这里会调用类中的 __getitem__()方法ax plt.subplot(1, 5, i1)ax.set_title(Sample # str(i1))show_landmarks(sample, ax)plt.show()结果如下所示画出了五张人脸以及他们对应的 landmark 点 2.3.2 编写Transform类
为什么会有 Transform 类这是因为在很多时候我们并不是把原图传入神经网络在输入数据之前会对原数据进行一些列的预处理preprocessing例如数据集中的图片不会全部都是相同尺寸的但大多神经网络都需要输入图片的尺寸是固定的因此我们需要对数据集中的做预处理resize 或是 crop 以保证输入数据的尺寸是符合神经网络要求的。Transform 类就是用于做数据预处理而设计的类我们一共设计三个类来组成 Transform 类Rescale() RandomCrop()ToTensor() Rescale类这个类用于对图片进行 resize。RandomCrop类这个用于对图片进行裁剪且是任意位置随机裁剪。ToTensor类这个类用于将 np 数组格式转换成 torch 中 tensor 的格式图片的 np 数组是 [height, width, channel]而 torch 中图片的 tensor 格式是 [channel, height, width]。 这些类中都要实现 __call__ 方法call 方法可以使得这些类在被实例化的时候就调用 __call__ 函数下的方法。
class Rescale(object):def __init__(self, output_size):assert isinstance(output_size, (list, tuple)), output size must be tuple or list.self.output_size output_sizedef __call__(self, sample):image, land_marks sample[image], sample[landmarks]h, w image.shape[:2]new_h, new_w self.output_sizeimg transform.resize(image, (int(new_w), int(new_h)))land_marks land_marks * [new_w / w, new_h / h]return {image: img, landmarks: land_marks}class RandomCrop(object):def __init__(self, output_size):assert isinstance(output_size, (int, tuple)), output size must be int or tuple.if isinstance(output_size, int):self.output_size [output_size, output_size]else:assert len(output_size) 2, output size tuples length must be 2.self.output_size output_sizedef __call__(self, sample):image, land_marks sample[image], sample[landmarks]h, w image.shape[:2]crop_h, crop_w self.output_sizetop np.random.randint(0, h - crop_h)left np.random.randint(0, w - crop_w)image image[top:topcrop_h, left:leftcrop_w]land_marks - [left, top]return {image:image, landmarks:land_marks}class ToTensor(object):def __call__(self, sample):image, land_marks sample[image], sample[landmarks]image image.transpose((2, 0, 1))return {image:image, landmarks:land_marks}这样我们就实现好了这三个功能的类接下来我们将这三个方法合成一个方法可以依次按顺序对一个 sample 做这三个操作。通过 torchvision.transforms() 来融合这三个方法。
scale Rescale((256, 256)) # 单实现 Resacle 处理
random_crop RandomCrop(128) # 单实现 RandomCrop 处理
compose transforms.Compose([Rescale((256, 256)), RandomCrop(224)]) # 对一个sample先Rescale再RandomCropsample face_dataset[66]for i, tsfm in enumerate([scale, random_crop, compose]): # 同时展示Rescale、RandomCrop和先Resacle再RandomCrop的效果transformed_data tsfm(sample)ax plt.subplot(1, 3, i1)ax.set_title(type(tsfm).__name__)show_landmarks(transformed_data, ax)
plt.show()效果如下Compose 是指结合了前两种方法先 resize 后再随机 Crop 后的样子 2.3.3 将Transform融合到Dataset中去
前面提到Transform 的作用是做数据预处理的因此我们期望每次从 Dataset 中读取数据的时候这些数据都被预处理一次因此我们可以把 Transform 融合到 Dataset 中去还记得我们在写 Dataset 的时候有留一个参数 transform 吗我们现在可以传进去就好了
transformed_dataset FaceLandmarksDataset(root./faces,transformtransforms.Compose([Rescale((256, 256)),RandomCrop(224), ToTensor()]))这样我们就写好了一个带有 Transform 的 Dataset 了。
2.3.4 编写DataLoader类
在有了 Dataset 类后我们就可以开始编写 DataLoader 类了Dataloader 主要是从Dataset 中去 sample 一个batch 的数据。我们可以直接使用 Pytorch 提供的 Dataloader 类
dl torch.utils.data.DataLoader(transformed_dataset, batch_size4, shuffleTrue, num_workers4) # windows下num_workers参数要等于0不然会报错这样就可以从我们的 Dataset 中取一个batch 的数据了下面我们来看看怎么用 dataloader 取出 batch data
def show_landmark_batch(sample_batch):images_batch, landmarks_batch sample_batch[image], sample_batch[landmarks]batch_size len(images_batch)im_size images_batch.size(2)grid utils.make_grid(images_batch)plt.imshow(grid.numpy().transpose(1, 2, 0))grid_border_size 2for i in range(batch_size):plt.scatter(landmarks_batch[i, :, 0].numpy() i * im_size (i 1) * grid_border_size, landmarks_batch[i, :, 1].numpy() grid_border_size, s10)plt.title(Batched Samples)for i_batch, sample_batch in enumerate(dl): # dataloader是一个迭代器迭代一次取出一个batch的sampleif i_batch 0:show_landmark_batch(sample_batch)plt.show()可视化结果如下 2.4 使用Tensorboard来可视化训练过程
Tensorboard 无疑是一个非常好用且直观的神经网络训练可视化利器Pytorch 中也集成了使用 Tensorboard 方法建立一个SummaryWriter() 类即可完成各种类型的可视化下面我们就一起尝试使用 Tensorboard 来做可视化。
2.4.1 Images可视化
有时候我们拿到的只是图片信息的向量信息例如MNIST数据集我们想查看这些图片到底长什么样这时候我们可以往 Tensorboard 中添加图片数据信息来可视化这些图片首先我们先建立一个 writer 对象。
from torch.utils.tensorboard SummaryWriter
writer SummaryWriter(run/experience_1) # 将log信息都保存在run/experience_1的目录下这样我们就建立好了一个 writer 对象之后的所有操作都通过 writer 对象来完成。
import matplotlib.pyplot as plt
import numpy as np
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optimtransform transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
trainset torchvision.datasets.FashionMNIST(./data, downloadTrue, trainTrue, transformtransform) # 加载数据集
testset torchvision.datasets.FashionMNIST(./data, downloadTrue, trainFalse, transformtransform) # 加载数据集
trainloader torch.utils.data.DataLoader(trainset, batch_size4, shuffleTrue)
testloader torch.utils.data.DataLoader(testset, batch_size4, shuffleTrue)classes (T-shirt/top, Trouser, Pullover, Dress, Coat,Sandal, Shirt, Sneaker, Bag, Ankle Boot)dataiter iter(trainloader)
images, labels dataiter.next()
img_grid torchvision.utils.make_grid(images) # 获取一个batch的数据并将4张图片合并成一张图片writer.add_image(four_fashion_samples, img_grid) # 将图片数据添加到 Tensorboard-图片名称图片数据这时候我们前往“run”文件夹存放的目录在终端中输入tensorboard --logdirrun来开启Tensorboard 打开后如下所示可以看到在 Images 这一栏已经显示了我们刚才添加进去的图片信息 2.4.2 Graph可视化
如果我们想更清楚的看到我们建立的神经网络模型长什么样我们可以使用writer.add_graph()方法来将模型添加入 Tensorboard
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 nn.Conv2d(1, 6, 5)self.pool nn.MaxPool2d(2, 2)self.conv2 nn.Conv2d(6, 16, 5)self.fc1 nn.Linear(16 * 4 * 4, 120)self.fc2 nn.Linear(120, 84)self.fc3 nn.Linear(84, 10)def forward(self, x):x self.pool(F.relu(self.conv1(x)))x self.pool(F.relu(self.conv2(x)))x x.view(-1, 16 * 4 * 4)x F.relu(self.fc1(x))x F.relu(self.fc2(x))x self.fc3(x)return xnet Net() # 实例化CNN网络
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(net.parameters(), lr0.001, momentum0.9)
writer.add_graph(net, images) # 将网络添加入Tensorboard-(神经网络对象随机传一组Input)之后我们可以在 Tensorboard 的Graphs这一栏看到我们整个神经网络的架构 2.4.3 训练中的LossAccuracy可视化
在模型训练过程中我们想实时的监测 LossAccuracy 等值的变化我们可以通过writer.add_scalar()方法往 Tensorboard 中加入监测变量
running_loss 0.0
right_num 0
for epoch in range(1): # loop over the dataset multiple timesfor i, data in enumerate(trainloader, 0):# get the inputs; data is a list of [inputs, labels]inputs, labels data# zero the parameter gradientsoptimizer.zero_grad()# forward backward optimizeoutputs net(inputs)loss criterion(outputs, labels)loss.backward()optimizer.step()_, preds_tensor torch.max(outputs, 1)right_num (preds_tensor.numpy() labels.numpy()).sum()running_loss loss.item()if i % 1000 999: # 每1000步更新一次running_loss / 1000accaracy right_num / 4000writer.add_scalar(training/loss, running_loss, epoch * len(trainloader) i) # 添加Loss监测变量-(变量名称变量值step)writer.add_scalar(training/accaracy, accaracy, epoch * len(trainloader) i) # 添加Accuracy监测变量-(变量名称变量值step)print([%d] Loss: %.4f Acc: %.2f % (i, running_loss, accaracy))running_loss 0.0right_num 0print(Finished Training)在训练过程中在Scalars一栏可以监测“Loss”和“Accuracy”的变化情况Tensorboard默认每30s刷新一次训练结果如下所示 2.5 Pytorch实现DQN算法
Deep Reinforcement Learning 结合了深度学习和增强学习使得增强学习能有更好的效用。这次我们使用Pytorch来实现一个DQN学会玩 CartPole-v0 的立杆游戏。DQN 一共分为target network 和 evaluate network我们在 DQN 类中会实现这两个网络。在更新 evaluate network 时我们使用qevalq_{eval}qeval 和 rtγ∗qnextr_t\gamma*q_{next}rtγ∗qnext 之间的差作为 Loss 值来更新网络其中 qevalq_{eval}qeval 来自evaluate networkqnextq_{next}qnext来自target network。DQN架构图和 net 示意图如下所示这里默认已经有DQN的基础如果不清楚DQN可以参考强化学习入门笔记。 import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import gym
from torch.utils.tensorboard import SummaryWriter# Hyper Parameters
EPOCH 400
BATCH_SIZE 32
LR 0.01 # learning rate
EPSILON 0.9 # greedy policy
GAMMA 0.9 # reward discount
TARGET_REPLACE_ITER 100 # target update frequency
MEMORY_CAPACITY 2000
env gym.make(CartPole-v0)
env env.unwrapped
N_ACTIONS env.action_space.n
N_STATES env.observation_space.shape[0]import 一些包和定义一些超参数这里要用到 gym 库需要先pip install gym。
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.fc1 nn.Linear(N_STATES, 20)self.fc1.weight.data.normal_(0, 0.1) self.fc2 nn.Linear(20, N_ACTIONS)self.fc2.weight.data.normal_(0, 0.1) def forward(self, x):x F.relu(self.fc1(x))return self.fc2(x)定义一个Net类这里我们只有一层 Hidden Layer。输入为 gym 给的 observation 的维度输出为 action_space 的维度下面我们实现 DQN 类。
class DQN(object):def __init__(self):self.target_net, self.evaluate_net Net(), Net()self.memory np.zeros((MEMORY_CAPACITY, N_STATES * 2 2))self.loss_Function nn.MSELoss()self.optimizer torch.optim.Adam(self.evaluate_net.parameters(), lrLR)self.point 0self.learn_step 0def choose_action(self, s):s torch.unsqueeze(torch.FloatTensor(s), 0) # torch 不支持传入单样本只能传入batch的data因此这里单样本数据需要提升一个维度if np.random.uniform() EPSILON: # epsilon-greedy探索 return torch.max(self.evaluate_net.forward(s), 1)[1].data.numpy()[0]else:return np.random.randint(0, N_ACTIONS)def store_transition(self, s, a, r, s_):self.memory[self.point % MEMORY_CAPACITY, :] np.hstack((s, [a, r], s_))self.point 1def sample_batch_data(self, batch_size):perm_idx np.random.choice(len(self.memory), batch_size)return self.memory[perm_idx]def learn(self) - float:if self.learn_step % TARGET_REPLACE_ITER 0:self.target_net.load_state_dict(self.evaluate_net.state_dict())self.learn_step 1batch_memory self.sample_batch_data(BATCH_SIZE)batch_state torch.FloatTensor(batch_memory[:, :N_STATES])batch_action torch.LongTensor(batch_memory[:, N_STATES : N_STATES 1].astype(int))batch_reward torch.FloatTensor(batch_memory[:, N_STATES 1 : N_STATES 2])batch_next_state torch.FloatTensor(batch_memory[:, -N_STATES:])q_eval self.evaluate_net(batch_state).gather(1, batch_action) # 由于返回的是对每个action的value因此用gather()去获得采取的action对应的valueq_next self.target_net(batch_next_state).detach() # target network是不做更新的所以要detach()q_target batch_reward GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1)loss self.loss_Function(q_eval, q_target)self.optimizer.zero_grad()loss.backward()self.optimizer.step()return loss.data.numpy()DQN中一共有几个需要注意的部分memorytarget networkevaluate network。其中 memory 是一个环形的队列当经验池满了之后就会把旧的数据给覆盖掉。target network会在一定的steps之后把evaluate network的参数直接复制到自己的网络中。
dqn DQN()writer SummaryWriter(run/MemoryCapacity_100_CustomReward/)
writer.add_graph(dqn.evaluate_net, torch.randn(1, N_STATES))global_step 0
for i in range(EPOCH):s env.reset()running_loss 0cumulated_reward 0step 0while True:global_step 1env.render()a dqn.choose_action(s)s_, r, done, _ env.step(a)# 自定义reward不用原始的rewardx, x_dot, theta, theta_dot s_r1 (env.x_threshold - abs(x)) / env.x_threshold - 0.8r2 (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5r r1 r2dqn.store_transition(s, a, r, s_)cumulated_reward rif dqn.point MEMORY_CAPACITY: # 在经验池满了之后才开始进行学习loss dqn.learn()running_loss lossif done or step 2000:print(【FAIL】Episode: %d| Step: %d| Loss: %.4f, Reward: %.2f % (i, step, running_loss / step, cumulated_reward))writer.add_scalar(training/Loss, running_loss / step, global_step)writer.add_scalar(training/Reward, cumulated_reward, global_step)breakelse:print(\rCollecting experience: %d / %d... %(dqn.point, MEMORY_CAPACITY), end)if done:breakif step % 100 99:print(Episode: %d| Step: %d| Loss: %.4f, Reward: %.2f % (i, step, running_loss / step, cumulated_reward))step 1s s_以上是学习的过程在 reward 的那个地方使用了自定义的 reward而不是使用 gym 给定的 r合理的制定 reward 可以帮助模型学的更好的效果我们后面会对使用不同的 reward 学习的效果进行对比。模型结构如下图所示 训练过程中按照上一节讲的内容利用 tensorboard 追踪 Loss 和 Reward 两个变量的值变化情况 可以看到Loss 的值在逐步趋于0这说明模型是趋于收敛的Reward 值也在总体上升最高达到600左右做了 smooth 后的值不是原本值倒立摆能够稳住很长一段时间。下面我们来看一下如果我们修改部分超参数的值会出现什么样的训练效果我一共做了4个对比试验
使用自定义回报值经验池容量设置为100使用默认回报值经验池容量为100使用默认回报值使用更为复杂的网络结构经验池容量为100使用默认回报值使用更为复杂的网络结构经验池容量为2000
回报值设定对模型效果的影响 在经验池容量和模型结构都相同的情况下使用自定义回报橘色和默认回报蓝色的效果如下图所示 可以看到Loss 函数最终都是趋于很小的值这说明使用两种方法模型都是收敛的但是 Reward 值差别非常大。为什么同样都是收敛的情况下效果差这么多这是因为模型收敛是指训练出来的Q-Net总会选择含有最大Q值的Action这样的Loss最小。但是Action的Q值是通过 Reward 来计算的rtargmaxaQπ(s,a)r_t argmax_aQ^\pi(s, a)rtargmaxaQπ(s,a)。因此 reward 的制定直接决定了模型效果的好坏。所以如果 reward 不能很好的与最终效用产生紧密的联系模型虽然能有找到最大效用行为的能力但这个最大效用的行为并不能产生很好的效果。因此如果看到 Loss 已经很小但最终效用不怎么好的情况下一般就考虑重新制定 reward选择一个能够更精准表示最终效用的评价函数。
模型复杂度对效用的影响 在经验池容量相同且均使用默认回报的情况下尝试修改原有模型使得模型变得稍微复杂一些看看效果会不会更好一些将模型再加一层 hidden layer 并且增加每一层神经元的个数代码如下
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.fc1 nn.Linear(N_STATES, 80)self.fc1.weight.data.normal_(0, 0.1) self.fc2 nn.Linear(80, 40)self.fc2.weight.data.normal_(0, 0.1) self.fc3 nn.Linear(40, N_ACTIONS)self.fc3.weight.data.normal_(0, 0.1) def forward(self, x):x F.relu(self.fc1(x))x F.relu(self.fc2(x))return self.fc3(x)模型结构如下 使用复杂模型红色和使用简单模型蓝色的 Reward 情况对比如下 可以看到两个模型 Loss 都是趋于0证明模型均收敛红色复杂模型的得比蓝色简单模型要稍微高一些但和使用自定义回报的模型比起来还是差很远。
经验池容量对模型效果的影响 对于相同复杂度的模型使用相同回报值通过改变经验池容量来观察对模型效果有什么影响容量为100红色和容量为2000蓝色模型的对比结果如下 观察可得扩展经验池后的效果会比小经验池的效果要好一些。因此通过以上总结我们可以得出在DQN中不同超参数对模型效果影响程度的结论即回报函数制定 经验池容量 模型复杂度。
2.6 Pytorch实现Policy Gradient算法
在实现了 DQN 这种 value-based 的算法之后我们尝试实现一种 policy-based 的方法Policy-Gradient。策略梯度是很多经典算法的基石包括 A3CPPO在内的多种算法都是基于策略梯度的基本思想来实现的因此这次我们同样基于 CartPole 的简单场景来实现 Policy Gradient 算法默认已经有PG的基础如果不清楚PG可以参考强化学习入门笔记。 Policy Gradinet 的示意图如下根据输入observation策略决策网络会输出每一个action对应被采取的概率效用越高的action概率就会被预测的越大。我们可以根据这个概率来进行行为选择这里和上面的DQN不一样DQN的行为选择是一定概率选择最大效用的行为一定概率随机选行为在PG算法中直接按照概率来选行为即结合了多选择高效用的行为的标准又结合了一定概率进行行为探索的标准。一旦选择好了一个行为后我们就去计算这个行为的效用是多少如果效用高我们就增加这个动作的概率反之则降低选择该行为的概率。 先定义神经网络层这里使用1个 hidden layer10个神经元
import gym
import numpy as np
import torch
import torch.nn.functional as F
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.utils.tensorboard import SummaryWriterWRITE_TENSORBOARD_FLAG Trueclass Net(nn.Module):def __init__(self, observation_dim, action_dim):super(Net, self).__init__()self.observation_dim observation_dimself.action_dim action_dimself.fc1 nn.Linear(self.observation_dim, 10)self.fc2 nn.Linear(10, self.action_dim)def forward(self, x):x F.tanh(self.fc1(x))return F.softmax(self.fc2(x))随后我们定义PolicyGradient类由于Policy Gradient中梯度计算公式为▽θlogπθ(a∣s)R(a)\bigtriangledown{_\theta}log\pi_\theta(a|s)R(a)▽θlogπθ(a∣s)R(a)πθ(a∣s)\pi_\theta(a|s)πθ(a∣s)是在网络参数为θ\thetaθ的情况下行为a被选择的概率R(a)R(a)R(a)是行为a选择后能得到的总回报值这个回报值通常使用R(a)∑t0TγtRnR(a) \sum_{t0}^{T}\gamma^tR_nR(a)∑t0TγtRn来计算因此我们必须完整的收集了一个Epoch的数据信息后才能进行一次梯度更新不然无法计算累计回报。这一点也和DQN不一样DQN可以进行单步更新而PG不可以。
class PolicyGradient(object):def __init__(self, observation_dim, action_dim, learning_rate0.01, gamma0.95):self.observation_dim observation_dimself.action_dim action_dimself.gamma gammaself.ep_obs, self.ep_r, self.ep_a [], [], []self.net Net(observation_dim, action_dim)self.optimizer torch.optim.Adam(self.net.parameters(), lrlearning_rate)def choose_action(self, observation):prob_list self.net(observation)action np.random.choice(range(prob_list.size(0)), pprob_list.data.numpy())return actiondef store_transition(self, obs, r, a):self.ep_obs.append(obs)self.ep_r.append(r)self.ep_a.append(a)def learn(self):cumulative_reward_list self.get_cumulative_reward()batch_obs torch.FloatTensor(np.vstack(self.ep_obs))batch_a torch.LongTensor(np.array(self.ep_a).reshape(-1, 1))batch_r torch.FloatTensor(cumulative_reward_list.reshape(-1, 1))action_prob self.net(batch_obs)action_prob.gather(1, batch_a)gradient torch.log(action_prob) * batch_rloss -torch.mean(gradient) # 网络会对loss进行minimize但我们是想做梯度上升所以加一个负号self.optimizer.zero_grad()loss.backward()self.optimizer.step()self.reset_epoch_memory()return loss.data.numpy()def get_cumulative_reward(self):running_r 0cumulative_reward np.zeros_like(self.ep_r)for i in reversed(range(len(self.ep_r))):running_r running_r * self.gamma self.ep_r[i]cumulative_reward[i] running_r# normalize cumulative rewardcumulative_reward - np.mean(cumulative_reward)cumulative_reward / np.std(cumulative_reward)return cumulative_rewarddef reset_epoch_memory(self):self.ep_a.clear()self.ep_obs.clear()self.ep_r.clear()值得注意的是在策略梯度中其实是没有loss这个概念的因为根本就没有标签这个loss的梯度是我们通过logπθ(a∣s)R(a)log\pi_\theta(a|s)R(a)logπθ(a∣s)R(a)算出来的由于在神经网络中我们做的是梯度下降θ−gradient∗LearningRate\theta - gradient*LearningRateθ−gradient∗LearningRate但在策略梯度算法中我们是希望做梯度上升的θgradient∗LearningRate\theta gradient*LearningRateθgradient∗LearningRate因此我们在求得了梯度之后要添加一个负号再将其作为loss。最后,我们建立 CartPole 场景并将该算法用在训练场景上
def main():DISPLAY_REWARD_THRESHOLD 200RENDER Falseenv gym.make(CartPole-v0)env env.unwrappedPG PolicyGradient(env.observation_space.shape[0], env.action_space.n)if WRITE_TENSORBOARD_FLAG:writer SummaryWriter(run/simple_model_experiment)writer.add_graph(PG.net, torch.rand(env.observation_space.shape))for i_eposide in range(120):obs env.reset()obs torch.FloatTensor(obs)while True:if RENDER: env.render()action PG.choose_action(obs)obs_, reward, done, _ env.step(action)PG.store_transition(obs, reward, action)if done:ep_r sum(PG.ep_r)if ep_r DISPLAY_REWARD_THRESHOLD: RENDER Trueprint(episode: %d | reward: %d % (i_eposide, ep_r))loss PG.learn()if WRITE_TENSORBOARD_FLAG:writer.add_scalar(training/loss, loss)writer.add_scalar(training/reward, ep_r)breakobs torch.FloatTensor(obs_)if __name__ __main__:main()
Policy Gradient 训练结果如下 loss对应的就是每一次更新的梯度reward对应的是模型的得分。可以看到使用策略梯度的效果还是蛮不错的在100个Epoch之后杆子基本就可以被很好的稳住了。