ch01-PyTorch基础概念

ch01-PyTorch基础概念

    • 0.引言
    • 1.PyTorch简介
    • 2.环境配置
    • 3.张量简介与创建
      • 3.1.张量的概念:多维数组
      • 3.2.张量的创建
        • 3.2.1.直接创建
        • 3.2.2.依据数值创建
        • 3.2.3.依概率分布创建张量
    • 4.张量操作与线性回归
      • 4.1.张量的操作:拼接、切分、索引与变换
        • 4.1.1.拼接
        • 4.1.2.切分
        • 4.1.3.索引
        • 4.1.4.变换
      • 4.2.张量的数学运算
      • 4.3.线性回归
    • 5.计算图与动态图机制
      • 5.1.计算图
      • 5.2.PyTorch 的动态图机制
    • 6.autograd与逻辑回归
      • 6.1.autograd 自动求导系统
      • 6.2. 逻辑回归
      • 6.3. 总结

0.引言

  • PyTorch 学习笔记01
  • PyTorch 学习笔记02
  • PyTorch_Practice

ch01-PyTorch基础概念_第1张图片

1.PyTorch简介

略.

2.环境配置

  • 参考链接.

1)验证成功

import torch
a=torch.ones(2,2)
a
tensor([[1., 1.],
        [1., 1.]])

查看pytorch版本

print ("hello pytorch {}".format(torch.__version__))

3)查看是否支持GPU

print (torch.cuda.is_available())

3.张量简介与创建

3.1.张量的概念:多维数组

ch01-PyTorch基础概念_第2张图片

ch01-PyTorch基础概念_第3张图片

Tensor与Variable:

  • 参考1.

  • 参考2

  • Variable:主要用于封装Tensor,进行自动求导,是torch.autograd中的数据类型。Variable是Pytorch的0.4.0版本之前的一个重要的数据结构,但是从0.4.0开始,它已经并入了Tensor中了。

  • data:被封装的Tensor

  • grad:data的梯度

  • grad_fn:创建Tensor的Function,是自动求导的关键

  • requires_grad:指示是否需要梯度

  • is_leaf:指示是否是叶子

从Pytorch0.4.0版本开始,Variable并入Tensor:
ch01-PyTorch基础概念_第4张图片

  • dtype: 张量的数据类型,三大类,共9种。torch.FloatTensor, torch.cuda.FloatTensor
    ch01-PyTorch基础概念_第5张图片

  • shape: 张量的形状。如:(64,3,224,224)

  • decive: 所在设备

3.2.张量的创建

3.2.1.直接创建

  • 1.torch.tensor()
torch.tensor(data, dtype=None, device=None, requires_grad=False, pin_memory=False)
功能:从data 创建 tensor

data : 数据 , 可以是 list, numpy
dtype : 数据类型,默认与 data 的一致
device 所在设备 , cuda cpu
requires_grad :是否需要梯度
pin_memory :是否存于锁页内存
import torch
import numpy as np

# Create tensors via torch.tensor

flag = True

if flag:
    arr = np.ones((3, 3))
    print("type of data:", arr.dtype)

    t = torch.tensor(arr, device='cuda')
    print(t)
type of data: float64
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], device='cuda:0', dtype=torch.float64)

其中,cuda表示采用了gpu,0是gpu的标号,由于只有一个gpu,因此是0。

  • 2.从numpy创建tensor : torch.from_numpy(ndarray)
    注意:共享内容。从 torch.from_numpy 创建的 tensor 于原 ndarray 共享内存 ,当修改其中一个的数据,另外一个也将会被改动。
    ch01-PyTorch基础概念_第6张图片
# Create tensors via torch.from_numpy(ndarray)
arr = np.array([[1, 2, 3], [4, 5, 6]])
t = torch.from_numpy(arr)
print("numpy array: ", arr)
print("tensor : ", t)

print("\n修改arr")
arr[0, 0] = 0
print("numpy array: ", arr)
print("tensor : ", t)

print("\n修改tensor")
t[0, 0] = -1
print("numpy array: ", arr)
print("tensor : ", t)
numpy array:  [[1 2 3]
 [4 5 6]]
tensor :  tensor([[1, 2, 3],
        [4, 5, 6]], dtype=torch.int32)

修改arr
numpy array:  [[0 2 3]
 [4 5 6]]
tensor :  tensor([[0, 2, 3],
        [4, 5, 6]], dtype=torch.int32)

修改tensor
numpy array:  [[-1  2  3]
 [ 4  5  6]]
tensor :  tensor([[-1,  2,  3],
        [ 4,  5,  6]], dtype=torch.int32)

3.2.2.依据数值创建

  • 1.torch.zeros():按照size创建全0张量
    • 功能:依size 创建全 0 张量
    • size : 张量的形状 , 如 (3,3),(3,224,224)
    • out : 输出的张量
    • layout 内存中布局形式 , 有strided(默认), sparse_coo(这个通常稀疏矩阵时设置,提高读取效率) 等
    • device 所在设备 , gpu cpu
    • requires_grad :是否需要梯度
