1.Facebook深度学习框架PyTorch,变量、求导、损失函数

 

 

PyTorch 是 Facebook 发布的一款非常具有个性的深度学习框架,它和 Tensorflow,Keras,Theano 等其他深度学习框架都不同,它是动态计算图模式,其应用模型支持在运行过程中根据运行参数动态改变,而其他几种框架都是静态计算图模式,其模型在运行之前就已经确定。

在 PyTorch 中 Tensor 代表多维数组,类似 TensorFlow 中的 matrix 或 ndarrays。

例如,生成一个 (5,3) 的 tensor:

x = torch.rand(5, 3)
print(x)

输出:

0.1290  0.8748  0.1096
0.7204  0.9472  0.2891
0.3106  0.1791  0.0739
0.2701  0.5434  0.9968
0.2098  0.9916  0.1561

获取它的大小:

print(x.size())

输出:

torch.Size([5, 3])

 

使用GPU加速

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)

a = np.ones(5)
b = pt.from_numpy(a) #从numpy中定义tensor
print(a,b)
#[ 1.  1.  1.  1.  1.] 
 #1
 #1
 #1
 #1
 #1
#[torch.DoubleTensor of size 5]


result = pt.Tensor(5,3) #Tensor和numpy中的ndarray相似,可以完成加减乘除等运算,相加后将结果交给一个新的Tensorresult
test = pt.add(x,y,out=result) #y自加x
# print(result,test)

y.add_(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)


#使用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与TensorFlow中的Variable一样,是构建神经网络和训练的核心类,
#使用Variable可以构建计算图,并在图中计算结果(正向传播)和微分(反向传播),
#Variable的一些运算符重载过,因此可以直接使用+-*/运算符
x = Variable(pt.ones(2,2),requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean() #构建一个计算图并计算了out的值,完成前向传播
# Variable containing:
#27
#[torch.FloatTensor of size 1]


# 使用out.backward()执行反向传播,就是计算微分。
out.backward()
print(x.grad)
#Variable containing:
# 4.5000  4.5000
# 4.5000  4.5000
#[torch.FloatTensor of size 2x2]

 

网络结构构建

import torch
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F


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) #线性连接层,为MLP的线性部分
        self.fc3 = nn.Linear(84, 10)

    def forward(self,x):
        #池化层max_pool2d
        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
        
        #view变形函数,其中的-1表示不关心batch,而函数
        x = x.view(-1, self.num_flat_features(x)) #reshape
        x = F.relu(self.fc1(x)) #ReLu激活函数
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self,x): #为了获得x的元素数量,这一步直接将x拍扁成向量
        size = x.size()[1:] #remove batch size
        num_f = 1
        for s in size:
            num_f *= s
        return num_f

预期输出:
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)
)

网络的前向传播

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

net = Net().cuda() #将整个网络放到GPU上,而net=Net()网络位置在CPU上,无法使用GPU加速。
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)
#)

net.zero_grad() #清除梯度,否则梯度将会积累到现有的梯度上。

inputdata = Variable(pt.randn(1,1,32,32)) #把图像大小转变为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()
print(net.conv1.bias.grad)

loss.backward() #直接使用预先定义的代价函数的.backward()方法实现
print(net.conv1.bias.grad) #看一看conv1的bias梯度在backward之前和之后的值。

for f in net.parameters(): #遍历net中的所有参数
    #f.grad.data为输出(代价函数)到这一参数的梯度
    f.data.sub_(f.grad.data * 0.01) #更新权值的时候,可以手动指定更新的方法


#除了手动制定,也可以从import torch.optim as optim中调用优化器
import torch.optim as optim 
#神经网络有多种不同的更新法则,如SGD,Nesterov-SGD, Adam, RMSProp等。
#为了实现此功能,有一个package叫做torch.optim已经实现了这些。
optimizer = optim.SGD(net.parameters(),lr=0.01)
optimizer.zero_grad()
out = net(inputdata)
loss = criterion(out,target) #criterion()为代价函数
loss.backward() #loss为代价函数的输出值
optimizer.step() #调用一次优化

 

 

 

 

 

 

 

代价函数

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

target = Variable(pt.arange(1,11)).cuda()
# print(target)
criterion = nn.MSELoss().cuda() #代价函数是MSELoss()平方平均函数
loss = criterion(out,target) #out shape = 1*10 target shape = 10

 

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

分类网络数据准备

教程提供的范例的训练集是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))])

#训练集是CIFAR10数据集,该数据集提供了10种不同类型的图片
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')


#分类网络搭建使用两层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)

    #将网络前馈部分放在了单独的forward()函数中
    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()#将网络放在了GPU上


#分类网络的训练
#定义优化器和代价函数,剩下的就是将数据丢进神经网络中了
#声明使用交叉熵函数作为代价函数
criterion = nn.CrossEntropyLoss()
#声明使用学习率0.001的SGD优化器
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

