pytorch 简介

pytorch 是目前世界上最流行的两个机器学习框架的其中之一,与 tensoflow 并峙双雄。它提供了很多方便的功能,例如根据损失自动微分计算应该怎样调整参数,提供了一系列的数学函数封装,还提供了一系列现成的模型,以及把模型组合起来进行训练的框架。pytorch 的前身是 torch,基于 lua,而 pytorch 基于 python,虽然它基于 python 但底层完全由 c++ 编写,支持自动并列化计算和使用 GPU 加速运算,所以它的性能非常好。

传统的机器学习有的会像前一节的例子中全部手写,或者利用 numpy 类库减少一部分工作量,也有人会利用 scikit-learn (基于 numpy) 类库封装好的各种经典算法。pytorch 与 tensorflow 和传统机器学习不一样的是,它们把重点放在了组建类似人脑的神经元网络 (Neural Network),所以能实现传统机器学习无法做到的非常复杂的判断,例如判断图片中的物体类型,自动驾驶等。不过,它们组建的神经元网络工作方式是不是真的和人脑类似仍然有很多争议,目前已经有人开始着手组建原理上更接近人脑的 GNN (Graph Neural Network) 网络,但仍未实用化,所以我们这个系列还是会着重讲解当前已经实用化并广泛应用在各个行业的网络模型。

学 pytorch 还是学 tensorflow 好?

对初学者来说一个很常见的问题是,学 pytorch 还是学 tensorflow 好?按目前的统计数据来说,公司更多使用 tensorflow,而研究人员更多使用 pytorch,pytorch 的增长速度非常快,有超越 tensorflow 的趋势。我的意见是学哪个都无所谓,如果你熟悉 pytorch,学 tensorflow 也就一两天的事情,反过来也一样,并且 pytorch 和 tensorflow 的项目可以互相移植,选一个觉得好学的就可以了。因为我觉得 pytorch 更好学 (封装非常直观,使用 Dynamic Graph 使得调试非常容易),所以这个系列会基于 pytorch 来讲。

Dynamic Graph 与 Static Graph

机器学习框架按运算的流程是否需要预先固定可以分为 Dynamic Graph 和 Static Graph,Dynamic Graph 不需要预先固定运算流程,而 Static Graph 需要。举例来说,对同一个公式 wx + b = y,Dynamic Graph 型的框架可以把 wx+b 分开写并且逐步计算,计算的过程中随时都可以用 print 等指令输出途中的结果,或者把途中的结果发送到其他地方记录起来;而 Static Graph 型的框架必须预先定好整个计算流程,你只能传入 w, x, b 给计算器,然后让计算器输出 y,中途计算的结果只能使用专门的调试器来查看。

一般的来说 Static Graph 性能会比 Dynamic Graph 好,Tensorflow (老版本) 使用的是 Static Graph,而 pytorch 使用的是 Dynamic Graph,但两者实际性能相差很小,因为消耗资源的大部分都是矩阵运算,使用批次训练可以很大程度减少它们的差距。顺带一提,Tensorflow 1.7 开始支持了 Dynamic Graph,并且在 2.0 默认开启,但大部分人在使用 Tensorflow 的时候还是会用 Static Graph。

  1. # Dynamic Graph 的印象,运算的每一步都可以插入自定义代码
  2. def forward(w, x, b):
  3. wx = w * x
  4. print(wx)
  5. y = wx + b
  6. print(y)
  7. return y
  8. forward(w, x, b)
  9. # Static Graph 的印象,需要预先编译整个计算流程
  10. forward = compile("wx+b")
  11. forward(w, x, b)

安装 pytorch

假设你已经安装了 python3,执行以下命令即可安装 pytorch:

  1. pip3 install pytorch

之后在 python 代码中使用 import torch 即可引用 pytorch 类库。

pytorch 的基本操作

接下来我们熟悉一下 pytorch 里面最基本的操作,pytorch 会用 torch.Tensor 类型来统一表现数值,向量 (一维数组) 或矩阵 (多维数组),模型的参数也会使用这个类型。(tensorflow 会根据用途分为好几个类型,这点 pytorch 更简洁明了)