torch.zeros(*size, out=None, dtype=None, 
	layout=torch.strided, device=None, requires_grad=False)

可见,该out的值与t相同,因此out是一个输出的作用,将张量生成的数据赋值给另一个变量。

  • 2.torch.zeros_like()
    • 功能:依据input 形状创建全 0 张量
    • intput : 创建与 input 同形状的全 0 张量
    • dtype : 数据类型
    • layout 内存中布局形式
torch.zeros_like(input, dtype=None, layout=None, device=None, requires_grad=False)
  • 3.torch.ones()
  • 4.torch.ones_like()
  • 5.torch.full()
  • 6.torch.full_like()
    • 功能:依据input 形状创建指定数据的张量
    • size : 张量的形状 , 如 (3,3)
    • fill_value : 张量的值
t = torch.full((3, 3), 5)
print(t)

tensor([[5, 5, 5],
        [5, 5, 5],
        [5, 5, 5]])
  • 7.torch.arange(),创建等差数列,区间:[start, end)
    • 功能:创建等差的1 维张量
    • 注意事项:数值区间为[start,end)
    • start : 数列起始值
    • end : 数列“结束值”
    • step : 数列公差,默认为 1
t = torch.arange(start=0, end=100, step=1, out=None, dtype=None, 
	layout=torch.strided, device=None, requires_grad=False)
t = torch.arange(2, 10, 2)
print(t)
# tensor([2, 4, 6, 8])
  • 8.torch.linspace(),创建均分数列,区间:[start, end]
    • 注意:step是步长;steps是长度
    • 功能:创建均分的1 维张量
    • start : 数列起始值,end : 数列结束值,steps : 数列长度,注意是长度。
    • 它的步长就是(end - start)/ steps。
t = torch.linspace(start=0, end=100, steps=5, out=None, dtype=None, 
	layout=torch.strided, device=None, requires_grad=False)
t = torch.linspace(2, 10, 6)
print(t)

# tensor([ 2.0000,  3.6000,  5.2000,  6.8000,  8.4000, 10.0000])
  • 9.torch.logspace(),创建对数均分的1维张量
    • 注意:长度steps,底是base默认为10
    • start : 数列起始值,end : 数列结束值,steps : 数列长度,base : 对数函数的底,默认为 10
t = torch.logspace(start=0, end=100, steps=5, base=10, out=None, 
	dtype=None, layout=torch.strided, device=None, requires_grad=False)
  • 10.torch.eye(),创建单位对角矩阵(2维张量)
    • 注意事项:默认为方阵,n: 矩阵行数; m:矩阵列

3.2.3.依概率分布创建张量

  • 1.torch.normal():生成正态分布(高斯分布),mean : 均值,std : 标准差
    • 四种模式: mean为标量, std为标量; mean为标量, std为张量; mean为张量, std为标量; mean为张量, std为张量。后三种基本用法相同,都是根据不同的维数进行
torch.normal(mean, std, out=None)
torch.normal(mean, std, size, out=None)
# the mean and std both are tensors
mean = torch.arange(1, 5, dtype=torch.float)
std = torch.arange(1, 5, dtype=torch.float)
t_normal = torch.normal(mean, std)
print("mean:{}\nstd:{}".format(mean, std))
print(t_normal)
#######由结果可知,其生成的tensor是上面每一维度的参数生成的。
# mean:tensor([1., 2., 3., 4.])
# std:tensor([1., 2., 3., 4.])
# tensor([ 0.4750,  3.6384, -2.1488,  5.3180])

需要注意的是,对于mean和std都是标量的情况下,需要指定生成的size。

# mean: scalar std: scalar
t_normal = torch.normal(0., 1., size=(4,))
print(t_normal)
# tensor([0.6614, 0.2669, 0.0617, 0.6213])
  • 2.torch.randn()
  • 3.torch.randn_like(), 生成标准正态分布
    • 注意:size指的是张量的形状
    • 功能:生成标准正态分布(均值为0,方差为1)
    • size : 张量的形状。
torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
  • 4.torch.rand()
  • 5.torch.rand_like() , 在区间[0, 1]上,生成均匀分布
  • 6.torch.randint()
  • 7.torch.randint_like(),在[low, high)生成整数均匀分布
    • 功能:区间[low, high) 生成整数均匀分布
    • size : 张量的形状
  • 8.torch.randperm(), 生成从0–n-1的随机排列
    • 功能:生成生成从0 到 n-1 的随机排列
    • n : 张量的长度
  • 9.torch.bernoulli(),生成伯努利分布
    • 功能 :以 input 为概率,生成伯努力分布(0 1 分布,两点分布)
    • input : 概率值

4.张量操作与线性回归

  • 参考1

  • 参考2

4.1.张量的操作:拼接、切分、索引与变换

