Pytorch学习笔记(二) 自动微分&神经网络

在这里插入图片描述

一、自动微分

autograd包是PyTorch中所有神经网络的核心,为 Tensors 上的所有操作提供自动微分。

在这里插入图片描述
注:可以不用Variable的表示方法了?不需要以下写法?

from torch.autograd import Variable
x=Variable(torch.ones(1,3),requires_grad=True)
y=x+2
y.creator # 该属性名已改成grad_fn

autograd的内部机理

在这里插入图片描述

核心类: torch.Tensor


x.requires_grad=True:

  会开始跟踪针对 tensor x 的所有操作, 完成所有计算后( x->y ), 可以调用 y.backward() 来自动计算所有梯度, tensor x 的梯度将累积到 x.grad 属性中。

'''
.requires_grad_( ... ) 会改变张量的 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() # 注意 这里的张量乘法是哈达马积m*n和m*n(不是矩阵乘法m*p和p*n),是每个位置上对应数值的乘积。
print(b.grad_fn)
'''
False
True
<SumBackward0 object at 0x7fc6d00db4a8>
'''

关于张量乘积
高维矩阵(张量)的乘法规则

注意:y.backward() 时,如果y是标量,则不需要为backward()传入任何参数,否则,需要传入一个与y同形的 tensor [原因]

# 雅可比向量举例
x=torch.randn(3,requires_grad=True)
y=x*2
while y.data.norm()<1000:
    y=y*2
print(y)
'''
tensor([  87.0814, 1119.1162,   55.7230], grad_fn=<MulBackward0>)
'''
v=torch.tensor([0.1,1.0,0.0001],dtype=torch.float)
y.backward(v)
print(x.grad)
'''
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])
'''

关于y.data.norm()<1000: L2范数 (欧几里得范数)

在这里插入图片描述
等价于 torch.sqrt(torch.sum(torch.pow(y, 2)))

关于 [0.1,1.0,0.0001]


调用 .detach():

  取消对 tensor 的梯度追踪,将其与计算历史记录分离,并防止将来的计算被跟踪。

print(x.requires_grad)
# y和x数据相同,不需要求导
# y不是x的拷贝,对y的修改也会影响x,如果直接令y=x,那么是不会取消追踪的
y=x.detach() 
print(y.requires_grad)
print(x.eq(y).all())  # 对比全部数据
'''
True
False
tensor(True)
'''

  还可以用 withtorch.no_grad() 将不想被追踪的操作代码包裹起来,适用于模型评估。

x=torch.randn(3,requires_grad=True)
print(x.requires_grad)
print((x**2).requires_grad)
with torch.no_grad():
    print((x**2).requires_grad)
print((x**2).requires_grad)
'''
True
True
False
True
'''

Function类

  Tensor 和 Function 互相连接并构建一个非循环图,它保存整个完整的计算过程的历史信息
  除了用户基于数据直接创建的 tensor ( eg. a=torch.Tensor([1, 2, 3,]) ) ,其他的Tensor必然是根据某些Tensor通过运算得到的,Function类就记录了这一运算过程,并存储在 .grad_fn 属性中。


在这里插入图片描述

import torch
x=torch.ones(2,2,requires_grad=True)
print(x)
'''
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
'''
print(x.grad_fn)
'''
None
'''
y=x+2
print(y)
print(y.grad_fn)
'''
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x7fbed1b13f28>
'''
z=y*y*3
out=z.mean()
print(z,out)
'''
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) 
tensor(27., grad_fn=<MeanBackward0>)
'''
out.backward()
print(x.grad)
'''
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])
'''

More about autograd 和 Function

二、神经网络

注意:torch.nn 只接受小批量的数据
  整个torch.nn包只接受那种小批量样本的数据,而非单个样本。 例如,nn.Conv2d 能够结构一个四维的 TensornSamples x nChannels x Height x Width
  如果你拿的是单个样本,使用 input.unsqueeze(0) 来加一个假维度就可以了。
在这里插入图片描述

手写数字识别网络训练过程:

1.定义一个包含可训练参数的神经网络

torch.nn.Conv2d()函数详解
网络定义详解

import torch
import torch.nn as nn
import torch.nn.functional as tf
class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__() # 复制并使用Net的父类nn.Module的初始化方法

        self.conv1=nn.Conv2d(1,6,5) # 定义conv1函数的是图像(2d)卷积函数:输入为图像(1 channel,即灰度图),输出为6 channels, 卷积核为5x5的正方形
        self.conv2=nn.Conv2d(6,16,5)
        # an affine operation: y=Wx+b
        self.fc1=nn.Linear(16*5*5,120) # 定义fc1为线性函数:y=Wx+b,并将16*5*5个节点连接到120个节点上。
        self.fc2=nn.Linear(120,84)
        self.fc3=nn.Linear(84,10)
    
    # 定义该神经网络的向前传播函数,该函数必须定义,一旦定义成功,向后传播函数也会自动生成(autograd)
    def forward(self,x):
        x=tf.max_pool2d(tf.relu(self.conv1(x)),(2,2))
        x=tf.max_pool2d(tf.relu(self.conv2(x)),2)
        x=x.view(-1,self.num_flat_features(x)) # 将张量x变形成一维的向量形式,总特征数并不改变,为接下来的全连接作准备。
        x=tf.relu(self.fc1(x)) # 注意要把x传到fc1中
        x=tf.relu(self.fc2(x))
        x=self.fc3(x)
        return x
    # 计算张量x的总特征量(把每个数字都看出是一个特征,即特征总量)
    def num_flat_features(self,x):
        size=x.size()[1:] # 这里为什么要使用[1:],是因为pytorch只接受批输入,也就是说一次性输入好几张图片,那么输入数据张量的维度自然上升到了4维,[1:]让我们把注意力放在后3维(实际每张图像的三个维度)
        num_features=1
        for s in size:
            num_features*=s
        return num_features

net=Net()
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(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)
'''
模型可训练的参数可以通过调用 net.parameters() 返回:
params=list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight
'''
10
torch.Size([6, 1, 5, 5])
'''
# 输出每一层的参数数
sum=0
for i in params:
    num=1
    print("该层的结构:"+str(list(i.size())))
    for j in i.size():
        num*=j
    print("参数和:"+str(num))
    sum+=num