torch.Tensor 类型可以使用 torch.tensor 函数构建,以下是一些简单的例子(运行在 python 的 REPL 中):

  1. # 引用 pytorch
  2. >>> import torch
  3. # 创建一个整数 tensor
  4. >>> torch.tensor(1)
  5. tensor(1)
  6. # 创建一个小数 tensor
  7. >>> torch.tensor(1.0)
  8. tensor(1.)
  9. # 单值 tensor 中的值可以用 item 函数取出
  10. >>> torch.tensor(1.0).item()
  11. 1.0
  12. # 使用一维数组创建一个向量 tensor
  13. >>> torch.tensor([1.0, 2.0, 3.0])
  14. tensor([1., 2., 3.])
  15. # 使用二维数组创建一个矩阵 tensor
  16. >>> torch.tensor([[1.0, 2.0, 3.0], [-1.0, -2.0, -3.0]])
  17. tensor([[ 1., 2., 3.],
  18. [-1., -2., -3.]])

tensor 对象的数值类型可以看它的 dtype 成员:

  1. >>> torch.tensor(1).dtype
  2. torch.int64
  3. >>> torch.tensor(1.0).dtype
  4. torch.float32
  5. >>> torch.tensor([1.0, 2.0, 3.0]).dtype
  6. torch.float32
  7. >>> torch.tensor([[1.0, 2.0, 3.0], [-1.0, -2.0, -3.0]]).dtype
  8. torch.float32

pytorch 支持整数类型 torch.uint8, torch.int8, torch.int16, torch.int32, torch.int64 ,浮点数类型 torch.float16, torch.float32, torch.float64,还有布尔值类型 torch.bool。类型后的数字代表它的位数 (bit 数),而 uint8 前面的 u 代表它是无符号数 (unsigned)。实际绝大部分场景都只会使用 torch.float32,虽然精度没有 torch.float64 高但它占用内存小并且运算速度快。注意一个 tensor 对象里面只能保存一种类型的数值,不能混合存放。

创建 tensor 对象时可以通过 dtype 参数强制指定类型:

  1. >>> torch.tensor(1, dtype=torch.int32)
  2. tensor(1, dtype=torch.int32)
  3. >>> torch.tensor([1.1, 2.9, 3.5], dtype=torch.int32)
  4. tensor([1, 2, 3], dtype=torch.int32)
  5. >>> torch.tensor(1, dtype=torch.int64)
  6. tensor(1)
  7. >>> torch.tensor(1, dtype=torch.float32)
  8. tensor(1.)
  9. >>> torch.tensor(1, dtype=torch.float64)
  10. tensor(1., dtype=torch.float64)
  11. >>> torch.tensor([1, 2, 3], dtype=torch.float64)
  12. tensor([1., 2., 3.], dtype=torch.float64)
  13. >>> torch.tensor([1, 2, 0], dtype=torch.bool)
  14. tensor([ True, True, False])

tensor 对象的形状可以看它的 shape 成员:

  1. # 整数 tensor 的 shape 为空
  2. >>> torch.tensor(1).shape
  3. torch.Size([])
  4. >>> torch.tensor(1.0).shape
  5. torch.Size([])
  6. # 数组 tensor 的 shape 只有一个值,代表数组的长度
  7. >>> torch.tensor([1.0]).shape
  8. torch.Size([1])
  9. >>> torch.tensor([1.0, 2.0, 3.0]).shape
  10. torch.Size([3])
  11. # 矩阵 tensor 的 shape 根据它的维度而定,每个值代表各个维度的大小,这个例子代表矩阵有 2 行 3 列
  12. >>> torch.tensor([[1.0, 2.0, 3.0], [-1.0, -2.0, -3.0]]).shape
  13. torch.Size([2, 3])

tensor 对象与数值,tensor 对象与 tensor 对象之间可以进行运算:

  1. >>> torch.tensor(1.0) * 2
  2. tensor(2.)
  3. >>> torch.tensor(1.0) * torch.tensor(2.0)
  4. tensor(2.)
  5. >>> torch.tensor(3.0) * torch.tensor(2.0)
  6. tensor(6.)

