基本概念

Tensor

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

Tensor的定义

  • 直接定义矩阵,使用torch.Tensor(shape)方法定义未初始化的张量,使用torch.rand(shape)torch.randn(shape)定义随机张量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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会“绑定”,即修改任何一个的值,另一个的值也会发生变化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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),以加法为例
    1
    2
    3
    4
    5
    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对象也发生变化
    1
    2
    3
    4
    5
    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上
    1
    2
    3
    a,b = pt.Tensor(2,2),pt.Tensor(2,2)
    a = a.cuda()
    b = b.cuda()

    Variable

    Variable正向传播

    Variable与TensorFlow中的Variable一样,是构建神经网络和训练的核心类,使用Variable可以构建计算图,并在图中计算结果(正向传播)和微分(反向传播),Variable的一些运算符重载过,因此可以直接使用+-*/运算符
    1
    2
    3
    4
    5
    6
    7
    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()可以执行反向传播,就是计算微分。
    1
    2
    3
    4
    5
    6
    out.backward()
    print(x.grad)
    #Variable containing:
    # 4.5000 4.5000
    # 4.5000 4.5000
    #[torch.FloatTensor of size 2x2]

    网络构建

    网络结构构建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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拍扁成向量

网络的前向传播

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

1
2
3
4
5
6
7
8
9
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加速。
定义网络后,直接传入输入即可完成前向传播
1
2
3
4
5
6
7
8
9
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()方法实现

1
2
3
4
5
net.zero_grad()
print(net.conv1.bias.grad)

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

在更新权值的时候,可以手动指定更新的方法
1
2
for f in net.parameters():
f.data.sub_(f.grad.data * 0.01)

其中:

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

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

1
2
3
4
5
6
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封装了一些代价函数,可以在训练的时候直接调用

1
2
3
4
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种不同类型的图片,引入代码如下图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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层的结构,是个基本的卷积神经网络,构建类如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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上

分类网络的训练

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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.执行反馈计算梯度并更新权值

分类网络的测试

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

1
2
3
4
5
6
7
8
9
10
11
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相比即可进行测试