print("总参数和:"+str(sum))
'''
该层的结构:[6, 1, 5, 5]
参数和:150
该层的结构:[6]
参数和:6
该层的结构:[16, 6, 5, 5]
参数和:2400
该层的结构:[16]
参数和:16
该层的结构:[120, 400]
参数和:48000
该层的结构:[120]
参数和:120
该层的结构:[84, 120]
参数和:10080
该层的结构:[84]
参数和:84
该层的结构:[10, 84]
参数和:840
该层的结构:[10]
参数和:10
总参数和:61706
'''

2.处理输入以及调用反向传播

随机生成一个32x32的input进行网络的训练 (为了使该网络应用于手写数字识别,要把数据集中的图片维度修改为32x32)
net=Net()
#print(net)
input=torch.randn(1,1,32,32)
out=net(input)
print(out)
'''
十个数字的概率张量:
tensor([[ 0.0587, -0.0765,  0.0846,  0.1052, -0.0052,  0.0298,  0.0342,  0.0129,
         -0.0719,  0.0846]], grad_fn=<AddmmBackward>)
'''
把所有参数梯度缓存器置零,用随机的梯度来反向传播
net.zero_grad() # 对所有的参数的梯度缓冲区进行归零
out.backward(torch.randn(1,10)) # 使用随机的梯度进行反向传播

3.计算损失(loss)

torch.nn package中的不同代价函数

损失函数的输入:模型输出值和目标真实值,并评估两者之间的差值。

nn.MSELoss计算输入和目标之间的均方误差:
input = torch.randn(1,1,32,32)
output=net(input)
target=torch.randn(10) # 假设的目标真实值
target=target.view(1,-1) # 使其形状与输出相同 10x1
print(target)
criterion=nn.MSELoss()

loss=criterion(output,target)
print(loss)
'''
tensor(1.6512, grad_fn=<MseLossBackward>)
'''

跟随loss从后往前看,调用 .creator 属性你可以看到这样的一个计算流程图:

'''
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d  
      -> view -> linear -> relu -> linear -> relu -> linear 
      -> MSELoss
      -> loss
'''
print(loss.creator)
'''
报错:AttributeError: 'Tensor' object has no attribute 'creator'
原因:creator属性名称已经改为grad_fn
grad_fn的值可以得知该变量是否是一个计算结果,也就是说该变量是不是一个函数的输出值。若是,则grad_fn返回一个与该函数相关的对象,否则是None
'''

因此当我们调用 loss.backward() 时,整个图都会微分,且所有的在图中的requires_grad=True 的张量都会以 .grad 来累积梯度。

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU
'''
<MseLossBackward object at 0x7fb753c6d358>
<AddmmBackward object at 0x7fb7543f65c0>
<AccumulateGrad object at 0x7fb753c6d358>
'''
net.zero_grad()     # zeroes the gradient buffers of all parameters
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward() # 进行反向传播
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
'''
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0100, -0.0213,  0.0175,  0.0126,  0.0111, -0.0109])
'''

4.更新网络的参数

最简单的更新规则就是随机梯度下降(SGD):

# weight=weight-learning_rate*gradient
learning_rate=0.01
for f in net.parameters():
    f.data.sub_(f.grad.data*learning_rate)

还有其他的更新方式,例如 SGD, Nesterov-SGD, Adam, RMSProp等,可以通过 torch.optim 来直接调用相应方法。

import torch.optim as optim
# create your optimizer
optimizer=optim.SGD(net.parameters(),lr=0.01)
# in your training loop:
optimizer.zero_grad() # zero the gradient buffers
output=net(input)
loss=criterion(output,target)
loss.backward()
optimizer.step()    # Does the update

猜你喜欢

转载自blog.csdn.net/Nismilesucc/article/details/119784723