zoukankan      html  css  js  c++  java
  • 计算图,张量的操作与线性回归

    2 计算图,张量的操作与线性回归


    目录

    计算图

    ​ 计算图是用来描述运算的有向无环图。计算图有两个主要元素:结点(Node)和边(Edge)。结点代表数据,如向量,矩阵,张量;边表示运算,如加减乘除卷积等。

    ​ 比如用计算图表示(y = (x + w) * (w + 1)):

    代码表示为:

    x = torch.tensor([1.])
    w = torch.tensor([2.])
    a = x.add(w)
    b = w.add(1.)
    y = a.mul(b)
    
    x.is_leaf()  # True
    w.is_leaf()  # True
    b.is_leaf()  # False
    

    ​ 其中叶子结点没有grad_func,非叶子节点没有grad(因为都可以用叶子节点的grad表示,计算完后就会被释放)。当然,也就意味着,我们可以用函数retain_grad()保存下来非叶子结点的grad

    ​ 根据搭建与执行图的区别,计算图又可以分为动态图和静态图。

    ​ 对于使用者来说,两种形式的计算图有着非常大的区别,同时静态图和动态图都有他们各自的优点,比如动态图比较方便debug,使用者能够用任何他们喜欢的方式进行debug,同时非常直观,而静态图是通过先定义后运行的方式,之后再次运行的时候就不再需要重新构建计算图,所以速度会比动态图更快。

    img

    ​ 接下来看一下TensorFlow1.x时代的动态图和PyTorch的静态图定义一个循环语句的差别:

    TensorFlow:

    import tensorflow as tf
    first_counter = tf.constant(0) # 定义变量
    second_counter = tf.constant(10) # 定义变量
    
    def cond(first_counter, second_counter, *args): # 定义条件
        return first_counter < second_counter
    def body(first_counter, second_counter): # 定义条件
        first_counter = tf.add(first_counter, 2)
        second_counter = tf.add(second_counter, 1)
        return first_counter, second_counter
        
    c1, c2 = tf.while_loop(cond, body, [first_counter, second_counter]) # 定义循环
    
    with tf.Session() as sess: # 建立会话执行计算图
        counter_1_res, counter_2_res = sess.run([c1, c2])
    
    print(first_counter)
    print(second_counter)
    

    PyTorch:

    import torch
    first_counter = torch.Tensor([0])
    second_counter = torch.Tensor([10])
     
    while (first_counter < second_counter)[0]:
        first_counter += 2
        second_counter += 1
     
    print(first_counter)
    print(second_counter)
    

    ​ 可以看到 TensorFlow 需要将整个图构建成静态的,换句话说,每次运行的时候图都是一样的,是不能够改变的,所以不能直接使用 Python 的 while 循环语句,需要使用辅助函数 tf.while_loop 写成 TensorFlow 内部的形式。

    ​ 而PyTorch 的写法跟 Python 的写法是完全一致的,没有任何额外的学习成本。

    张量的操作:拼接、切分、索引和变换

    张量的拼接

    • torch.cat(tensors, dim=0):将张量按维度dim进行拼接
    x = torch.randn(2, 3)
    x
    # tensor([[-0.5741, -0.6404,  1.2253],
    #        [ 0.1389,  1.1403, -0.5611]])
    torch.cat((x, x, x), 0).shape
    # torch.Size([6, 3])
    torch.cat((x, x, x), 1).shape
    # torch.Size([2, 9])
    
    • torch.stack(tensors, dim=0):沿着一个新的维度对张量进行连接。如果不是新的维度,则插入到该维度(原先维度一次后移)
    x = torch.randn(2, 3)
    x
    # tensor([[-0.5741, -0.6404,  1.2253],
    #        [ 0.1389,  1.1403, -0.5611]])
    
    # 新维度拼接
    torch.stack((x, x, x), 2).shape
    # torch.Size([2, 3, 3])
    
    # 插入到第0维度
    torch.stack((x, x, x), 0).shape
    # torch.Size([3, 2, 3])
    

    张量的拆分

    • torch.chunk(input, chunks, dim=0):在给定维度上对张量进行平均切分,返回一个张量列表。注意,如果不能整除的话,最后一份张量小于其他张量。
    x = torch.ones((2, 5))
    tensors_list = torch.chunk(x, 1, 2)
    for t in tensors_list:
        print(t.shape)
    # torch.Size([2, 3])
    # torch.Size([2, 2])
    
    • torch.split(tensor, split_size_or_sections, dim=0):将张量按维度进行切分,返回一个张量列表。与torch.chunk不同的是第二个参数,第二个参数如果为整数,则表示切分后该维度每一维的长度不能整除时最后一维减小); 如果为list,则按list指定的维度进行切分
    x = torch.ones((2, 5, 3))
    # 切成指定大小
    tensor_list1 = torch.split(x, 2, 1)
    for i in tensor_list1:
        print(i.shape)
    # torch.Size([2, 2, 3])
    # torch.Size([2, 2, 3])
    # torch.Size([2, 1, 3])
    
    # 切成指定维度
    tensor_list2 = torch.split(x, [2, 1, 2], 1)
    for i in tensor_list2:
        print(i.shape)
    # torch.Size([2, 2, 3])
    # torch.Size([2, 1, 3])
    # torch.Size([2, 2, 3])
    

    ​ 注意:list的维度之和必须等于该维度长度,否则抛异常

    • torch.index_select(input, dim, index, out=None)在维度dim上,按index索引数据,返回一个依index索引拼接起来的张量。
    t = torch.randint(0, 9, size = (3, 3))
    indices = torch.tensor([0, 2], dtype = torch.long)
    t_selected = torch.index_select(t, dim = 1, index = indices)
    print(t, t_selected, sep = "
    ")
    
    # tensor([[5, 8, 0], 
    #         [5, 0, 0], 
    #         [8, 7, 6]])
    # tensor([[5, 0],    
    #         [5, 0],    
    #         [8, 6]])
    

    注意:indices必须为torch.long

    • torch.masked_select(input, mask, out=None)按mask中的True进行索引,返回一个一维张量
    t = torch.randint(0, 9, size = (3, 3))
    mask = t.ge(5) # ge greater than or equal, le, lt, gt
    t_selected = torch.masked_select(t, mask)
    print(t, t_selected, sep = "
    ")
    
    # tensor([[4, 5, 0],
    #         [5, 7, 1],
    #         [2, 5, 8]])
    # tensor([5, 5, 7, 5, 8])
    

    ​ 学过MIPS系统的应该很懂ge, le, lt, gt这些缩写的意思,不多做说明。

    ​ 此外,还可以通过Python切片的方式对tensor进行索引。

    x = torch.randn(2, 5)
    print(x, x[:][0::2], sep = "
    ")
    
    # tensor([[ 0.6614,  0.2669,  0.0617,  0.6213, -0.4519],
    #         [-0.1661, -1.5228,  0.3817, -1.0276, -0.5631]])
    # tensor([[ 0.6614,  0.2669,  0.0617,  0.6213, -0.4519]])
    

    张量的变换

    • torch.reshape(input, shape):变换张量的形状,返回一个新张量。注意:如果张量在内存中是连续的,新张量与input共享数据内存
    x = torch.randperm(8)
    x_reshape = torch.reshape(x, (2, 4))
    print(x, x.shape, x_reshape.shape, sep = "
    ")
    
    # tensor([5, 4, 2, 6, 7, 3, 1, 0])
    # torch.Size([8])
    # torch.Size([2, 4])
    
    x[7] = 1
    print(x, x_reshape, sep = "
    ")
    
    # tensor([5, 4, 2, 6, 7, 3, 1, 1])
    # tensor([[5, 4, 2, 6],
    #         [7, 3, 1, 1]])
    

    ​ 注意:reshape大小计算后必须与原张量相同,或者写作-1,代表缺省,可通过其他维度计算出来。

    • torch.transpose(input, dim0, dim1):对张量的某两个维度进行转置。
    x = torch.zeros((3, 4, 5))
    torch.transpose(x, 0, 1).size()  # 图像预处理 c*h*w => h*w*c
    
    # torch.Size([4, 3, 5])
    
    • torch.t(input):对二维张量进行转置。等效于torch.transpose(input, 0, 1)
    a = torch.zeros((3, 4))
    torch.t(a).size()
    
    # torch.Size([4, 3])
    
    • torch.squeeze(input, dim=None, out=None)压缩长度为一的维度,其中dim为None时,移除所有长度为1的轴;若指定维度,当且仅当该轴的长度为1时,才会被移除。
    x = torch.zeros(2,1,2,1,2)
    x.size()
    # torch.Size([2, 1, 2, 1, 2])
    
    y = torch.squeeze(x)
    y.size()
    # torch.Size([2, 2, 2])
    
    y = torch.squeeze(x, 0)
    y.size()
    # torch.Size([2, 1, 2, 1, 2])
    
    y = torch.squeeze(x, 1)
    y.size()
    # torch.Size([2, 2, 1, 2])
    
    • torch.unsqueeze(input, dim, out=None)在指定位置依据生成方式插入坐标轴
    x = torch.zeros(2,1,2,1,2)
    x.size()
    torch.unsqueeze(x, 0).size()
    # torch.Size([1, 2, 1, 2, 1, 2])
    

    张量的数学运算

    四则运算

    torch.add(input, other, *, alpha=1, out=None) # 相加
    torch.sub(input, other, out=None) # 相减
    torch.mul(input, other, out=None) # 相乘
    torch.div(input, other, out=None) # 相除
    

    torch.add()运算时遵循:

    [out = input + alpha imes other ]

    其余的向量化运算。

    还有:

    torch.addcdiv(input, tensor1, tensor2, *, value=1, out=None)
    torch.addcdiv(input, tensor1, tensor2, *, value=1, out=None)
    

    前者遵循:

    [out = input + value * frac{tensor1}{tensor2} ]

    后者同理。

    对数,指数, 幂函数运算

    torch.exp(input, out=None)  # e^input
    torch.pow(input, exponent, out=None) #幂函数
    
    # 对数
    torch.log(input, out=None)
    torch.log1p(input, out=None)
    torch.log2(input, out=None)
    torch.log10(input, out=None)
    

    ​ 显然,这些运算都遵循向量式运算。

    几个对数函数的运算规则分别是:

    [egin{align} out &= log_{e}input ag{1} \ out &= log_{e}(1 + input) ag{2} \ out &= log_{2}input ag{3} \ out &= log_{10}input ag{4} end{align} ]

    三角函数

    torch.sin(input, out=None):正弦
    torch.cos(input, out=None):余弦
    torch.tan(input, out=None):正切
    torhc.asin(input, out=None)
    torch.cosh(input, out=None)
    torch.atan2(input, out=None)
    

    ​ 用法就是普通三角函数的向量运算。

    其他

    其他数学运算
    • torch.abs(input, out=None):返回张量的绝对值。
    • torch.ceil(input, out=None):对张量向下取整。
    • torch.floor(input, out=None):对张量向上取整。
    • torch.floor_divide(input, other, out=None):张量相除后向下取整。
    • torch.fmod(input, other, out=None):对张量取余。
    • torch.neg(input, out=None):取张量的相反数。
    • torch.round(input, out=None):四舍五入
    • torch.sigmoid(input, out=None):对张量进行 sigmoid 计算。
    • torch.sqrt(input, out=None):对张量取平方根。
    • torch.square(input, out=None):对张量平方。
    • torch.sort(input, dim=-1, descending=False, out=None):返回张量排序后的结果
    降维函数
    • torch.argmax(input, dim, keepdim=False):返回张量内最大元素的索引。
    • torch.argmin(input, dim, keepdim=False, out=None):返回张量内最小元素的索引。
    • torch.mean(input, dim, keepdim=False, out=None):返回张量内元素的平均数。
    • torch.median(input, dim=-1, keepdim=False, out=None):返回张量内元素的中位数。
    • torch.prod(input, dim, keepdim=False, dtype=None):返回张量内元素的连乘积。
    • torch.std(input, dim, unbiased=True, keepdim=False, out=None):返回张量内元素的标准差。
    • torch.sum(input, dim, keepdim=False, dtype=None):返回张量内元素的和。
    • torch.var(input, dim, keepdim=False, unbiased=True, out=None):返回张量内元素的方差。
      如果上述函数中的 dim 变量没有显式赋值,则对整个张量进行计算,返回一个值;若 dim 被显式赋值,则对该 dim 内的数据进行计算。
    比较函数
    • torch.eq(input, other, out=None):比较张量中元素是否相等。
    • torch.equal(input, other):比较两个张量是否相同。
    • torch.ge(input, other, out=None):比较第一个张量内的元素是否小于等于第二个张量内的对应元素。
    • torch.gt(input, other, out=None):比较第一个张量内的元素是否小于第二个张量内的对应元素。
    • torch.isnan(input, out=None):张量内的元素是否为非数值元素
    • torch.le(input, other, out=None):比较第一个张量内的元素是否大于等于第二个张量内的对应元素。
    • torch.lt(input, other, out=None):比较第一个张量内的元素是否大于第二个张量内的对应元素。
    • torch.max(input, dim, keepdim=False, out=None):返回张量指定轴上的最大元素。
    • torch.min(input, dim, keepdim=False, out=None):返回张量指定轴上的最小元素。
    • torch.ne(input, other, out=None):比较张量间的元素是否不相同。

    线性回归

    ​ 接下来我们用之前学到的东西来做一个线性回归。即用线性关系去拟合一些离散的点。

    求解步骤

    1. 确定模型 $ y = w*x + b$,目标即求解w和b

    2. 选则均方差损失函数

      [loss = frac{1}{m}sum_{i=1}^{m}(y_{i} - hat{y_{i}})^2 ]

    3. 求解梯度并更新w, b

      [egin{align} w &= w - LR * w.grad \ b &= b - LR * b.grad end{align} ]

    代码

    import torch
    import matplotlib.pyplot as plt
    torch.manual_seed(10)
    
    lr = 0.05  # 学习率
    
    # 创建训练数据
    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(1600):
    
        # 前向传播
        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)
    
        # 清零张量的梯度
        w.grad.zero_()
        b.grad.zero_()
    
        # 绘图
        if iteration % 40 == 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: {}
    w: {} b: {}".format(iteration, w.data.numpy(), b.data.numpy()))
            plt.pause(0.5)
    
            if loss.data.numpy() < 1e-3:
                break
    
    

    结果

    参考

    CSDN博客:[DL]PyTorch 学习总结(2)

    Github:PyTorch_Tutorial

  • 相关阅读:
    WinForm多线程+委托防止界面假死
    网页制作知识库
    HTML Agility Pack:簡單好用的快速 HTML Parser
    .NET 4.0 和 .NET 4.0 Client Profile 区别
    使用OPCNetAPI连接OPCServer
    Win7系统删除微软拼音
    Unity3D脚本18:可视化辅助设置类 Gizmos
    mysql 索引
    重启oracle方法一二三
    php7 安装扩展
  • 原文地址:https://www.cnblogs.com/hwahe/p/14272656.html
Copyright © 2011-2022 走看看