for epoch in range(2):
    running_loss = 0
    for i,data in enumerate(trainloader,0):
        inputs,labels = data
        #获得数据并将其放在GPU上
        inputs,labels = Variable(inputs).cuda(),Variable(labels).cuda()
        #初始化梯度
        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')


#分类网络的测试
#网络测试部分就是将所有的训练数据再投入网络中训练一次,看真实结果与预测结果是否相同,
corret,total = 0,0
for images,labels in testloader:
    images = images.cuda()
    labels = labels.cuda()
    #前馈得到预测结果
    outputs = net(Variable(images))
    #在第一维看取出最大的数(丢弃)和最大数的位置(保留)后再与label相比即可进行测试
    _,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))

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

概述:

(1)  torch.Tensor——多维数组

(2)  autograd.Variable——包装了一个Tensor,并且记录了应用于其上的运算。与Tensor具有相同的API,同时增加了一些新东西例如backward()。并且有相对于该tensor的梯度值。

(3)  nn.Module——神经网络模块。封装参数的简便方式,对于参数向GPU移动,以及导出、加载等有帮助。

(4)  nn.Parameter——这是一种变量(Variable),当作为一个属性(attribute)分配到一个模块(Module)时,可以自动注册为一个参数(parameter)。

(5)  autograd.Function——执行自动求导运算的前向和反向定义。每一个Variable运算,创建至少一个单独的Function节点,该节点连接到创建了Variable并且编码了它的历史的函数身上。

 

 

 

 

 

 

1 pytorch安装

  • 用pip换成清华的源之后,下载73%之后显示读取失败所以我又换成coand安装了。
  • 添加Anaconda的TUNA镜像
 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
  • 设置搜索时显示通道地址
 conda config --set show_channel_urls yes
  • conda GPU的命令如图所示:
conda install pytorch torchvision -c pytorch
  • conda CPU的命令如图所示:
conda install pytorch-cpu -c pytorch 
pip3 install torchvision


 

2 变量

先看看 Tensor,pytorch 中的数据都是封装成 Tensor 来引用的,Tensor 实际上就类似于 numpy 中的数组,两者可以自由转换。

生成一个 (3,4) 维度的数组

import torch
x = torch.Tensor(3,4)
print("x Tensor: ",x)

{%}

可以看到 torch.Tensor() 方法生成制定维度的随机数。

下面看看 Variable 的基本操作,引用 Variable:

import torch
from torch.autograd import Variable
x=Variable(torch.Tensor(2,2))
print("x variable: ",x)

{%}

我们看到 Variable 比 Tensor 多出来第一行提示符”Variable containing:”,说明 Variable 不光包含了数据,还包含了其他东西,那么还包含什么东西呢?

默认 Variable 是有导数 grad 的,x.data 是数据,这里 x.data 就是 Tensor。x.grad 是计算过程中动态变化的导数。

print ("x.data: ",x.data, ",x.grad: ",x.grad)

此时 Variable 还未进行计算,因此 x.grad 为 None。

示例代码 chart1.py 中的 example1 方法。完整代码的执行结果截图如下:

{%}

 

3 求导

理解 pytorch,首先要理解求导的概念。

数学上求导简单来说就是求取方程式相对于输入参数的变化率,也就是加速度。这部分理论基础参考高等数学的内容,上过大学的都学过,可能现在都忘了吧:)

求导的作用是用导数对神经网络的权重参数进行调整,注意这里提到了权重参数的概念,这是神经网络的范畴定义了,关于神经网络的基础知识本书不做介绍,读者最好先了解有关神经网络的基本概念再读此书。

Pytorch 中为求导提供了专门的包,包名叫 autograd。如果用 autograd.Variable 来定义参数,则 Variable 自动定义了两个变量,data 代表原始权重数据;而 grad 代表求导后的数据,也就是梯度。每次迭代过程就用这个 grad 对权重数据进行修正。

{%}

实践:

import torch
from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad=True)
print(x)

输出:

1  1
 1  1
[torch.FloatTensor of size 2x2]

 

y=x+2
print(y)

输出:

3  3
 3  3
[torch.FloatTensor of size 2x2]

 

z = y * y * 3
out = z.mean()
print(z, out)

输出:

(Variable containing:
 27  27
 27  27
[torch.FloatTensor of size 2x2]
, Variable containing:
 27
[torch.FloatTensor of size 1]
) [torch.FloatTensor of size 1]

 

out.backward()

反向传播,也就是求导数的意思。输出 out 对 x 求导:

print(x.grad)

输出结果:

Variable containing:
 4.5000  4.5000
 4.5000  4.5000
[torch.FloatTensor of size 2x2]

4.5 是怎么算出来的呢,从前面的公式可以看出 z=(x+2)*(x+2)*3,它的导数是 3*(x+2)/2,当 x=1 时导数的值就是 3*(1+2)/2=4.5,和 pytorch 计算得出的结果是一致的。

权值更新方法

weight = weight + learning_rate * gradient

 

learning_rate = 0.01
for f in model.parameters():
    f.data.sub_(f.grad.data * learning_rate)