4.1.1.拼接

  • torch.cat(): 将张量按维度dim进行拼接
    • 功能:将张量按维度dim进行拼接
    • tensor:张量序列
    • dim:拼接维度
  • torch.stack():在新建的维度dim上进行拼接
    • 功能:在新创建的维度dim上进行拼接
    • tensor:张量序列
    • dim:要拼接的维度
t = torch.ones((2, 3))

t_0 = torch.cat([t, t], dim=0)
t_1 = torch.stack([t, t], dim=0)

print(t_0)
print(t_0.shape)
print(t_1)
print(t_1.shape)
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
torch.Size([4, 3])
tensor([[[1., 1., 1.],
         [1., 1., 1.]],

        [[1., 1., 1.],
         [1., 1., 1.]]])
torch.Size([2, 2, 3])

与cat相比,stack创建在了一个新维度

4.1.2.切分

  • torch.chunk(input, chunks, dim): 将张量按维度dim进行平均切分
    • 功能:将张量按维度dim进行平均切分
    • 返回值:张量列表
    • 注意:若不能整除,最后一份张量小于其他张量
    • input:要切分的张量
    • chunks:要切分的份数
    • dim:要切分的维度
    t = torch.ones((2, 7))
    print(t)
    
    list_of_tensor = torch.chunk(t, dim=1, chunks=3)
    print(list_of_tensor)
    
    tensor([[1., 1., 1., 1., 1., 1., 1.],
            [1., 1., 1., 1., 1., 1., 1.]])
    (tensor([[1., 1., 1.],
            [1., 1., 1.]]), 
    tensor([[1., 1., 1.],
            [1., 1., 1.]]),
    tensor([[1.], [1.]]))
    
  • torch.split(): 将张量按维度dim进行切分
    • 功能:将张量按维度dim进行切分
    • 返回值:张量列表
    • split_size_or_sections:为int时,表示每一份的长度;为list时,按list元素切分
    t = torch.ones((2, 7))
    print(t)
    list_of_tensor_2 = torch.split(t, 3, dim=1)
    print(list_of_tensor_2)
    
    list_of_tensor_3 = torch.split(t, [2, 2, 3], dim=1)
    print(list_of_tensor_3)
    
    tensor([[1., 1., 1., 1., 1., 1., 1.],
            [1., 1., 1., 1., 1., 1., 1.]])
    (tensor([[1., 1., 1.],
            [1., 1., 1.]]), tensor([[1., 1., 1.],
            [1., 1., 1.]]), tensor([[1.],
            [1.]]))
    (tensor([[1., 1.],
            [1., 1.]]), tensor([[1., 1.],
            [1., 1.]]), tensor([[1., 1., 1.],
            [1., 1., 1.]]))
    

list内元素之和等于维度上的长度

4.1.3.索引

  • torch.index_select(): 在维度dim上,按index索引数据

    • 功能:在维度dim上,按index索引数据
    • 返回值:依index索引数据拼接的张量
    t = torch.randint(0, 9, (3, 3))
    print(t)
    
    # index_select
    idx=torch.tensor([0,2],dtype=torch.long)
    t_index_select=torch.index_select(t,index=idx,dim=0)
    print(t_index_select)
    
    tensor([[7, 1, 5],
            [1, 3, 4],
            [3, 4, 0]])
    tensor([[7, 1, 5],
            [3, 4, 0]])
    
  • torch.masked_select(): 按mask中的True进行索引, 返回一维张量。

    • 功能:按mmask中的True进行索引
    • 返回值:一维张量
    • input:要索引的张量
    • mask:与input同形状的布尔类型张量
    t = torch.randint(0, 9, (3, 3))
    print(t)
    
    # masked_select
    mask = t.ge(5)
    print(mask)
    
    t_masked_select = torch.masked_select(t, mask)
    print(t_masked_select)
    
    tensor([[3, 8, 1],
            [6, 4, 1],
            [4, 8, 2]])
    tensor([[False,  True, False],
            [ True, False, False],
            [False,  True, False]])
    tensor([8, 6, 8])
    

