动手学深度学习1- pytorch初学

pytorch 初学

Tensors

Tensors 与numpy 中的ndarrays很像,pytorch可以支持GPU操作

from __future__ import print_function
import torch
创建空的tensor
x = torch.empty(5,3) # 5行3列的空的tensors
print(x)
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
创建随机的一个随机数矩阵
x = torch.rand(5,3)
print(x)
tensor([[0.5109, 0.1927, 0.5499],
        [0.8677, 0.8713, 0.9610],
        [0.9356, 0.0391, 0.3159],
        [0.0266, 0.7895, 0.6610],
        [0.7188, 0.1331, 0.2180]])
创建0元素的矩阵
x = torch.zeros(5,3)
print(x)
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
直接从已经数据创建tensor
x= torch.tensor([5,5,3])
print(x)
tensor([5, 5, 3])
创建新的矩阵
x = x.new_ones(5,3,dtype=torch.double)
print(x)
# 根据现有的张量创建张量。 这些方法将重用输入张量的属性,例如, dtype,除非设置新的值进行覆盖
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
x = torch.randn_like(x,dtype=torch.float)
print(x)
# 更新了x的dtype,保留了原始的x的size
tensor([[ 0.8914,  1.5704, -0.1844],
        [ 0.7747, -0.6860, -0.5596],
        [ 0.1804, -0.2909, -1.3262],
        [-1.3021, -0.4132, -2.7060],
        [ 0.8989, -0.7269,  1.3862]])
print(x.size())
torch.Size([5, 3])

计算操作

加法操作
y = torch.rand(5,3)
print(x+y)
tensor([[ 1.6333,  2.1744,  0.4975],
        [ 1.5430, -0.5863, -0.1416],
        [ 0.6954,  0.6694, -0.4113],
        [-0.9279, -0.1156, -1.8519],
        [ 1.5791,  0.1524,  2.1037]])
print(torch.add(x,y))
tensor([[ 1.6333,  2.1744,  0.4975],
        [ 1.5430, -0.5863, -0.1416],
        [ 0.6954,  0.6694, -0.4113],
        [-0.9279, -0.1156, -1.8519],
        [ 1.5791,  0.1524,  2.1037]])
result = torch.empty(5,3)
torch.add(x,y,out=result)
print(result)
tensor([[ 1.6333,  2.1744,  0.4975],
        [ 1.5430, -0.5863, -0.1416],
        [ 0.6954,  0.6694, -0.4113],
        [-0.9279, -0.1156, -1.8519],
        [ 1.5791,  0.1524,  2.1037]])
# 加法操作,inplace 操作,替换y值,类似于y+=x
y.add_(x)
print(y)
tensor([[ 1.6333,  2.1744,  0.4975],
        [ 1.5430, -0.5863, -0.1416],
        [ 0.6954,  0.6694, -0.4113],
        [-0.9279, -0.1156, -1.8519],
        [ 1.5791,  0.1524,  2.1037]])
转化形状
x = torch.randn(4,4)
y= x.view(16)
z=x.view(-1,8)  # the size -1 是根据其他维度进行计算出来的
print(x.size(),y.size(),z.size())
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
torch.numel(x)  # return the number of the input tensor
16
tensor 与numpy 的转化
import numpy as np
a = np.array([1,2,3])
t = torch.as_tensor(a)
print(t)
tensor([1, 2, 3])
a = torch.ones(5)
print(a)
tensor([1., 1., 1., 1., 1.])
b = a.numpy()  # 类似于a和b,a 变了,b也跟着变,类似于numpy 中的view的操作
print(b)
[1. 1. 1. 1. 1.]
a.add_(1)
print(a)
print(b)
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]
id(a),id(b)
(4487109080, 4807132064)

数据在GPU上的操作

# 以下代码只有在PyTorch GPU版本上才会执行,配的mac没有GPU,所以没有显示结果
if torch.cuda.is_available():
    device = torch.device("cuda")          # GPU
    y = torch.ones_like(x, device=device)  # 直接创建一个在GPU上的Tensor
    x = x.to(device)                       # 等价于 .to("cuda")
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # to()还可以同时更改数据类型

自动梯度求导