向量和矩阵还可以批量进行运算(内部会并列化运算):

  1. # 向量和数值之间的运算
  2. >>> torch.tensor([1.0, 2.0, 3.0])
  3. tensor([1., 2., 3.])
  4. >>> torch.tensor([1.0, 2.0, 3.0]) * 3
  5. tensor([3., 6., 9.])
  6. >>> torch.tensor([1.0, 2.0, 3.0]) * 3 - 1
  7. tensor([2., 5., 8.])
  8. # 矩阵和单值 tensor 对象之间的运算
  9. >>> torch.tensor([[1.0, 2.0, 3.0], [-1.0, -2.0, -3.0]])
  10. tensor([[ 1., 2., 3.],
  11. [-1., -2., -3.]])
  12. >>> torch.tensor([[1.0, 2.0, 3.0], [-1.0, -2.0, -3.0]]) / torch.tensor(2)
  13. tensor([[ 0.5000, 1.0000, 1.5000],
  14. [-0.5000, -1.0000, -1.5000]])
  15. # 矩阵和与矩阵最后一个维度相同长度向量之间的运算
  16. >>> torch.tensor([[1.0, 2.0, 3.0], [-1.0, -2.0, -3.0]]) * torch.tensor([1.0, 1.5, 2.0])
  17. tensor([[ 1., 3., 6.],
  18. [-1., -3., -6.]])

tensor 对象之间的运算一般都会生成一个新的 tensor 对象,如果你想避免生成新对象 (提高性能),可以使用 _ 结尾的函数,它们会修改原有的对象:

  1. # 生成新对象,原有对象不变,add 和 + 意义相同
  2. >>> a = torch.tensor([1,2,3])
  3. >>> b = torch.tensor([7,8,9])
  4. >>> a.add(b)
  5. tensor([ 8, 10, 12])
  6. >>> a
  7. tensor([1, 2, 3])
  8. # 在原有对象上执行操作,避免生成新对象
  9. >>> a.add_(b)
  10. tensor([ 8, 10, 12])
  11. >>> a
  12. tensor([ 8, 10, 12])

pytorch 还提供了一系列方便的函数求最大值,最小值,平均值,标准差等:

  1. >>> torch.tensor([1.0, 2.0, 3.0])
  2. tensor([1., 2., 3.])
  3. >>> torch.tensor([1.0, 2.0, 3.0]).min()
  4. tensor(1.)
  5. >>> torch.tensor([1.0, 2.0, 3.0]).max()
  6. tensor(3.)
  7. >>> torch.tensor([1.0, 2.0, 3.0]).mean()
  8. tensor(2.)
  9. >>> torch.tensor([1.0, 2.0, 3.0]).std()
  10. tensor(1.)

pytorch 还支持比较 tensor 对象来生成布尔值类型的 tensor:

  1. # tensor 对象与数值比较
  2. >>> torch.tensor([1.0, 2.0, 3.0]) > 1.0
  3. tensor([False, True, True])
  4. >>> torch.tensor([1.0, 2.0, 3.0]) <= 2.0
  5. tensor([ True, True, False])
  6. # tensor 对象与 tensor 对象比较
  7. >>> torch.tensor([1.0, 2.0, 3.0]) > torch.tensor([1.1, 1.9, 3.0])
  8. tensor([False, True, False])
  9. >>> torch.tensor([1.0, 2.0, 3.0]) <= torch.tensor([1.1, 1.9, 3.0])
  10. tensor([ True, False, True])