4.1.4.变换

  • orch.reshape()

    • 功能:变换张量的形状
    • 注意:当张量在内存中是连续时,新张量与input共享内存
    • input:要变换的张量
    • shape:新张量的形状
    # torch.reshape
    t = torch.randperm(8)
    print(t)
    t_reshape = torch.reshape(t, (2, 4))  # -1代表不关心
    print(t_reshape)
    
    tensor([2, 3, 1, 4, 0, 5, 7, 6])
    tensor([[2, 3, 1, 4],
            [0, 5, 7, 6]])
    
  • torch.transpose(): 交换张量的两个维度

    # torch.transpose
    t = torch.rand((2, 3, 4))
    print(t)
    t_transpose = torch.transpose(t, dim0=1, dim1=2)
    print(t_transpose)
    
    tensor([[[0.5063, 0.6772, 0.8968, 0.4836],
             [0.0820, 0.5198, 0.1273, 0.1895],
             [0.3535, 0.9936, 0.7150, 0.4375]],
    
            [[0.7801, 0.9114, 0.2901, 0.7171],
             [0.0553, 0.9102, 0.4060, 0.4010],
             [0.1037, 0.1053, 0.7860, 0.4523]]])
    tensor([[[0.5063, 0.0820, 0.3535],
             [0.6772, 0.5198, 0.9936],
             [0.8968, 0.1273, 0.7150],
             [0.4836, 0.1895, 0.4375]],
    
            [[0.7801, 0.0553, 0.1037],
             [0.9114, 0.9102, 0.1053],
             [0.2901, 0.4060, 0.7860],
             [0.7171, 0.4010, 0.4523]]])
    
  • torch.t(): 2维张量转置,对矩阵而言,等价于 torch.transpose(input, 0, 1)

  • torch.squeeze(): 压缩长度为1的维度(轴)

    • 功能:压缩长度为1的维度(轴)
    • dim:若为None,移除所有长度为1的轴;若指定维度,当且仅当该轴长度为1时,课被移除
    # torch.squeeze
    t=torch.rand((1,2,3,1))
    
    t1=torch.squeeze(t)
    print(t1.shape)
    
    t2=torch.squeeze(t,dim=2)
    print(t2.shape)
    
    torch.Size([2, 3])
    torch.Size([1, 2, 3, 1])
    
  • torch.unsqueeze(): 依据dim扩展维度

    • 功能:依据dim扩展维度

4.2.张量的数学运算

ch01-PyTorch基础概念_第7张图片

这里重点演示一下加法这个函数,因为这个函数有一个小细节:torch.add(input, alpha=1, other, out=None):逐元素计算input+alpha * other。注意人家这里有个 alpha,叫做乘项因子。类似权重的个东西。这个东西让计算变得更加简洁, 比如线性回归我们知道有个 y = wx + b, 在这里直接一行代码torch.add(b, w, x) 就搞定。

  • torch.add(): 逐元素计算 input+alpha×other

ch01-PyTorch基础概念_第8张图片

# torch.add
t0=torch.rand((3,3))
t1=torch.ones_like(t0)
print(t0)
print(t1)
t_add=torch.add(t0,10,t1)
print(t_add)
t_0:
tensor([[ 0.6614,  0.2669,  0.0617],
        [ 0.6213, -0.4519, -0.1661],
        [-1.5228,  0.3817, -1.0276]])
t_1:
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
t_add_10:
tensor([[10.6614, 10.2669, 10.0617],
        [10.6213,  9.5481,  9.8339],
        [ 8.4772, 10.3817,  8.9724]])

-torch.addcdiv()

  • torch.addcmul()
    ch01-PyTorch基础概念_第9张图片

4.3.线性回归

线性回归是分析一个变量与另外一(多)个变量之间关系的方法。因变量是 y,自变量是 x,关系线性:

任务就是求解 w,b。
ch01-PyTorch基础概念_第10张图片

ch01-PyTorch基础概念_第11张图片

我们的求解步骤:

  • 1.确定模型:Model -> y = wx + b
  • 2.选择损失函数:这里用 MSE :
  • 3.求解梯度并更新 w, b:

这就是我上面说的叫做代码逻辑的一种思路,写代码往往习惯先有一个这样的一种思路,然后再去写代码的时候,就比较容易了。而如果不系统的学一遍 Pytorch,一上来直接上那种复杂的 CNN, LSTM 这种,往往这些代码逻辑不好形成,因为好多细节我们根本就不知道。所以这次学习先从最简单的线性回归开始,然后慢慢的到复杂的那种网络。下面我们开始写一个线性回归模型:

# -*- coding:utf-8 -*-
"""
@file name  : lesson-03-Linear-Regression.py
@author     : TingsongYu https://github.com/TingsongYu
@date       : 2018-10-15
@brief      : 一元线性回归模型
"""
import torch
import matplotlib.pyplot as plt
torch.manual_seed(10)

lr = 0.05  # 学习率    20191015修改

# 创建训练数据
x = torch.rand(20, 1) * 10  # x data (tensor), shape=(20, 1)
y = 2*x + (5 + torch.randn(20, 1))  # y data (tensor), shape=(20, 1)

# 构建线性回归参数
w = torch.randn((1), requires_grad=True)
b = torch.zeros((1), requires_grad=True)