深度学习中通常需要对函数求梯度(gradient),pytorch提供的autograd包能够根据输入和前向传播过程自动构建计算图,并执行方向传播过程,后续将主要介绍autograd包实现自动求梯度的有关操作

自动求导的概念

上节介绍的Tensor是这个包的核心类,如果将其属性 .required_grad 设置为True,将开始追踪(track)在其上的所有操作(可以利用链式法则进行梯度传播了)。计算完成后,可以调用.backward() 来完成所有的梯度计算。此Tensor的梯度将累积到.grad属性中。
需要注意的是,如果调用y.backward()时,如果y是标量,则不需要为backward() 传入任何参数。其余情况,需要传入一个与y相同shape的Tensor。

如果不想被继续追踪,可以调用.detach()将其追踪记录中分离出来,这样就可以防止将来的计算被追踪,这样梯度就传不过去了。此外还可以用with torch.no_grad() 将不想被追踪的操作代码块包裹起来,这样的方法在评估模型的时候常用,因为在评估模型时,不需要计算已经训练出的参数的的梯度。

Function 类

Function是另外一个很重要的类,Tensor和Function相互结合就可以构建一个记录有整个计算过程的有向无环图(DAG)????
每个Tensor都有一个.grad_fn属性,该属性即创建Tensor的Function,就是说该Tensor是不是通过某些运算得到的,如果是,grad_fn返还一个与这些运算相关的对象,否则是None。

Tensor实例
# 创建一个Tensor并设置requires_grad=True
x= torch.ones(2,2,requires_grad=True)
print(x)
print(x.grad_fn)# 返回结果为None,x是直接创建的,则说明该Tensor不是通过运算得到
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None
y =x+2
print(y)
print(y.grad_fn)  ## AddBackward0,y是通过一个假发操作创建的


'''
想x这样直接通过创建的称为叶子节点,叶子节点对应grad_fn 是None


'''
print(x.is_leaf,y.is_leaf)

tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x11eb55cc0>
True False
复杂运算
z = y*y*3
out = z.mean()
print(z,out)
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward1>)
"""
可以通过.requires_grad_()来用in_place的方式改变requires_grad的属性
"""
a= torch.randn(2,2)  # 缺失的情况下默认requires_grad=False
a =(a*3)/(a-1)
print(a.requires_grad)   # False
a.requires_grad_(True)
print(a.requires_grad)    # True
b = (a*a).sum()
print(b.grad_fn)

False
True
<SumBackward0 object at 0x11eb65780>
梯度
'''
因为out是一个标量,所以调用backward()时不需要指定求导变量:

'''
out.backward()    # 等价于out.backward(torch.tensor(1.))

我们看下out关于x的梯度

\[\frac { d ( o u t ) } { d x } \]

print(x.grad)
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

\[o = \frac { 1 } { 4 } \sum _ { i = 1 } ^ { 4 } z _ { i } = \frac { 1 } { 4 } \sum _ { i = 1 } ^ { 4 } 3 \left( x _ { i } + 2 \right) ^ { 2 } \]

\[\left. \frac { \partial o } { \partial x _ { i } } \right| _ { x _ { i } = 1 } = \frac { 9 } { 2 } = 4.5 \]

数学上的意义

数学上,如果有一个函数值和自变量都为向量的函数 y=f(x)
y=f(x), 那么 y关于x 的梯度就是一个雅可比矩阵(Jacobian matrix):

\[J = \left( \begin{array} { c c c } { \frac { \partial y 1 } { \partial x _ { 1 } } } & { \cdots } & { \frac { \partial y _ { 1 } } { \partial x _ { n } } } \\ { \vdots } & { \ddots } & { \vdots } \\ { \frac { \partial y _ { m } } { \partial x _ { 1 } } } & { \cdots } & { \frac { \partial y _ { m } } { \partial x _ { n } } } \end{array} \right) \]

而torch.autograd这个包就是用来计算一些雅克比矩阵的乘积的,例如如果v是一个标量函数的L = g(y)的梯度:

\[v = \left( \begin{array} { c c c } { \frac { \partial l } { \partial y 1 } } & { \cdots } & { \frac { \partial l } { \partial y m } } \end{array} \right) \]