pytorch 还支持生成指定形状的 tensor 对象:

  1. # 生成 2 行 3 列的矩阵 tensor,值全部为 0
  2. >>> torch.zeros(2, 3)
  3. tensor([[0., 0., 0.],
  4. [0., 0., 0.]])
  5. # 生成 3 行 2 列的矩阵 tensor,值全部为 1
  6. torch.ones(3, 2)
  7. >>> torch.ones(2, 3)
  8. tensor([[1., 1., 1.],
  9. [1., 1., 1.]])
  10. # 生成 3 行 2 列的矩阵 tensor,值全部为 100
  11. >>> torch.full((3, 2), 100)
  12. tensor([[100., 100.],
  13. [100., 100.],
  14. [100., 100.]])
  15. # 生成 3 行 3 列的矩阵 tensor,值为范围 [0, 1) 的随机浮点数
  16. >>> torch.rand(3, 3)
  17. tensor([[0.4012, 0.2412, 0.1532],
  18. [0.1178, 0.2319, 0.4056],
  19. [0.7879, 0.8318, 0.7452]])
  20. # 生成 3 行 3 列的矩阵 tensor,值为范围 [1, 10] 的随机整数
  21. >>> (torch.rand(3, 3) * 10 + 1).long()
  22. tensor([[ 8, 1, 5],
  23. [ 8, 6, 5],
  24. [ 1, 6, 10]])
  25. # 和上面的写法效果一样
  26. >>> torch.randint(1, 11, (3, 3))
  27. tensor([[7, 1, 3],
  28. [7, 9, 8],
  29. [4, 7, 3]])

这里提到的操作只是常用的一部分,如果你想了解更多 tensor 对象支持的操作,可以参考以下文档:

pytorch 保存 tensor 使用的数据结构

为了减少内存占用与提升访问速度,pytorch 会使用一块连续的储存空间 (不管是在系统内存还是在 GPU 内存中) 保存 tensor,不管 tensor 是数值,向量还是矩阵。

我们可以使用 storage 查看 tensor 对象使用的储存空间:

  1. # 数值的储存空间长度是 1
  2. >>> torch.tensor(1).storage()
  3. 1
  4. [torch.LongStorage of size 1]
  5. # 向量的储存空间长度等于向量的长度
  6. >>> torch.tensor([1, 2, 3], dtype=torch.float32).storage()
  7. 1.0
  8. 2.0
  9. 3.0
  10. [torch.FloatStorage of size 3]
  11. # 矩阵的储存空间长度等于所有维度相乘的结果,这里是 2 行 3 列总共 6 个元素
  12. >>> torch.tensor([[1, 2, 3], [-1, -2, -3]], dtype=torch.float64).storage()
  13. 1.0
  14. 2.0
  15. 3.0
  16. -1.0
  17. -2.0
  18. -3.0
  19. [torch.DoubleStorage of size 6]

pytorch 会使用 stride 来确定一个 tensor 对象的维度:

  1. # 储存空间有 6 个元素
  2. >>> torch.tensor([[1, 2, 3], [-1, -2, -3]]).storage()
  3. 1
  4. 2
  5. 3
  6. -1
  7. -2
  8. -3
  9. [torch.LongStorage of size 6]
  10. # 第一个维度是 2,第二个维度是 3 (2 行 3 列)
  11. >>> torch.tensor([[1, 2, 3], [-1, -2, -3]]).shape
  12. torch.Size([2, 3])
  13. # stride 的意义是表示每个维度之间元素的距离
  14. # 第一个维度会按 3 个元素来切分 (6 个元素可以切分成 2 组),第二个维度会按 1 个元素来切分 (3 个元素)
  15. >>> torch.tensor([[1, 2, 3], [-1, -2, -3]])
  16. tensor([[ 1, 2, 3],
  17. [-1, -2, -3]])
  18. >>> torch.tensor([[1, 2, 3], [-1, -2, -3]]).stride()
  19. (3, 1)