for iteration in range(1000):

    # 前向传播
    wx = torch.mul(w, x)
    y_pred = torch.add(wx, b)

    # 计算 MSE loss
    loss = (0.5 * (y - y_pred) ** 2).mean()

    # 反向传播
    loss.backward()

    # 更新参数
    b.data.sub_(lr * b.grad)
    w.data.sub_(lr * w.grad)

    # 清零张量的梯度   20191015增加
    w.grad.zero_()
    b.grad.zero_()

    # 绘图
    if iteration % 20 == 0:

        plt.scatter(x.data.numpy(), y.data.numpy())
        plt.plot(x.data.numpy(), y_pred.data.numpy(), 'r-', lw=5)
        plt.text(2, 20, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color':  'red'})
        plt.xlim(1.5, 10)
        plt.ylim(8, 28)
        plt.title("Iteration: {}\nw: {} b: {}".format(iteration, w.data.numpy(), b.data.numpy()))
        plt.pause(1.5)

        if loss.data.numpy() < 1:
            break
          

今天的学习内容结束, 下面简单的梳理一遍,其实小东西还是挺多的。

  • 首先我们从 Pytorch 最基本的数据结构开始,认识了张量到底是个什么东西,说白了就是个多维数组,而张量本身有很多的属性,有关于数据本身的 data,dtype,shape,dtype,也有关于求导的 requires_grad,grad,grad_fn,is_leaf;
  • 然后我们学习了张量的创建方法,比如直接创建,从数组创建,数值创建,按照概率创建等。这里面涉及到了很多的创建函数 tensor(),from_numpy(),ones(),zeros(),eye(),full(),arange(),linspace(),normal(),randn(),rand(),randint(),randperm() 等;
  • 接着就是张量的操作部分,有基本操作和数学运算,基本操作部分有张量的拼接两个函数 (.cat, .stack),张量的切分两个函数 (.chunk, .split),张量的转置 (.reshape, .transpose, .t),张量的索引两个函数 (.index_select, .masked_select)。数学运算部分,也是很多数学函数,有加减乘除的,指数底数幂函数的,三角函数的很多;
  • 最后基于上面的所学完成了一个简单的线性回归。

这次整理了很多的函数,每个函数的用法不同,具体用法先不用刻意记住,先知道哪些函数具体完成什么功能,到时候用的时,边查边用,慢慢的多练才能熟。

5.计算图与动态图机制

  • 参考1
  • 参考2

5.1.计算图

深度学习就是对张量进行一系列的操作,随着操作种类和数量的增多,会出现各种值得思考的问题。比如多个操作之间是否可以并行,如何协同底层的不同设备,如何避免冗余的操作,以实现最高效的计算效率,同时避免一些 bug。因此产生了计算图 (Computational Graph)。

计算图是用来描述运算的有向无环图,有两个主要元素:节点 (Node) 和边 (Edge)。

  • 节点表示数据,如向量、矩阵、张量。
  • 边表示运算,如加减乘除卷积等。

用计算图表示:y=(x+w)*(w+1),如下所示:

  • a = x + w
  • b = w + 1
  • y = a * b

ch01-PyTorch基础概念_第12张图片

可以看作, y = a × b y=a \times b y=a×b ,其中 a = x + w , b = w + 1 a=x+w,b=w+1 a=x+wb=w+1

计算图与梯度求导:这里求 y 对 w 的导数。根复合函数的求导法则,可以得到如下过程。

∂ y ∂ w = ∂ y ∂ a ∂ a ∂ w + ∂ y ∂ b ∂ b ∂ w = b ∗ 1 + a ∗ 1 = b + a = ( w + 1 ) + ( x + w ) = 2 ∗ w + x + 1 = 2 ∗ 1 + 2 + 1 = 5 \begin{aligned} \frac{\partial y}{\partial w} & =\frac{\partial y}{\partial a} \frac{\partial a}{\partial w}+\frac{\partial y}{\partial b} \frac{\partial b}{\partial w} \\ & =b * 1+a * 1 \\ & =b+a \\ & =(w+1)+(x+w) \\ & =2 * w+x+1 \\ & =2 * 1+2+1=5 \end{aligned} wy=aywa+bywb=b1+a1=b+a=(w+1)+(x+w)=2w+x+1=21+2+1=5
ch01-PyTorch基础概念_第13张图片

体现到计算图中,就是根节点 y 到叶子节点 w 有两条路径 y -> a -> w和y ->b -> w。根节点依次对每条路径的孩子节点求导,一直到叶子节点w,最后把每条路径的导数相加即可。

代码如下:

import torch
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
# y=(x+w)*(w+1)
a = torch.add(w, x)     # retain_grad()
b = torch.add(w, 1)
y = torch.mul(a, b)
# y 求导
y.backward()
# 打印 w 的梯度,就是 y 对 w 的导数
print(w.grad)
# 结果为tensor([5.])

回顾前面说过的 Tensor 中有一个属性is_leaf标记是否为叶子节点。
ch01-PyTorch基础概念_第14张图片

在上面的例子中,x 和 w 是叶子节点,其他所有节点都依赖于叶子节点。叶子节点的概念主要是为了节省内存,在计算图中的一轮反向传播结束之后,非叶子节点的梯度是会被释放的。

  • 叶子结点 :用户创建的结点称为叶子结点,如 X 与 W

  • is_leaf: 指示张量是否为叶子结点

  • 叶子节点的作用是标志存储叶子节点的梯度,而清除在反向传播过程中的变量的梯度,以达到节省内存的目的。当然,如果想要保存过程中变量的梯度值,可以采用retain_grad()

  • grad_fn: 记录创建该张量时所用的方法(函数)

代码示例:

# 查看叶子结点
print("is_leaf:\n", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)

# 查看梯度
print("gradient:\n", w.grad, x.grad, a.grad, b.grad, y.grad)

结果为:

is_leaf:
 True True False False False
gradient:
 tensor([5.]) tensor([2.]) None None None

非叶子节点的梯度为空,如果在反向传播结束之后仍然需要保留非叶子节点的梯度,可以对节点使用retain_grad()方法。

而 Tensor 中的 grad_fn 属性记录的是创建该张量时所用的方法 (函数)。而在反向传播求导梯度时需要用到该属性。

示例代码:

# 查看梯度
print("w.grad_fn = ", w.grad_fn)
print("x.grad_fn = ", x.grad_fn)
print("a.grad_fn = ", a.grad_fn)
print("b.grad_fn = ", b.grad_fn)
print("y.grad_fn = ", y.grad_fn)

结果为

w.grad_fn =  None
x.grad_fn =  None
a.grad_fn =  
b.grad_fn =  
y.grad_fn =  

5.2.PyTorch 的动态图机制

根据计算图搭建方式,可将计算图分为动态图和静态图.

动态图 vs 静态图:

在这里插入图片描述

  • 动态图:
    • 运算与搭建同时进行
    • 灵活 易调节

例如动态图 PyTorch:
ch01-PyTorch基础概念_第15张图片

  • 静态
    • 先搭建图, 后运算
    • 高效 不灵活。

静态图 TensorFlow:
ch01-PyTorch基础概念_第16张图片

PyTorch 采用的是动态图机制 (Dynamic Computational Graph),而 Tensorflow 采用的是静态图机制 (Static Computational Graph)。

动态图是运算和搭建同时进行,也就是可以先计算前面的节点的值,再根据这些值搭建后面的计算图。优点是灵活,易调节,易调试。PyTorch 里的很多写法跟其他 Python 库的代码的使用方法是完全一致的,没有任何额外的学习成本。

静态图是先搭建图,然后再输入数据进行运算。优点是高效,因为静态计算是通过先定义后运行的方式,之后再次运行的时候就不再需要重新构建计算图,所以速度会比动态图更快。但是不灵活。TensorFlow 每次运行的时候图都是一样的,是不能够改变的,所以不能直接使用 Python 的 while 循环语句,需要使用辅助函数 tf.while_loop 写成 TensorFlow 内部的形式。

6.autograd与逻辑回归

本节课主要分为两部分:PyTorch 中的自动求导系统以及逻辑回归模型。我们知道,深度模型的训练就是不断地更新权值,而权值的更新需要求解梯度,因此,梯度在我们的模型训练过程中是至关重要的。然而,求解梯度通常十分繁琐,因此,PyTorch 中引入了自动求导系统帮助我们完成这一过程。在 PyTorch 中,我们无需手动计算梯度,只需要搭建好前向传播的计算图,然后根据 PyTorch 中的 autograd 方法就可以得到所有张量的梯度。

6.1.autograd 自动求导系统

  • torch.autograd.backward()
    • 功能:自动求取计算图中各结点的梯度。
torch.autograd.backward(
    tensors,
    grad_tensors=None,
    retain_graph=None,
    create_graph=False
)

主要参数:

  • tensors:用于求导的张量,如 loss。
  • retain_graph:保存计算图,PyTorch 默认在反向传播完成后丢弃计算图,如需保存则将该项设为 True。
  • create_graph:创建导数计算图,用于高阶求导。
  • grad_tensors:多梯度权重,当我们有多个 loss 需要计算梯度的时候,就需要设置各个 loss 的权重比例。

回顾一下如何通过计算图求解梯度:

y = ( x + w ) ∗ ( w + 1 ) y=(x+w) *(w+1) y=(x+w)(w+1)

  • a = x + w a=x+w a=x+w
  • b = w + 1 b=w+1 b=w+1
  • y = a ∗ b y=a * b y=ab
    ∂ y ∂ w = ∂ y ∂ a ∂ a ∂ w + ∂ y ∂ b ∂ b ∂ w = b ∗ 1 + a ∗ 1 = ( w + 1 ) + ( x + w ) = 2 ∗ w + x + 1 = 2 ∗ 1 + 2 + 1 = 5 \begin{aligned} \frac{\partial y}{\partial w} & =\frac{\partial y}{\partial a} \frac{\partial a}{\partial w}+\frac{\partial y}{\partial b} \frac{\partial b}{\partial w} \\ & =b * 1+a * 1 \\ & =(w+1)+(x+w) \\ & =2 * w+x+1 \\ & =2 * 1+2+1=5 \end{aligned} wy=aywa+bywb=b1+a1=(w+1)+(x+w)=2w+x+1=21+2+1=5

代码示例:

w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)

