PyTorch60分钟教程学习笔记

基本概念

Tensor

tensor是的含义是张量,简单的理解可以将其当成三维矩阵,pytorch中的张量是对数据的一种封装,也是数据结构中最核心的部分之一。对于pytorch中的张量,数组可能是更好的理解方法。

Tensor的定义

  • 直接定义矩阵,使用torch.Tensor(shape)方法定义未初始化的张量,使用torch.rand(shape)torch.randn(shape)定义随机张量
import torch as pt
x = pt.Tensor(2,4)
print(x)
# 1.00000e-23 *
#  0.0000  0.0000  1.2028  0.0000
#  0.0000  0.0000  1.1517  0.0000
# [torch.FloatTensor of size 2x4]
x = pt.rand(5,3)
# 0.7609  0.5925  0.5840
# 0.1949  0.6366  0.3763
# 0.1802  0.8529  0.9373
# 0.6013  0.9685  0.9945
# 0.6555  0.1740  0.9884
# [torch.FloatTensor of size 5x3]
print(x,x.size()[0])
# 5
y = pt.rand(5,3)
  • 从numpy 中定义tensor,使用torch.from_numpy(ndarray)的方法,需要注意的是,这种情况下numpy矩阵和tensor会“绑定”,即修改任何一个的值,另一个的值也会发生变化
a = np.ones(5)
b = pt.from_numpy(a)
print(a,b)
#[ 1.  1.  1.  1.  1.] 
 #1
 #1
 #1
 #1
 #1
#[torch.DoubleTensor of size 5]

Tensor的基本操作

Tensor和numpy中的ndarray相似,可以完成加减乘除等运算,常见的操作方法通常为Tensor.操作(参数)torch.操作(参数,out=输出tensor),以加法为例

result = pt.Tensor(5,3)
test = pt.add(x,y,out=result)
# print(result,test)

y.add_(x)

两种方法test = pt.add(x,y,out=result)y.add_(x)都是相加,前者是相加后将结果交给一个新的Tensorresult,而后者可以理解为y自加x
Tensor还可以转换为numpy的对象ndarray,可以使用Tensor.numpy()获得与Tensor绑定的ndarray对象,修改Tensor时,ndarray对象也发生变化

a = pt.ones(5)
b = a.numpy()
# print(a,b)
a.add_(1)
# print(a,b)

使用GPU加速

使用Tensor = Tensor.cuda()的方法可以讲Tensor放到GPU上,通常的运算不支持从CPU到GPU的变换,因此若要在GPU上进行网络运算,网络声明完成后也要调用网络和输入的.cuda()方法将网络和输入放在GPU上

a,b = pt.Tensor(2,2),pt.Tensor(2,2)
a = a.cuda()
b = b.cuda()

Variable

Variable正向传播

Variable与TensorFlow中的Variable一样,是构建神经网络和训练的核心类,使用Variable可以构建计算图,并在图中计算结果(正向传播)和微分(反向传播),Variable的一些运算符重载过,因此可以直接使用+-*/运算符

x = Variable(pt.ones(2,2),requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
# Variable containing:
#27
#[torch.FloatTensor of size 1]

Variable反向传播

以上构建了一个计算图并计算了out的值,完成前向传播,使用out.backward()可以执行反向传播,就是计算微分。

out.backward()
print(x.grad)
#Variable containing:
# 4.5000  4.5000
# 4.5000  4.5000
#[torch.FloatTensor of size 2x2]

网络构建

网络结构构建

class Net(nn.Module):
    """docstring for Net"""
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1,6,5)# input channel,6 output channel,5x5
        self.conv2 = nn.Conv2d(6,16,5)
        self.fc1 = nn.Linear(16*5*5,120)#input 16*5*5,output120
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self,x):
        x = F.max_pool2d(F.relu(self.conv1(x)),(2,2)) #input,poolcore shape
        x = F.max_pool2d(F.relu(self.conv2(x)), 2) #(2,2) => 2 because 2=2
        x = x.view(-1, self.num_flat_features(x)) #reshape
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self,x):
        size = x.size()[1:] #remove batch size
        num_f = 1
        for s in size:
            num_f *= s
        return num_f

以上为构建一个简单的CNN的例子,其中

  • nn来自import torch.nn as nn这其中封装各种各样的网络层
  • F来自import torch.nn.functional as F,这其中封装了各种各样的神经网络需要使用的函数

在网络结构中

  • nn.Linear(input_size,output_size)为线性连接层,为MLP的线性部分
    -nn.Conv2d(input_channel,output_channel,shape)表示卷积核

函数中

  • F.max_pool2d(input,core_shape)为池化层
  • F.relu(input)为ReLu激活函数

另外,Variable.view()为变形函数,其中的-1表示不关心batch,而函数self.num_flat_features(x)是为了获得x的元素数量,这一步直接将x拍扁成向量

网络的前向传播

定义网络后(以上的类)后,声明后可以直接调用