pytorch 的一个很强大的地方是,通过 view 函数可以修改 tensor 对象的维度 (内部改变了 stride),但是不需要创建新的储存空间并复制元素:

  1. # 创建一个 2 行 3 列的矩阵
  2. >>> a = torch.tensor([[1, 2, 3], [-1, -2, -3]])
  3. >>> a
  4. tensor([[ 1, 2, 3],
  5. [-1, -2, -3]])
  6. >>> a.shape
  7. torch.Size([2, 3])
  8. >>> a.stride()
  9. (3, 1)
  10. # 把维度改为 3 行 2 列
  11. >>> b = a.view(3, 2)
  12. >>> b
  13. tensor([[ 1, 2],
  14. [ 3, -1],
  15. [-2, -3]])
  16. >>> b.shape
  17. torch.Size([3, 2])
  18. >>> b.stride()
  19. (2, 1)
  20. # 转换为向量
  21. >>> c = b.view(6)
  22. >>> c
  23. tensor([ 1, 2, 3, -1, -2, -3])
  24. >>> c.shape
  25. torch.Size([6])
  26. >>> c.stride()
  27. (1,)
  28. # 它们的储存空间是一样的
  29. >>> a.storage()
  30. 1
  31. 2
  32. 3
  33. -1
  34. -2
  35. -3
  36. [torch.LongStorage of size 6]
  37. >>> b.storage()
  38. 1
  39. 2
  40. 3
  41. -1
  42. -2
  43. -3
  44. [torch.LongStorage of size 6]
  45. >>> c.storage()
  46. 1
  47. 2
  48. 3
  49. -1
  50. -2
  51. -3
  52. [torch.LongStorage of size 6]

使用 stride 确定维度的另一个意义是它可以支持共用同一个空间实现转置 (Transpose) 操作:

  1. # 创建一个 2 行 3 列的矩阵
  2. >>> a = torch.tensor([[1, 2, 3], [-1, -2, -3]])
  3. >>> a
  4. tensor([[ 1, 2, 3],
  5. [-1, -2, -3]])
  6. >>> a.shape
  7. torch.Size([2, 3])
  8. >>> a.stride()
  9. (3, 1)
  10. # 使用转置操作交换维度 (行转列)
  11. >>> b = a.transpose(0, 1)
  12. >>> b
  13. tensor([[ 1, -1],
  14. [ 2, -2],
  15. [ 3, -3]])
  16. >>> b.shape
  17. torch.Size([3, 2])
  18. >>> b.stride()
  19. (1, 3)
  20. # 它们的储存空间是一样的
  21. >>> a.storage()
  22. 1
  23. 2
  24. 3
  25. -1
  26. -2
  27. -3
  28. [torch.LongStorage of size 6]
  29. >>> b.storage()
  30. 1
  31. 2
  32. 3
  33. -1
  34. -2
  35. -3
  36. [torch.LongStorage of size 6]

转置操作内部就是交换了指定维度在 stride 中对应的值,你可以根据前面的描述想想对象在转置后的矩阵中会如何划分。

现在再想想,如果把转置后的矩阵用 view 函数专为向量会变为什么?会变为 [1, -1, 2, -2, 3, -3] 吗?

实际上这样的操作会导致出错

写给程序员的机器学习入门 (二) - pytorch 与矩阵计算入门的更多相关文章

  1. 写给程序员的机器学习入门 (八 补充) - 使用 GPU 训练模型

    在之前的文章中我训练模型都是使用的 CPU,因为家中黄脸婆不允许我浪费钱买电脑.终于的,附近一个废品回收站的朋友转让给我一台破烂旧电脑,所以我现在可以体验使用 GPU 训练模型了

  2. 写给程序员的机器学习入门 (十) - 对象识别 Faster-RCNN - 识别人脸位置与是否戴口罩

    每次看到大数据人脸识别抓逃犯的新闻我都会感叹技术发展的太快了,国家治安水平也越来越好了

  3. 写给程序员的机器学习入门 (九) - 对象识别 RCNN 与 Fast-RCNN

    因为这几个月饭店生意恢复,加上研究 Faster-RCNN 用掉了很多时间,就没有更新博客了.这篇开始会介绍对象识别的模型与实现方法,首先会介绍最简单的 RCNN 与 Fast-RCNN 模型,下一篇 ...

  4. 写给程序员的机器学习入门 (十一) - 对象识别 YOLO - 识别人脸位置与是否戴口罩

    这篇将会介绍目前最流行的对象识别模型 YOLO,YOLO 的特征是快,识别速度非常快

  5. 写给程序员的机器学习入门 (五) - 递归模型 RNN,LSTM 与 GRU

    递归模型的应用场景 在前面的文章中我们看到的多层线性模型能处理的输入数量是固定的,如果一个模型能接收两个输入那么你就不能给它传一个或者三个.而有时候我们需要根据数量不一定的输入来预测输出,例如文本就是 ...

  6. 写给程序员的机器学习入门 (七) - 双向递归模型 (BRNN) - 根据上下文补全单词

    这一篇将会介绍什么是双向递归模型和如何使用双向递归模型实现根据上下文补全句子中的单词. 双向递归模型 到这里为止我们看到的例子都是按原有顺序把输入传给递归模型的,例如传递第一天股价会返回根据第一天股价 ...

  7. 写给程序员的机器学习入门 (八) - 卷积神经网络 (CNN) - 图片分类和验证码识别

    这一篇将会介绍卷积神经网络 (CNN),CNN 模型非常适合用来进行图片相关的学习,例如图片分类和验证码识别,也可以配合其他模型实现 OCR. 使用 Python 处理图片 在具体介绍 CNN 之前, ...

  8. 写给嵌入式程序员的循环冗余校验(CRC)算法入门引导

    写给嵌入式程序员的循环冗余校验(CRC)算法入门引导 http://blog.csdn.net/liyuanbhu/article/details/7882789 前言 CRC校验(循环冗余校验)是数 ...

  9. GitHub这么火,程序员你不学学吗? 超简单入门教程 【转载】

    本GitHub教程旨在能够帮助大家快速入门学习使用GitHub. 本文章由做全栈攻城狮-写代码也要读书,爱全栈,更爱生活.原创.如有转载,请注明出处. GitHub是什么? GitHub首先是个分布式 ...