# 如果希望后面再次执行该计算图,可以将 retain_graph 参数设为 True
# y.backward(retain_graph=True) 

y.backward()
print(w.grad)

输出结果:

tensor([5.])

当有多个 loss 需要计算梯度时,通过 grad_tensors 设置各 loss 权重比例:

w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

a = torch.add(w, x)
b = torch.add(w, 1)

# y0 = (x+w) * (w+1)    dy0/dw = 2*w + x + 1 = 5
y0 = torch.mul(a, b)

# y1 = (x+w) + (w+1)    dy1/dw = 2
y1 = torch.add(a, b)  

# 这种情况下,loss 是一个向量 [y0, y1]
loss = torch.cat([y0, y1], dim=0)

# 梯度的权重:dy0/dw 权重为 1,dy1/dw 权重为 2
grad_tensors = torch.tensor([1., 2.])

# gradient 传入 torch.autograd.backward() 中的 grad_tensors
loss.backward(gradient=grad_tensors)  

print(w.grad) # 5*1 + 2*2 = 9

输出结果:

tensor([9.])
  • torch.autograd.grad()
    • 功能:求取梯度。
torch.autograd.grad(
    outputs,
    inputs,
    grad_outputs=None,
    retain_graph=None,
    create_graph=False
)

主要参数:

  • outputs:用于求导的张量,如 loss。
  • inputs:需要梯度的张量。
  • create_graph:创建导数计算图,用于高阶求导。
  • retain_graph:保存计算图。
  • grad_outputs:多梯度权重。