那么根据链式法则,可以得到:L关于x的雅克比矩阵就是

\[v J = \left( \begin{array} { c c c } { \frac { \partial l } { \partial y _ { 1 } } } & { \cdots } & { \frac { \partial l } { \partial y _ { m } } } \end{array} \right) \left( \begin{array} { c c c } { \frac { \partial y _ { 1 } } { \partial x _ { 1 } } } & { \cdots } & { \frac { \partial y _ { 1 } } { \partial x _ { n } } } \\ { \vdots } & { \ddots } & { \vdots } \\ { \frac { \partial y _ { m } } { \partial x _ { 1 } } } & { \cdots } & { \frac { \partial y _ { m } } { \partial x _ { n } } } \end{array} \right) = \left( \begin{array} { c c c } { \frac { \partial l } { \partial x _ { 1 } } } & { \cdots } & { \frac { \partial l } { \partial x _ { n } } } \end{array} \right) \]

注意:grad在反向传播过程中是累加的(accumulated),这意味着运行反向传播,梯度都会累加到前一次的梯度,所以一般在反正传播之前需要把梯度清零

# 再来反向传播一次,注意grad是累加的
out2 = x.sum()
out2.backward()
print(x.grad)

out3 = x.sum()
x.grad.data.zero_()   # 梯度清零,将梯度的数据变成0
out3.backward()
print(x.grad)

tensor([[5.5000, 5.5000],
        [5.5000, 5.5000]])
tensor([[1., 1.],
        [1., 1.]])

现在需要解释一个问题:
为什么在y.backward()时,如果y是标量,责不需要为backward()传入任何参数;否则需要传入一个与y同形的Tensor?

  1. 首先为了避免向量(甚至更高维张量)对张量求导,而转换成标量对张量求导;
  2. 不允许张量对张量求导,只允许标量与张量求导,求导的结果是和自变量同形的张量。这个地方说的就是不能让函数对函数求导(估计是一个意思吧)
x = torch.tensor([1.0,2.0,3.0,4.0],requires_grad=True)
y = 2*x
z= y.view(2,2)
print(z)
tensor([[2., 4.],
        [6., 8.]], grad_fn=<ViewBackward>)

现在的y不是一个标量,所以在调用backward()时需要传入一个和y同形的权重向量进行加权就和得到一个标量。

v = torch.tensor([[1.0,1.0],[0.01,0.01]],dtype=torch.float)
z.backward(v)   # 此时v就是与y同形的权重向量
print(x.grad)   # x.grad是和x同形的张量

tensor([2.0000, 2.0000, 0.0200, 0.0200])
中断梯度追踪
x = torch.tensor(1.0,requires_grad=True)
y1 = x**2
with torch.no_grad():
    y2 = x**3
y3 = y1+y2
print(x.requires_grad)
print(y1,y1.requires_grad)   # 平方
print(y2,y2.requires_grad)   # 标量,在with torch.no_grad未被追踪
print(y3,y3.requires_grad)    # 求和
print(y2,y2.is_leaf)  #  标量没有计算公式,y2也称为称为叶子节点,叶子节点对应grad_fn 是None

True
tensor(1., grad_fn=<PowBackward0>) True
tensor(1.) False
tensor(2., grad_fn=<AddBackward0>) True
tensor(1.) True
y3.backward()
print(x.grad)
tensor(2.)

y3 = y1+y2 =x2+ y3,当x=1 时,dy3/dx应该是5,但是y2的定义被torch.no_grad()包裹的,
所以与y2相关的梯度是不会被回传的,只有与y1有关的梯度才会回传,即x**2对x的梯度

修改tensor的数字,不被autograd记录(即不会影响方向传播),可以对tensor.data 进行操作
x = torch.ones(1,requires_grad=True)
print(x.data) # 也是一个tensor
print(x.data.requires_grad)  # 但是已经是独立于计算图之外
y = 2*x

x.data *=100 # 只改变了data属性值,不会记录在计算图,因此不会影响梯度传播
y.backward()
print(x)
print(x.grad)
tensor([1.])
False
tensor([100.], requires_grad=True)
tensor([2.])

posted on 2019-10-27 21:05  多一点  阅读(692)  评论(0编辑  收藏  举报

导航