随机推荐

  1. 【colab pytorch】使用tensorboardcolab可视化

    import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from ...

  2. 安卓 打飞机 app 开发 第一篇

    先上效果图 其实,当时刚买 htc G8 的时候(那时北京的房价还是6千一平),安卓2.1 ,2.3 的时候就已经有安卓方面的开发的兴趣,但后来就没有弄过... today 突然想起来,手机上连个游戏 ...

  3. JavaFX之FXML+CSS创建窗体以及透明窗体添加阴影

    前言 开通博客园有一段日子了,一直没空也没想好该写点什么.最近正好在做一个桌面程序,初次接触JavaFX,体验下来确实比swing好用不少.索性便记记学习笔记吧,虽然FX好像挺没存在感,没人用的感觉. ...

  4. 【原创】(求锤得锤的故事)Redis锁从面试连环炮聊到神仙打架。

    这是why技术的第38篇原创文章 又到了一周一次的分享时间啦,老规矩,还是先荒腔走板的聊聊生活. 有上面的图是读大学的时候,一次自行车骑行途中队友抓拍的我的照片.拍照的地方,名字叫做牛背山,一个名字很 ...

  5. 求你了,别再说Java对象都是在堆内存上分配空间的了!

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...

  6. MySQL记录操作(增删改)

    概览 MySQL数据操作: DML 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 使用INSERT实现数据的插入 UPDATE实现数据的更新 使用DELETE实现数据 ...

  7. 解决使用 el-table 中使用多选框 Checkbox 不刷新问题

    问题 在 el-table 中使用 Checkbox 仅作为展示时,v-model 双向绑定就变得不那么适用了,这时候我们会使用 checked 属性来代替v-model. 问题来了当使用 filte ...

  8. CUDA Pro Tip: Write Flexible Kernels with Grid-Stride Loops

    https://devblogs.nvidia.com/cuda-pro-tip-write-flexible-kernels-grid-stride-loops/ One of the most c ...

  9. 图数据库 Nebula Graph TTL 特性

    导读 身处在现在这个大数据时代,我们处理的数据量需以 TB.PB, 甚至 EB 来计算,怎么处理庞大的数据集是从事数据库领域人员的共同问题.解决这个问题的核心在于,数据库中存储的数据是否都是有效的.有 ...

  10. 【HDU2883】kebab——最大流

    题目链接 把"时间粒子"作为最大流的计算结果 设置超级源点为 0 顾客点范围为 1 - 204 时间点 205 - 610 超级汇点 615 超级源点与所有顾客连线,容量为需求的烤 ...