求取二阶梯度:

x = torch.tensor([3.], requires_grad=True)
y = torch.pow(x, 2)  # y = x**2

# grad_1 = dy/dx = 2x = 2 * 3 = 6
grad_1 = torch.autograd.grad(y, x, create_graph=True)  
print(grad_1)

# grad_2 = d(dy/dx)/dx = d(2x)/dx = 2
grad_2 = torch.autograd.grad(grad_1[0], x)  
print(grad_2)

输出结果:

(tensor([6.], grad_fn=),)
(tensor([2.]),)

注意事项:

  • 梯度不自动清零。
  • 依赖于叶子结点的结点,requires_grad 默认为 True。
  • 叶子结点不可执行原位操作 (in-place)。

代码示例 1:

# 1. 梯度不会自动清零,重复求取会叠加,可以使用 .grad.zero_() 方法手动清零
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

for i in range(3):
    a = torch.add(w, x)
    b = torch.add(w, 1)
    y = torch.mul(a, b)

    y.backward()
    print(w.grad)

# 梯度清零,下划线表示原位操作 (in-place)
w.grad.zero_()

for i in range(3):
    a = torch.add(w, x)
    b = torch.add(w, 1)
    y = torch.mul(a, b)

    y.backward()
    print(w.grad)
    w.grad.zero_()

输出结果:

tensor([5.])
tensor([10.])
tensor([15.])
tensor([5.])
tensor([5.])
tensor([5.])

代码示例 2:

# 2. 依赖于叶子结点的结点, requires_grad 默认为 True
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)

print(a.requires_grad, b.requires_grad, y.requires_grad)

输出结果:

True True True

代码示例 3:

# 3. 叶子结点不可执行 in-place (原位操作)。因为 PyTorch 计算图中引用叶子结点的值是
#    直接引用其前向传播时的地址,为了防止计算出错,叶子结点不可执行 in-place 操作。

#    in-place (原位操作): 从原始内存地址中直接改变数据。
#    非 in-place 操作: 开辟一块新的内存地址存储改变后的数据。

a = torch.ones((1, ))
print(id(a), a)

# 非 in-place 操作
a = a + torch.ones((1, ))
print(id(a), a)

# in-place 操作
a += torch.ones((1, ))
print(id(a), a)

输出结果:

4875211904 tensor([1.])
4875212336 tensor([2.])
4875212336 tensor([3.])

对叶子结点执行 in-place 操作将导致报错:

w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)

# 对非叶子结点 a 执行非 in-place 操作
print(a.add(1))

# 对非叶子结点 a 执行 in-place 操作
print(a.add_(1))

# 对叶子结点 w 执行非 in-place 操作
print(w.add(1))

# 对叶子结点 w 执行 in-place 操作,会报错
print(w.add_(1))

y.backward()

输出结果:

tensor([4.], grad_fn=)
tensor([4.], grad_fn=)
tensor([2.], grad_fn=)
Traceback (most recent call last):
  File "", line 1, in 
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_bundle/pydev_umd.py", line 197, in runfile
    pydev_imports.execfile(filename, global_vars, local_vars)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/andy/PycharmProjects/hello_pytorch/lesson/lesson-05/lesson-05-autograd.py", line 145, in 
    print(w.add_(1))
RuntimeError: a leaf Variable that requires grad is being used in an in-place operation.

6.2. 逻辑回归