learning_rate 是学习速率,多数时候就叫做 lr,是学习步长,用步长 * 导数就是每次权重修正的 delta 值,lr 越大表示学习的速度越快,相应的精度就会降低。

 

 

 

 

4 损失函数

损失函数,又叫目标函数,是编译一个神经网络模型必须的两个参数之一。另一个必不可少的参数是优化器。

损失函数是指用于计算标签值和预测值之间差异的函数,在机器学习过程中,有多种损失函数可供选择,典型的有距离向量,绝对值向量等。

{%}

上图是一个用来模拟线性方程自动学习的示意图。粗线是真实的线性方程,虚线是迭代过程的示意,w1 是第一次迭代的权重,w2 是第二次迭代的权重,w3 是第三次迭代的权重。随着迭代次数的增加,我们的目标是使得 wn 无限接近真实值。

那么怎么让 w 无限接近真实值呢?其实这就是损失函数和优化器的作用了。图中 1/2/3 这三个标签分别是 3 次迭代过程中预测 Y 值和真实 Y 值之间的差值(这里差值就是损失函数的意思了,当然了,实际应用中存在多种差值计算的公式),这里的差值示意图上是用绝对差来表示的,那么在多维空间时还有平方差,均方差等多种不同的距离计算公式,也就是损失函数了,这么一说是不是容易理解了呢?

这里示意的是一维度方程的情况,那么发挥一下想象力,扩展到多维度,是不是就是深度学习的本质了?

下面介绍几种常见的损失函数的计算方法,pytorch 中定义了很多类型的预定义损失函数,需要用到的时候再学习其公式也不迟。

我们先定义两个二维数组,然后用不同的损失函数计算其损失值。

import torch
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
sample = Variable(torch.ones(2,2))
a=torch.Tensor(2,2)
a[0,0]=0
a[0,1]=1
a[1,0]=2
a[1,1]=3
target = Variable (a)

sample 的值为:[[1,1],[1,1]]。

target 的值为:[[0,1],[2,3]]。

4.1 nn.L1Loss

{%}

L1Loss 计算方法很简单,取预测值和真实值的绝对误差的平均数即可。

criterion = nn.L1Loss()
loss = criterion(sample, target)
print(loss)

最后结果是:1。

它的计算逻辑是这样的:

先计算绝对差总和:|0-1|+|1-1|+|2-1|+|3-1|=4;

然后再平均:4/4=1。

4.2 nn.SmoothL1Loss

SmoothL1Loss 也叫作 Huber Loss,误差在 (-1,1) 上是平方损失,其他情况是 L1 损失。

{%}

criterion = nn.SmoothL1Loss()
loss = criterion(sample, target)
print(loss)

最后结果是:0.625。

4.3 nn.MSELoss

平方损失函数。在nn package中有很多种不同的损失函数,最简单的一个loss就是nn.MSELoss,它计算输出值和目标值之间的均方差。计算公式是预测值和真实值之间有多大差距,平方和的平均数。

{%}

sample = net(input)
target = Variable(torch.arange(1, 11))  # a dummy target, for example
criterion = nn.MSELoss()
loss = criterion(sample, target)
print(loss)

最后结果是:1.5。

4.4 nn.BCELoss

二分类用的交叉熵,其计算公式较复杂,这里主要是有个概念即可,一般情况下不会用到。

{%}

criterion = nn.BCELoss()
loss = criterion(sample, target)
print(loss)

最后结果是:-13.8155。

4.5 nn.CrossEntropyLoss

交叉熵损失函数

{%}

该公式用的也较多,比如在图像分类神经网络模型中就常常用到该公式。

criterion = nn.CrossEntropyLoss()
loss = criterion(sample, target)
print(loss)

最后结果是:报错,看来不能直接这么用!

看文档我们知道 nn.CrossEntropyLoss 损失函数是用于图像识别验证的,对输入参数有各式要求,这里有这个概念就可以了,在图像识别一文中会有正确的使用方法。

4.6 nn.NLLLoss

负对数似然损失函数(Negative Log Likelihood)

{%}

在前面接上一个 LogSoftMax 层就等价于交叉熵损失了。注意这里的 xlabel 和上个交叉熵损失里的不一样,这里是经过 log 运算后的数值。这个损失函数一般也是用在图像识别模型上。

criterion = F.nll_loss()
loss = criterion(sample, target)
print(loss)
loss=F.nll_loss(sample,target)

最后结果是:报错,看来不能直接这么用!

Nn.NLLLoss 和 nn.CrossEntropyLoss 的功能是非常相似的!通常都是用在多分类模型中,实际应用中我们一般用 NLLLoss 比较多。

4.7 nn.NLLLoss2d

和上面类似,但是多了几个维度,一般用在图片上。

  • input, (N, C, H, W)

  • target, (N, H, W)

比如用全卷积网络做分类时,最后图片的每个点都会预测一个类别标签。

criterion = nn.NLLLoss2d()
loss = criterion(sample, target)
print(loss)

最后结果是:报错,看来不能直接这么用!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI研究院

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值