PyTorch | 学习笔记4
一.认识Pytorch
1.什么是Pytorch
Pytorch是一个基于Numpy的科学计算包,向它的使用者提供了两大功能
①作为Numpy的替代者,向用户提供使用GPU强大功能的能力
②作为一款深度学习的平台,向用户提供最大的灵活性和速度
2.基本元素操作
Tensors张量:类似Numpy中的ndarray数据结构,最大的区别在于Tensor可以利用GPU的加速功能
import torch
#创建矩阵的操作
#创建一个没有初始化的矩阵
x = torch.empty(5,3)
#创建一个有初始化的矩阵
x = torch.rand(5,3)
#当声明一个未初始化的矩阵时,它本身不包含任何确切的值
#当创建一个未初始化的矩阵时,分配给矩阵的内存中有什么数值就赋值给了这个矩阵,本质是毫无意义的数据
#创建一个全零矩阵并可指定数据元素的类型为long
x = torch.zeros(5,3,dtype=torch.long)
#直接通过数据创建张量
x = torch.tensor([2.5,3.5])
#通过已有的一个张量创建相同尺寸的新张量
#利用news_methods方法得到一个张量
x = x.new_ones(5,3,dtype=torch.double)
#利用randn_like方法得到相同张量尺寸的一个新张量,并且采用随机初始化来对其赋值
y = torch.randn_like(x,dtype=torch.float)
#得到张量的尺寸
print(x.size())
#torch.size函数本质上返回的是一个tuple,因此它支持一切元组的操作
3.基本运算操作
#加法操作
x = torch.rand(5,3)
y = torch.rand(5,3)
print(x+y)
print(torch.add(x,y))
#第三种加法方式:提前设定一个空的张量,然后将空的张量作为加法的结果存储张量
result = torch.empty(5,3)
torch.add(x,y,out=result)
print(result)
#第四种加法方式:in-place(原地置换)
#所有in-place的操作函数都有一个下划线的后缀
y.add_(x)
#用类似Numpy的方式对张量进行操作
print(x[:,1])
#改变张量的形状:torch.view(),需要保证数据元素的总数量不变
x = torch.randn(4,4)
y = x.view(16)
#-1代表自动匹配个数
z = x.view(-1,8)
print(x.size(),y.size(),z.size())
#如果张量中只有一个元素,可以用.item()将值取出,作为一个python number
x = torch.randn(1)
print(x.item())
4.Torch Tensor和Numpy array之间的相互转换
#torch tensor和numpy array共享底层的内存空间,因此改变其中一个值,另一个值也会随之改变
a = torch.ones(5)
#将torch tensor转换为numpy array
b = a.numpy()
#对其中一个进行加法操作,另一个也随之被改变
a.add_(1)
#将numpy array转换为torch tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a,1,out=a)
print(a)
print(b)
#注意:所有在CPU上的Tensors,除了CharTensor,都可以转换为Numpy array并可以反向转换
#关于cuda tensor:tensors可以用.to()方法来将其移动到任意设备上
#如果服务器上已经安装了GPU和CUDA
if torch.cuda.is_available():
#定义一个设备对象,这里指定成cuda,即使用GPU
device = torch.device("cuda")
#直接在GPU上创建一个Tensor
y = torch.ones_like(x,device=device)
#将在CPU上面的x张量移动到GPU上面
x = x.to(device)
#x和y都在GPU上面,才能支持加法运算
z = x + y
#此处的张量z在GPU上面
print(z)
#也可以将z转移到CPU上面,并同时指定张量元素的数据类型
print(z.to("cpu",torch.double))
二.Pytorch中的autograd
在整个Pytorch框架中,所有的神经网络本质上都是一个autograd package(自动求导工具包)
autograd package提供了一个对tensors上所有的操作进行自动微分的功能
1.torch.Tensor
torch.Tensor是整个package中的核心类,如果将属性requires_grad设置为true,它将追踪在这个类上定义的所有操作。当代码要进行反向传播的时候,直接调用.backward()就可以自动计算所有的梯度。在这个Tensor上所有梯度将被累加进属性.grad中
如果想终止一个Tensor在计算图中的追踪回溯,只需要执行.detach()就可以将该Tensor从计算图中撤下,在未来的回溯计算中也不会再计算该Tensor
除了.detach(),如果想终止对计算图的回溯,也就是不再进行方向传播求导数的过程,也可以采用代码块的方式with torch.no_grad(),这种方式非常适用于对模型进行预测的时候,因为预测阶段不再需要对梯度进行计算
关于torch.Function:Function类和Tensor类同等重要的一个核心类,它和Tensor共同构建了一个完整的类,每一个Tensor拥有一个.grad_fn属性,代表引用了哪个具体的Function创建了该Tensor;如果某个张量Tensor是用户自定义的,则其对应的grad_fn is None
2.Tensor的操作
x1 = torch.ones(3,3)
x = torch.ones(2,2,requires_grad=True)
#在具有requires_grad=True的Tensor上执行一个加法操作
y = x + 2
#打印Tensor的grad_fn属性
print(x.grad_fn)
print(y.grad_fn)
#在Tensor上执行更复杂的操作
z = y*y*3
out = z.mean()
print(z,out)
#关于方法requires_grad_():该方法可以原地改变Tensor的属性requires_grad的值,如果没有主动设定默认#为False
a = torch.randn(2,2)
a = ((a*3)/(a-1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a*a).sum()
print(b.grad_fn)
3.梯度Gradients
#在pytorch中,反向传播是依靠backward()实现的
out.backward()
print(x.grad)
#关于自动求导的属性设置:可以通过设置requires_grad=True来执行自动求导
#也可以通过代码块的限制来停止自动求导
print(x.requires_grad)
print((x**2).requires_grad)
with torch.no_grad():
print((x**2).requires_grad)
#可以通过detach()获得一个新的Tensor,拥有相同的内容但不需要自动求导
print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
三.使用Pytorch构建一个神经网络
torch.nn:使用Pytorch来构建神经网络,主要的工具都在torch.nn包中;nn依赖于autograd来定义模型,并对其自动求导
构建神经网络的典型流程:
①定义一个拥有可学习参数的神经网络
②遍历训练数据集
③处理输入数据使其流经神经网络
④计算损失值
⑤将网络参数的梯度进行反向传播
⑥以一定的规则更新网络的权重
首先定义一个Pytorch实现的神经网络
#导入若干工具包
import torch
import torch.nn as nn
import torch.nn.functional as F
#定义一个简单的网络类
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
#定义第一层卷积神经网络,输入通道维度=1,输出通道维度=6,卷积核大小3*3
self.conv1 = nn.Conv2d(1,6,3)
#定义第二层卷积神经网络,输入通道维度=6,输出通道维度=16,卷积核大小3*3
self.conv2 = nn.Conv2d(6,16,3)
#定义三层全连接网络
self.fc1 = nn.Linear(16*6*6,120)
self.fc2 = nn.Linear(120,84)
self.fc3 = nn.Linear(84,10)
def forward(self,x):
#在(2,2)的池化窗口下执行最大池化操作
x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
x = F.max_pool2d(F.relu(self.conv2(x)),2)
x = x.view(-1,self.num_flat_feature(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_feature(self,x):
#计算size,除了第0个维度上的batch_size
size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
注意:模型中所有的可训练参数,可以通过net.parameters()来获得
params = list(net.parameters())
print(len(params))
print(params[0].size())
假设图像的输入尺寸为32*32
input = torch.randn(1,1,32,32)
out = net(input)
print(out)
有了输出张量后,就可以执行梯度归零和反向传播的操作了
net.zero_grad()
out.backward(torch.randn(1,10))
注意: torch.nn构建的神经网络只支持mini-batchs的输入,不支持单一样本的输入
1.损失函数
损失函数的输入是一个输入的pair:(output,target),然后计算出一个数值来评估output和target之间的差距大小
在torch.nn中有若干不同的损失函数可供使用,比如nn.MSELoss就是通过计算均方差损失来评估输入和目标之间的差距
应用nn.MSELoss计算损失的例子
output = net(input)
target = torch.randn(10)
#改变target的形状为二维张量,为了和output匹配
target = target.view(1,-1)
criterion = nn.MSELoss()
loss = criterion(output,target)
print(loss)
关于方向传播的链条:如果我们跟踪loss反向传播的方向,使用grad_fn属性打印,将可以看到一张完整的计算图:input → conv2d → relu → maxpool2d → conv2d → relu → maxpool2d → view → linear → relu → linear → relu → linear → MSELoss → loss
当调用loss.backward()时,整张计算图将对loss进行自动求导,所有属性requires_grad=True的Tensors都将参与梯度求导的运算,并将梯度累加到Tensors中的.grad属性中
print(loss.grad_fn) #MSELoss
print(loss.grad_fn.next_function[0][0]) #Linear
print(loss.grad_fn.next_function[0][0].next_functions[0][0]) #ReLU
2.反向传播
在Pytorch中执行反向传播非常简便,全部操作就是loss.backward()
在执行反向传播之前,要先将梯度清零,否则梯度会在不同的批次数据之间被累加
#梯度清零
net.zero_grad()
print(net.conv1.bias.grad)
#反向传播
loss.backward()
print(net.conv1.bias.grad)
3.更新网络参数
更新参数最简单的算法就是SGD(随机梯度下降)
具体算法公式表达式:weight = weight - learning_rate * gradient
首先,用传统的Python代码实现SGD
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data*learning_rate)
然后使用Pytorch官方推荐的标准代码
#首先导入优化器的包,optim中包含若干常用的优化算法,比如SGD,Adam等
import torch.optim as optim
#通过optim创建优化器对象
optimizer = optim.SGD(net.parameters(),lr=0.01)
#将优化器执行梯度清零的操作
optimizer.zero_grad()
output = net(input)
loss = criterion(output,target)
#对损失值执行反向传播的操作
loss.backward()
#参数的更新通过一行标准代码来执行
optimizer.step()
四.使用Pytorch构建一个分类器
1.分类器任务和数据介绍
构造一个将不同图像进行分类的神经网络分类器,对输入的图片进行判别并完成分类
本案例采用CIFAR10数据集作为原始图片数据
2.训练分类器的步骤
①使用torchvision下载CIFAR10数据集
import torch
import torchvision
import torchvision.transforms as transforms
#下载数据集并对图片进行调整,因为torchvision数据集的输出是PILImage格式,数据域在[0,1]
#我们将其转换为标准数据域[-1,1]的张量格式
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data',train=True,
download=True,transform=transform)
trainloader = torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data',train=False,
download=True,transforms=transform)
testloader = torch.utils.data.DataLoader(testset,batch_size=4,shuffle=False,num_workers=2)
classes = ('plane','car','bird','cat','deer','dog','frog','horse','ship','truck')
#展示若干训练集的图片
import matplotlib.pyplot as plt
import numpy as np
#构建展示图片的函数
def imshow(img):
img = img/2 + 0.5
mpimg = img.numpy()
plt.imshow(np.transpose(i=npimg,(1,2,0)))
plt.show()
#从数据迭代器中读取一张图片
dataiter = iter(trainloader)
images,labels = dataiter.next()
#展示图片
imshow(torchvision.utils.make_grid(images))
#打印标签label
print(''.join('%5s' % classes[label[j]] for j in range(4)))
②定义卷积神经网络
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(3,6,5)
self.pool = nn.MaxPool2d(2,2)
self.conv2 = nn.Conv2d(6,16,5)
self.fc1 = nn.Linear(16*5*5,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*5*5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
③定义损失函数
采用交叉熵损失函数和随机梯度下降优化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
④在训练集上训练模型
采用基于梯度下降的优化算法,都需要很多个轮次的迭代训练
for epoch in range(2):
running_loss = 0.0
for i,data in enumerate(trainloader,0):
#data中包含输入图像张量inputs,标签张量labels
inputs,labels = data
#首先将优化器梯度归零
optimizer.zero_grad()
#输入图像张量进网络,得到输出张量outputs
outputs = net(inputs)
#利用网络的输出outputs和标签labels计算损失值
loss = criterion(outputs,labels)
#反向传播+参数更新,是标准代码的标准流程
loss.backward()
optimizer.step()
#打印轮次和损失值
running_loss += loss.item()
if (i+1) % 2000 == 0:
print('[%d,%5d] loss:%.3f' % (epoch + 1,i + 1,running_loss / 2000))
print('Finish Training')
⑤在测试集上测试模型
第一步:展示测试集中的若干图片
dataiter = iter(testloader)
images,labels = dataiter.next()
#打印原始图片
imshow(torchvision.utils.make_grid(images))
#打印真实的标签
print('GroundTruth:',''.join('%5s' % classes[labels[j]] for j in range(4)))
第二步:加载模型并对测试图片进行预测
#首先实例化模型的类对象
net = Net()
#加载训练阶段保存好的模型的状态字典
net.load_state_dict(torch.load(PATH))
#利用模型对图片进行预测
outputs = net(images)
#共有10个类别,采用模型计算出的概率最大的作为预测的类别
_,predicted = torch.max(outputs,1)
#打印预测标签的结果
print('predicted:',''.join('%5s' % classes[predicted[j]] for j in range(4)))
接下来看一下在全部测试集上的表现
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images,labels = data
outputs = net(images)
_,predicted = torch.max(outputs.data,1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('accuracy of the network on the 10000 test images:%d %%' % (100 * correct / total))
为了更加细致的看一下模型在哪些类别上表现更好,在哪些类别上表现更差,我们分类别地进行准确率计算
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
for data in testloader:
images,labels = data
outputs = net(images)
_,predicted = torch.max(outputs,1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1
for i in range(10):
print('accuracy of %5s:%2d %%' % (classes[i],100*class_correct[i]/class_total[i]))
3.在GPU上训练模型
为了真正利用Pytorch中Tensor的优秀属性加速模型的训练,我们可以将训练过程转移到GPU上进行
首先定义设备,如果CUDA是可用的则被定义成GPU,否则被定义为CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
当训练模型的时候,只需要将模型转移到GPU上,同时将输入的图片和标签页转移到GPU上即可
#将模型转移到GPU上
net.to(device)
#将输入的图片张量和标签张量转移到GPU上
inputs,labels = data[0].to(device),data[1].to(device)