逻辑回归 (Logistic Regression) 是 线性 的 二分类 模型。
模型表达式:
y = f ( W X + b ) f ( x ) = 1 1 + e − x \begin{aligned} & y=f(W X+b) \\ & f(x)=\frac{1}{1+e^{-x}} \end{aligned} y=f(WX+b)f(x)=1+ex1
即:
y = 1 1 + e − ( W X + b ) y=\frac{1}{1+e^{-(W X+b)}} y=1+e(WX+b)1
这里, 我们将 f ( x ) f(x) f(x) 称为 Sigmoid 函数, 又称 Logistic 函数:
 class  = { 0 , y < 0.5 1 , y ≥ 0.5 \text { class }= \begin{cases}0, & y<0.5 \\ 1, & y \geq 0.5\end{cases}  class ={0,1,y<0.5y0.5

ch01-PyTorch基础概念_第17张图片

线性回归是分析 自变量 x x x 与 因变量 y y y (标量) 之间关系的方法;而逻辑回归是分析 自变量 x x x 与 因变量 y y y (概率) 之间关系的方法。

ch01-PyTorch基础概念_第18张图片

机器学习训练的 5 个步骤:

ch01-PyTorch基础概念_第19张图片

  • 1.数据:数据收集、清洗、划分、预处理。
  • 2.模型:根据任务的难易程度,选择简单的线性模型或者复杂的神经网络模型等等。
  • 3.损失函数:根据不同任务选择不同的损失函数并计算其梯度。例如:在线性回归中,我们可以选择均方误差损失函数;在分类任务中,我们可以选择交叉熵损失函数。
  • 4.优化器:得到梯度之后,我们选择某种优化器来更新权值。
  • 5.迭代训练:有了数据、模型、损失函数和优化器之后,我们就可以进行迭代训练了。

代码示例:

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
torch.manual_seed(10)

# ============================== Step 1/5: 生成数据 ===================================
sample_nums = 100
mean_value = 1.7
bias = 1
n_data = torch.ones(sample_nums, 2)
x0 = torch.normal(mean_value * n_data, 1) + bias    # 类别0 数据 shape=(100, 2)
y0 = torch.zeros(sample_nums)                       # 类别0 标签 shape=(100, 1)
x1 = torch.normal(-mean_value * n_data, 1) + bias   # 类别1 数据 shape=(100, 2)
y1 = torch.ones(sample_nums)                        # 类别1 标签 shape=(100, 1)
train_x = torch.cat((x0, x1), 0)
train_y = torch.cat((y0, y1), 0)


# ============================== Step 2/5: 选择模型 ===================================
class LR(nn.Module):
    def __init__(self):
        super(LR, self).__init__()
        self.features = nn.Linear(2, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = self.features(x)
        x = self.sigmoid(x)
        return x


lr_net = LR()   # 实例化逻辑回归模型

# ============================== Step 3/5: 选择损失函数 ================================
loss_fn = nn.BCELoss()  # 二分类交叉熵损失 Binary Cross Entropy Loss

# ============================== Step 4/5: 选择优化器 ==================================
lr = 0.01   # 学习率
optimizer = torch.optim.SGD(lr_net.parameters(), lr=lr, momentum=0.9)    # 随机梯度下降

# ============================== Step 5/5: 模型训练 ====================================
for iteration in range(1000):

    # 前向传播
    y_pred = lr_net(train_x)

    # 计算 loss
    loss = loss_fn(y_pred.squeeze(), train_y)

    # 反向传播
    loss.backward()

    # 更新参数
    optimizer.step()

    # 绘图
    if iteration % 20 == 0:

        mask = y_pred.ge(0.5).float().squeeze()  # 以 0.5 为阈值进行分类
        correct = (mask == train_y).sum()   # 计算正确预测的样本个数
        acc = correct.item() / train_y.size(0)   # 计算分类准确率

        plt.scatter(x0.data.numpy()[:, 0], x0.data.numpy()[:, 1], c='r', label='class 0')
        plt.scatter(x1.data.numpy()[:, 0], x1.data.numpy()[:, 1], c='b', label='class 1')

        w0, w1 = lr_net.features.weight[0]
        w0, w1 = float(w0.item()), float(w1.item())
        plot_b = float(lr_net.features.bias[0].item())
        plot_x = np.arange(-6, 6, 0.1)
        plot_y = (-w0 * plot_x - plot_b) / w1

        plt.xlim(-5, 7)
        plt.ylim(-7, 7)
        plt.plot(plot_x, plot_y)

        plt.text(-5, 5, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
        plt.title("Iteration: {}\nw0:{:.2f} w1:{:.2f} b:{:.2f} accuracy:{:.2%}".format(iteration, w0, w1, plot_b, acc))
        plt.legend()

        plt.show()
        plt.pause(0.5)

        if acc > 0.99:
            break

6.3. 总结

本节课介绍了 PyTorch 自动求导系统中的 torch.autograd.backwardtorch.autograd.grad 这两个常用方法,并演示了一阶、二阶导数的求导过程;理解了自动求导系统,以及数据载体 —— 张量,前向传播构建计算图,计算图求取梯度过程。有了这些知识之后,我们就可以开始正式训练机器学习模型。这里通过演示逻辑回归模型的训练,学习了机器学习回归模型的五大模块:数据、模型、损失函数、优化器和迭代训练过程。这五大模块将是后面学习的主线。

你可能感兴趣的:(PyTorch,pytorch,深度学习,人工智能)