net = Net().cuda()
print(net)
#Net (
# (conv1): Conv2d(1, 6, kernel_size=(5, 5), #stride=(1, 1))
# (conv2): Conv2d(6, 16, kernel_size=(5, 5), #stride=(1, 1))
# (fc1): Linear (400 -> 120)
# (fc2): Linear (120 -> 84)
# (fc3): Linear (84 -> 10)
#)

其中.cuda()是将整个网络放到GPU上,如果直接使用net = Net()网络位置在CPU上,将无法使用GPU加速。
定义网络后,直接传入输入即可完成前向传播

net.zero_grad() # Zero the gradient buffers of all parameters 

inputdata = Variable(pt.randn(1,1,32,32)) #?(1,1,32,32) nSamples x nChannels x Height x Width
inputdata = inputdata.cuda()
# 1-batch 1-input channel 32,32
# print(inputdata)
# print(inputdata.unsqueeze(0)) #[torch.FloatTensor of size 1x1x1x32x32]
out = net(inputdata)
print(out)

其中net.zero_grad()是为了清除梯度,起类似于初始化的作用。pytorch要求数据与网络的位置相同,因此若是网络声明在GPU上,数据也必须要GPU上加速。

网络的反向传播(权值更新)

网络的反向传播可以直接使用预先定义的代价函数的.backward()方法实现

net.zero_grad()
print(net.conv1.bias.grad)

loss.backward()
print(net.conv1.bias.grad)

在更新权值的时候,可以手动指定更新的方法

for f in net.parameters():
    f.data.sub_(f.grad.data * 0.01)

其中:

  • net.parameters()是个生成器,可以遍历net中的所有参数
  • f.grad.data为输出(代价函数)到这一参数的梯度

除了手动制定,也可以从import torch.optim as optim中调用优化器

optimizer = optim.SGD(net.parameters(),lr=0.01)
optimizer.zero_grad()
out = net(inputdata)
loss = criterion(out,target)
loss.backward()
optimizer.step()

其中criterion()为代价函数,loss为代价函数的输出值,optimizer.step()为调用一次优化

代价函数

代价函数表示当前结果距离期望输出的“距离”,torch.nn封装了一些代价函数,可以在训练的时候直接调用

target = Variable(pt.arange(1,11)).cuda()
# print(target)
criterion = nn.MSELoss().cuda()
loss = criterion(out,target) #out shape = 1*10 target shape = 10

这里调用的代价函数是MSELoss()平方平均函数

分类网络搭建,训练与测试

分类网络数据准备

教程提供的范例的训练集是CIFAR10数据集,该数据集提供了10种不同类型的图片,引入代码如下图

import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim


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, transform=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')

这部分仅仅是下载并提供数据集,不必深究,需要注意的是从testloader中获得数据即可

分类网络搭建

分类网络搭建使用两层conv+pool后接3层mlp层的结构,是个基本的卷积神经网络,构建类如下

class Net(nn.Module):
    """docstring for Net"""
    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().cuda()

这里将组件的定义放在了构造函数中,而将网络前馈部分放在了单独的forward()函数中。另外,使用net = Net().cuda()将网络放在了GPU上

分类网络的训练

分类网络的训练需要定义优化器和代价函数,剩下的就是将数据丢进神经网络中了,代码如下

criterion = nn.CrossEntropyLoss()
#声明使用交叉熵函数作为代价函数
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
#声明使用学习率0.001的SGD优化器

for epoch in range(2):
    running_loss = 0
    for i,data in enumerate(trainloader,0):
        inputs,labels = data
        inputs,labels = Variable(inputs).cuda(),Variable(labels).cuda()
        #获得数据并将其放在GPU上
        optimizer.zero_grad()
        #初始化梯度

        outputs = net(inputs)
        #前馈
        loss = criterion(outputs,labels)
        loss.backward()
        optimizer.step()
        #反馈计算梯度并更新权值

        running_loss += loss.data[0]
        if i % 200 == 0:
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0
            #打印平均代价函数值
print('Finished Training')

总结一下,该部分代码总共做了以下几件事

  • 定义优化器与代价函数
  • 执行网络训练

执行网络训练部分,每次迭代包括以下操作

  1. 获取batch数据并将其放在GPU上
    2.初始化梯度
    3.执行前馈计算代价函数
    4.执行反馈计算梯度并更新权值

分类网络的测试

网络测试部分就是将所有的训练数据再投入网络中训练一次,看真实结果与预测结果是否相同,代码如下

corret,total = 0,0
for images,labels in testloader:
    images = images.cuda()
    labels = labels.cuda()
    outputs = net(Variable(images))
    _,predicted = torch.max(outputs.data,1)
    total += labels.size(0)
    corret += (predicted == labels).sum()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * corret / total))

前馈得到预测结果后,使用_,predicted = torch.max(outputs.data,1)在第一维看取出最大的数(丢弃)和最大数的位置(保留)后再与label相比即可进行测试

推荐阅读更多精彩内容