通过范例学习 PyTorch

本博文通过几个独立的例子介绍了 PyTorch 的基础概念。

其核心,PyTorch 提供了两个主要的特征:

  • 一个 n-维张量(n-dimensional Tensor),类似 NumPy 但是可以运行在 GPU 设备上
  • 构建和训练神经网络,可自动求微分

我们将使用三阶多项式去拟合 y=sin(x) 的问题作为我们的例子。神经网络将会有 4 个参数,并且将使用梯度下降通过最小化(minimizing)网络输出和真实输出的欧氏距离(Euclidean distance)去拟合随机数据。

Tensors

热身:NumPy

在介绍 PyTorch 之前,我们首先使用 NumPy 实现一个神经网络。

NumPy 提供了一种 n-维数组(n-dimensional)数组对象,和许多操纵这些数组的函数。NumPy 对于科学计算是一个充满活力的框架;它不需要了解任何关于计算图(computation graphs)、深度学习或梯度。但是,我们可以轻松地使用 NumPy 操作手动实现网络的前向传播和反向传播,拟合一个三阶多项式到 sine 函数。

  1. import numpy as np
  2. import math
  3. # 创建随机输入和输出的数据
  4. x = np.linspace(-math.pi, math.pi, 2000)
  5. y = np.sin(x)
  6. # 随机初始化权重(weight)
  7. a = np.random.randn()
  8. b = np.random.randn()
  9. c = np.random.randn()
  10. d = np.random.randn()
  11. learning_rate = 1e-6
  12. for t in range(2000):
  13. # 前向传播:计算预测的 y
  14. # y = a + bx + cx^2 + dx^3
  15. y_pred = a + b * c + c * x ** 2 + d * x ** 3
  16. # 计算打印损失值(loss)
  17. loss = np.square(y_pred - y).sum()
  18. if t % 100 == 99:
  19. print(t, loss)
  20. # 反向传播计算 a, b, c, d 关于 loss 的梯度
  21. grad_y_pred = 2.0 * (y_pred - y)
  22. grad_a = grad_y_pred.sum()
  23. grad_b = (grad_y_pred * x).sum()
  24. grad_c = (grad_y_pred * x ** 2).sum()
  25. grad_d = (grad_y_pred * x ** 3).sum()
  26. # 更新权重参数(weight)
  27. a -= learning_rate * grad_a
  28. b -= learning_rate * grad_b
  29. c -= learning_rate * grad_c
  30. d -= learning_rate * grad_d
  31. print(f'Result: y = {a} + {b} x + {c} x^2 + {d} x^3')
  1. 99 880.579689608281
  2. 199 854.0555627713447
  3. 299 835.0649579500803
  4. 399 821.369172352883
  5. 499 811.4200797717988
  6. 599 804.1424592009078
  7. 699 798.7835679283971
  8. 799 794.8123966679943
  9. 899 791.8516135436988
  10. 999 789.6312092332105
  11. 1099 787.9566727255892
  12. 1199 786.6869765834631
  13. 1299 785.7192368208233
  14. 1399 784.9779487653068
  15. 1499 784.4073829648856
  16. 1599 783.9661786053822
  17. 1699 783.6234753797376
  18. 1799 783.3561293941161
  19. 1899 783.146697690572
  20. 1999 782.9819710274254
  21. Result: y = -0.05099192206877935 + 5.075189949472816 x + 0.004669889031269278 x^2 + 0.028076619049389115 x^3

PyTorch: Tensors

尽管 NumPy 是一个非常棒的框架,但是它不可以利用 GPU 去加速数值计算。对于现代深度神经网络,GPU 通过提供了 50倍或以上 的加速,不幸地是 NumPy 对于深度学习是还不够的。

这里我们介绍了 PyTorch 最基础的概念:Tensor 。一个 PyTorch Tensor 从概念上与 NumPy 数组是完全相同的:Tensor 是一个 n 维数组(n-dimensional array),并且 PyTorch 提供了许多处理这些 Tensor 的函数。幕后,Tensor 记录了一个计算图(computation graph)和梯度,并且这些对于科学计算也是一个非常实用并充满活力的工具。

不像 NumPy,PyTorch Tensor 可以利用 GPU 加速数值计算。为了将 PyTorch Tensor 运行在 GPU 上,你只需要简单地指定正确的设备。

这里我们使用 PyTorch Tensor 去拟合一个三阶多项式到 sine 函数。就像上面 NumPy 的例子,我们需要手动实现网络的前向传播和反向传播。

  1. import torch
  2. import math
  3. dtype = torch.float
  4. device = torch.device("cpu")
  5. # 下面这条注释,可以使用 GPU
  6. # device = torch.device("cuda:0")
  7. # 创建随机输入输出
  8. x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
  9. y = torch.sin(x)
  10. # 随机初始化权重(weight)
  11. a = torch.randn((), device=device, dtype=dtype)
  12. b = torch.randn((), device=device, dtype=dtype)
  13. c = torch.randn((), device=device, dtype=dtype)
  14. d = torch.randn((), device=device, dtype=dtype)
  15. learnign_rate = 1e-6
  16. for t in range(2000):
  17. # 前向传播:计算预测的 y
  18. y_pred = a + b * c + c * x ** 2 + d * x ** 3
  19. # 计算和输出损失值(loss)
  20. loss = (y_pred - y).pow(2).sum().item()
  21. if t % 100 == 99:
  22. print(t, loss)
  23. # 反向传播计算 a, b, c, d 关于损失值(loss)的梯度
  24. grad_y_pred = 2.0 * (y_pred - y)
  25. grad_a = grad_y_pred.sum()
  26. grad_b = (grad_y_pred * x).sum()
  27. grad_c = (grad_y_pred * x ** 2).sum()
  28. grad_d = (grad_y_pred * x ** 3).sum()
  29. # 使用梯度下降更新权重(weight)
  30. a -= learnign_rate * grad_a
  31. b -= learnign_rate * grad_b
  32. c -= learnign_rate * grad_c
  33. d -= learnign_rate * grad_d
  34. print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')
  1. 99 883.4296875
  2. 199 846.044921875
  3. 299 823.0543823242188
  4. 399 808.742919921875
  5. 499 799.6961059570312
  6. 599 793.8943481445312
  7. 699 790.1231079101562
  8. 799 787.6404418945312
  9. 899 785.9865112304688
  10. 999 784.872314453125
  11. 1099 784.113525390625
  12. 1199 783.5916748046875
  13. 1299 783.2293090820312
  14. 1399 782.9754028320312
  15. 1499 782.7958984375
  16. 1599 782.6681518554688
  17. 1699 782.5762939453125
  18. 1799 782.509765625
  19. 1899 782.4613647460938
  20. 1999 782.4259033203125
  21. Result: y = 0.015941178426146507 + 2.6255147457122803 x + -0.0018874391680583358 x^2 + 0.028076613321900368 x^3

自动求导

PyTorch: Tensor and autograd

在上面的例子中,我们必须手动实现神经网络的前向传播和反向传播。

对于一个小的两层网络手动实现反向传播并没有什么大不了的,但是对于更大更复杂的网络是一件非常可怕的事情。

幸亏的是,我们可以使用 自动微分(automatic differentiation 自动化神经网络的反向传播的计算。在 PyTorch 的 autograd 包正好提供了这个功能。当使用 autograd 时,神经网络的前向传播将定义一个 计算图(Computational graph ,图中的结点(nodes)将会是一个 Tensor,每条边(edges)将会是从一个输入 Tensor 产生输出 Tensor 的函数。反向传播通过计算图让我们轻松地计算梯度。

这听起来有些复杂,但是实际上是相当简单的。每一个 Tensor 代表了计算图中的结点。如果 x 是一个 Tensor,它就有 x.requires_grad=True 然后 x.grad 就是另一个张量,其持有 x 关于某个标量值的梯度。

这里我们使用 PyTorch Tensor 和 autograd 实现使用一个三阶多项式去拟合 sine 曲线的例子;现在,我们不再需要手动地实现网络的反向传播。

  1. import torch
  2. import math
  3. dtype = torch.float
  4. device = torch.device("cpu")
  5. # 下面这条注释,可以使用 GPU
  6. # device = torch.device("cuda:0")
  7. # 创建 Tensor
  8. # 默认情况下,requires_grad=False 表示我们不需要计算关于这些 Tensor 的梯度
  9. x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
  10. y = torch.sin(x)
  11. # 创建随机权重(weight) Tensor
  12. # 对于一个三阶多项式,我们需要 4 个参数
  13. # 设置 requires_grad=True 表明我们在反向传播的时候想要计算关于这些 Tensor 的梯度
  14. a = torch.randn((), device=device, dtype=dtype, requires_grad=True)
  15. b = torch.randn((), device=device, dtype=dtype, requires_grad=True)
  16. c = torch.randn((), device=device, dtype=dtype, requires_grad=True)
  17. d = torch.randn((), device=device, dtype=dtype, requires_grad=True)
  18. learnign_rate = 1e-6
  19. for t in range(2000):
  20. # 使用 Tensor 上的运算前向传播计算预测的 y
  21. y_pred = a + b * c + c * x ** 2 + d * x ** 3
  22. # 使用 Tensor 上的操作计算并打印损失值(loss)
  23. # 现在,loss 是一个 Tensor,shape 是 (1,)
  24. # loss.item() 得到 loss 持有的标量值
  25. loss = (y_pred - y).pow(2).sum()
  26. if t % 100 == 99:
  27. print(t, loss.item())
  28. # 使用 autograd 计算反向传播。下面这个调用将会计算 loss 关于所有 requires_grad=True 的 Tensor 的梯度。
  29. # 在此之后,调用 a.grad, b.grad, c.grad, d.grad 将得到 a, b, c, d 关于 loss 的梯度
  30. loss.backward()
  31. # 使用梯度下降手动更新权重。使用 torch.no_grad() 包起来。
  32. # 因为这些权重(weight)都有 requires_grad=True 但是在 autograd 中,我们不需要跟踪这些操作。
  33. with torch.no_grad():
  34. a -= learning_rate * a.grad
  35. b -= learning_rate * b.grad
  36. c -= learning_rate * c.grad
  37. d -= learning_rate * d.grad
  38. # 更新完参数之后,需要手动地将这些梯度清零
  39. a.grad = None
  40. b.grad = None
  41. c.grad = None
  42. d.grad = None
  43. print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')
  1. 99 943.6873168945312
  2. 199 903.8673095703125
  3. 299 873.939453125
  4. 399 851.4177856445312
  5. 499 834.4534301757812
  6. 599 821.6660766601562
  7. 699 812.0220336914062
  8. 799 804.74560546875
  9. 899 799.2537841796875
  10. 999 795.108154296875
  11. 1099 791.9780883789062
  12. 1199 789.6143798828125
  13. 1299 787.8292846679688
  14. 1399 786.4810791015625
  15. 1499 785.4628295898438
  16. 1599 784.693603515625
  17. 1699 784.1126708984375
  18. 1799 783.6737060546875
  19. 1899 783.34228515625
  20. 1999 783.091796875
  21. Result: y = 0.032735299319028854 + 0.6361034512519836 x + -0.005412467289716005 x^2 + 0.028076613321900368 x^3

PyTorchL 定义一个新的 autograd 函数

在底层,每一个原始自动求导(autograd)运算符实际上是两个操作在 Tensor 上的函数。前向传播(forward 函数计算出从输入 Tensor 到输出 Tensor。反向传播(backward 函数收到输出 Tensor 关于某个标量值的梯度,并且计算输入 Tensor 关于那些相同标量值的梯度。

在 PyTorch 中我们可以轻松地定义我们自己的自动求导运算符,一个 torch.autograd.Function 的子类并且实现 forwardbackward 函数。然后我们可以使用我们新定义的自动求导运算符,构建一个类实例(instance)然后像函数样调用它,传入输入数据的 Tensor。

在这个例子中,我们定义我们的模型为 \(y=a+b P_3(c+dx)\) 而不是 \(y=a+bx+cx^2+dx^3\),其中 \(P_3(x)=\frac{1}{2}\left(5x^3-3x\right)\) 是一个 3 次(degreeLegendre 多项式。对于计算 \(P_3\) 的前向传播和反向传播,我们写下自定义的自动求导函数,并用它实现我们的模型。

  1. import torch
  2. import math
  3. class LegendrePolynomial3(torch.autograd.Function):
  4. """
  5. 我们可以通过继承 torch.autograd.Function 实现我们自定义自动求导函数,
  6. 并实现操作在 Tensor 上的前向传播和反向传播。
  7. """
  8. @staticmethod
  9. def forward(ctx, input):
  10. """
  11. 在前向传播中,我们接受一个包含输入的 Tensor 并返回包含输出的 Tensor。
  12. ctx 是一个上下文(context)对象,用来存放反向传播计算时用到的信息。
  13. 你可以使用 ctx.save_for_backward 方法缓存任意对象以供反向传播使用。
  14. """
  15. ctx.save_for_backward(input)
  16. return 0.5 * (5 * input ** 3 - 3 * input)
  17. @staticmethod
  18. def backward(ctx, grad_output):
  19. """
  20. 在反向传播中,我们接收一个张量,其持有 loss 关于输出的梯度,
  21. 并且我们需要计算 loss 关于输入的梯度。
  22. """
  23. input, = ctx.saved_tensors
  24. return grad_output * 1.5 * (5 * input ** 2 - 1)
  25. dtype = torch.float
  26. device = torch.device("cpu")
  27. # device = torch.device("cuda:0") # 取消注释可以使用 GPU
  28. # 创建输入输出 Tensor。
  29. # 默认情况下,requires_grad=False 表明我们在反向传播时不需要计算这些 Tensor 的梯度。
  30. x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
  31. y = torch.sin(x)
  32. # 创建随机权重(weight)Tensor。在这个例子,我们需要 4 个权重:
  33. # y = a + b * P3(c + d * x) 为保证收敛,这些权重需要被初始化成离正确结果不太远。
  34. # 设置 requires_grad=True 表明我们在反向传播期间想要计算关于这些 Tensor 的梯度。
  35. a = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
  36. b = torch.full((), -1.0, device=device, dtype=dtype, requires_grad=True)
  37. c = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
  38. d = torch.full((), 0.3, device=device, dtype=dtype, requires_grad=True)
  39. learning_rate = 5e-6
  40. for t in range(2000):
  41. # 为了应用(apply)我们的函数,我们使用 Function.apply
  42. # 并将这个函数取个别名 P3
  43. P3 = LegendrePolynomial3.apply
  44. # 前向传播:使用操作(operations)计算预测的 y。
  45. # 我们使用我们自定义的 autograd 操作计算 P3。
  46. y_pred = a + b * P3(c + d * x)
  47. # 计算并输出损失值(loss)
  48. loss = (y_pred - y).pow(2).sum()
  49. if t % 100 == 99:
  50. print(t, loss.item())
  51. # 使用 autograd 计算反向传播。
  52. loss.backward()
  53. # 使用梯度下降更新权重
  54. with torch.no_grad():
  55. a -= learning_rate * a.grad
  56. b -= learning_rate * b.grad
  57. c -= learning_rate * c.grad
  58. d -= learning_rate * d.grad
  59. # 更新完权重后,手动清零梯度
  60. a.grad = None
  61. b.grad = None
  62. c.grad = None
  63. d.grad = None
  64. print(f'Result: y = {a.item()} + {b.item()} * P3({c.item()} + {d.item()} x)')
  1. 99 209.95834350585938
  2. 199 144.66018676757812
  3. 299 100.70249938964844
  4. 399 71.03519439697266
  5. 499 50.97850799560547
  6. 599 37.403133392333984
  7. 699 28.206867218017578
  8. 799 21.973188400268555
  9. 899 17.7457275390625
  10. 999 14.877889633178711
  11. 1099 12.931766510009766
  12. 1199 11.610918045043945
  13. 1299 10.714258193969727
  14. 1399 10.10548210144043
  15. 1499 9.692106246948242
  16. 1599 9.411375045776367
  17. 1699 9.220745086669922
  18. 1799 9.091285705566406
  19. 1899 9.003361701965332
  20. 1999 8.943639755249023
  21. Result: y = -5.423830273798558e-09 + -2.208526849746704 * P3(1.3320399228078372e-09 + 0.2554861009120941 x)

nn module

PyTorch: nn

对于定义一个复杂的运算符和自动微分,计算图和 autograd 是一个非常强大的范例(paradigm),但是对于一个很大的神经网络来说,原生的(raw)autograd 就有一点低级(low-level)了。

当构建神经网络的时候,我们经常考虑将这些计算安排整理到一个 层(layers 中,在学习期间,一些 可学习参数(learnable parameters 将会被优化。

在 TensorFlow 中,像 Keras, TensorFlow-SlimTFLearn 包在原生计算图之上提供了更高阶的抽象,这非常有益于构建神经网络。

在 PyTorch 中,nn 包服务于相同的目的。nn 包定义了一套 Modules,大致上等价于神经网络中的层(layers)。一个 Module 接受输入 Tensor 并计算输出 Tensor,也许也会持有内部的状态(state),比如包含可学习的参数的(learnable parameters)Tensor。当训练神经网络时,nn 包也定了一套损失函数(loss function)。

在这个例子中,我们使用 nn 包实现我们的多项式模型。

  1. import torch
  2. import math
  3. # 创建输入输出 Tensor
  4. x = torch.linspace(-math.pi, math.pi, 2000)
  5. y = torch.sin(x)
  6. # 在这个例子中,输出 y 是一个(x,x^2,x^3)的线性函数,所以
  7. # 我们可以考虑它是一个线性(linear layer)神经网络层。
  8. # 让我们准备 Tensor(x,x^2,x^3)
  9. p = torch.tensor([1, 2, 3])
  10. xx = x.unsqueeze(-1).pow(p)
  11. # 在上面的代码中,x.unsqueeze(-1) 有着 (2000,1)的形状(shape),并且 p 有着 (3,)的形状
  12. # 对于这个例子,广播语义(broadcasting semantics),得到一个 (2000,3)的 Tensor。
  13. # 使用 nn 包定义我们一系列的层的模型。nn.Sequential 是一个 Module,其包含其它 Modules
  14. # 并按顺序应用它们产生输出。线性 Modules 从输入使用一个线性函数计算输出
  15. # 并在内部持有模型的 weight 和 bias 的 Tensor。Flatten layer 展开线性层的输出到
  16. # 一个匹配 y 的形状(shape)的一维(1D)的 Tensor。
  17. model = torch.nn.Sequential(
  18. torch.nn.Linear(3, 1),
  19. torch.nn.Flatten(0, 1)
  20. )
  21. # nn 包同样包含流行的损失函数(loss function)的定义;在这个例子中,
  22. # 我们将使用均方误差(Mean Square Error——MSE)作为我们的损失函数。
  23. loss_fn = torch.nn.MSELoss(reduction='sum')
  24. learning_rate = 1e-6
  25. for t in range(2000):
  26. # 前向传播:通过传入 x 到 model 计算预测的 y。Module 对象重写了
  27. # __call__ 函数,所我们可以就像调用函数一样调用他们。当你这么做的时候
  28. # 传入输入 Tensor 到 Module 然后它计算产生输出的 Tensor。
  29. y_pred = model(xx)
  30. # 计算并打印损失值(loss)。我们传入 y 的预测值和真实值的 Tensor,
  31. # 之后 loss function 返回损失值(loss)的 Tensor。
  32. loss = loss_fn(y_pred, y)
  33. if t % 100 == 99:
  34. print(t, loss.item())
  35. # 运行反向传播之前清零一下梯度
  36. model.zero_grad()
  37. # 反向传播:计算 loss 关于所有 model 的可学习参数的梯度。从底层上来说,
  38. # 每一个 requires_grad=True 的 Module 的参数(parameters)都被存储在一个 Tensor 中,
  39. # 所以下面这个调用将计算 model 中所有可学习参数的的梯度。
  40. loss.backward()
  41. # 使用梯度下降更新权重。每一个参数都是一个 Tensor,
  42. # 所以我们就像之前一样得到它的梯度。
  43. with torch.no_grad():
  44. for param in model.parameters():
  45. param -= learning_rate * param.grad
  46. # 你也可以就像得到列表(list)的第一个元素一样,得到 model 的第一层
  47. linear_layer = model[0]
  48. # 对于 linear layer,它的参数被存储为 weight 和 bias。
  49. print(f'Result: y = {linear_layer.bias.item()} + {linear_layer.weight[:, 0].item()} x + {linear_layer.weight[:, 1].item()} x^2 + {linear_layer.weight[:, 2].item()} x^3')
  1. 99 672.3364868164062
  2. 199 448.7064208984375
  3. 299 300.4986572265625
  4. 399 202.26097106933594
  5. 499 137.13494873046875
  6. 599 93.9523696899414
  7. 699 65.31460571289062
  8. 799 46.318885803222656
  9. 899 33.71611785888672
  10. 999 25.35308074951172
  11. 1099 19.802162170410156
  12. 1199 16.116811752319336
  13. 1299 13.669382095336914
  14. 1399 12.043644905090332
  15. 1499 10.963366508483887
  16. 1599 10.245325088500977
  17. 1699 9.7678804397583
  18. 1799 9.450323104858398
  19. 1899 9.23902416229248
  20. 1999 9.098373413085938
  21. Result: y = -0.006202241405844688 + 0.8414672017097473 x + 0.0010699888225644827 x^2 + -0.09115784615278244 x^3

PyTorch: optim

到目前为止,我们通过 torch.no_grad() 手动更改持有可学习参数的 Tensor 来更新了我们模型的权重。这对于一些简单的优化算法,比如随机梯度下降(stochastic gradient descent),并不是一个太大的负担,但是实际上,我们经常使用更复杂的优化器(Optimizer),比如 AdaGradRMSpropAdam等,训练神经网络。

PyTorch 里的 optim 包抽象了一个优化算法的思想,并且提供了常用的优化算法的实现。

在这个例子,我们依旧使用 nn 包定义我们的模型,但是我们将使用 optim 包提供的 RMSprop 算法优化模型。

  1. import torch
  2. import math
  3. # 创建输入输出的 Tensor。
  4. x = torch.linspace(-math.pi, math.pi, 2000)
  5. y = torch.sin(x)
  6. # 准备输入的 Tensor(x,x^2,x^3)。
  7. p = torch.tensor([1, 2, 3])
  8. xx = x.unsqueeze(-1).pow(p)
  9. # 使用 nn 包定义我们的模型和损失函数。
  10. model = torch.nn.Sequential(
  11. torch.nn.Linear(3, 1),
  12. torch.nn.Flatten(0, 1)
  13. )
  14. loss_fn = torch.nn.MSELoss(reduction='sum')
  15. # 使用 optim 包定义一个将会为我们更新模型的权重(weight)的优化器(Optimizer)。
  16. # 这里我们将使用 RMSprop,optim 包包含了许多其它优化算法。
  17. # RMSprop 构造器的第一个参数是告诉优化器哪些 Tensor 应该被更新。
  18. learning_rate = 1e-3
  19. optimizer = torch.optim.RMSprop(model.parameters(), lr=learning_rate)
  20. for t in range(2000):
  21. # 前向传播:传入 x 到 model 计算预测的 y
  22. y_pred = model(xx)
  23. # 计算并打印损失值(loss)
  24. loss = loss_fn(y_pred, y)
  25. if t % 100 == 99:
  26. print(t, loss.item())
  27. # 在反向传播之前,需要使用优化器(optimizer)对象清零所有将被更新的变量的梯度
  28. # (模型的可学习参数(learnable weight))。这是因为在默认情况下,不论何时 .backward() 被调用时,
  29. # 梯度会被累积在缓冲区(换言之,不会被覆盖)。可以通过 torch.autograd.backward 的官方文档查看更多细节。
  30. optimizer.zero_grad()
  31. # 反向传播:计算 loss 关于模型参数的梯度。
  32. loss.backward()
  33. # 调用优化器上的 step 函数,更新它的参数。
  34. optimizer.step()
  35. linear_layer = model[0]
  36. print(f'Result: y = {linear_layer.bias.item()} + {linear_layer.weight[:, 0].item()} x + {linear_layer.weight[:, 1].item()} x^2 + {linear_layer.weight[:, 2].item()} x^3')
  1. 99 1610.013671875
  2. 199 844.4555053710938
  3. 299 525.6090698242188
  4. 399 334.3529968261719
  5. 499 213.2096405029297
  6. 599 135.09706115722656
  7. 699 83.0303955078125
  8. 799 48.23293685913086
  9. 899 26.453231811523438
  10. 999 14.763155937194824
  11. 1099 10.078741073608398
  12. 1199 9.046186447143555
  13. 1299 8.941937446594238
  14. 1399 8.894529342651367
  15. 1499 8.899309158325195
  16. 1599 8.912175178527832
  17. 1699 8.913202285766602
  18. 1799 8.911144256591797
  19. 1899 8.92212200164795
  20. 1999 8.92563247680664
  21. Result: y = -0.0005531301139853895 + 0.8562383651733398 x + -0.0005647626821883023 x^2 + -0.0938328206539154 x^3

PyTorch:定制 nn Modules

某些时候,你想要指定的模型比 Modules 存在的顺序(sequence)模型还要复杂,在这种情况下,你可以通过继承 nn.Module 的子类并且定义一个 forward 函数,这个函数使用其它 Modules 或者其它 autograd 操作符,接收输入 Tensor 并计算输出 Tensor。

在这个例子中,我们实现我们的三阶多项式作为定制的 Module 的子类。

  1. import torch
  2. import math
  3. class Polynomial3(torch.nn.Module):
  4. def __init__(self):
  5. """
  6. 在这个构造器,我们实例化四个参数并赋它们为成员 parameters。
  7. """
  8. super().__init__()
  9. self.a = torch.nn.Parameter(torch.randn(()))
  10. self.b = torch.nn.Parameter(torch.randn(()))
  11. self.c = torch.nn.Parameter(torch.randn(()))
  12. self.d = torch.nn.Parameter(torch.randn(()))
  13. def forward(self, x):
  14. """
  15. 在前向传播函数,我们接收一个输入数据的 Tensor,并且我们必须返回输出数据的 Tensor。
  16. 我们可以使用定义在构造器的 Modules 以及任意的操作在 Tensor 上的运算符。
  17. """
  18. return self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3
  19. def string(self):
  20. """
  21. 就像 Python 中的任意一个类一样,你也可以随便定义任何的方法(method)在 PyTorch Modules中。
  22. """
  23. return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3'
  24. # 创建输入输出的 Tensor。
  25. x = torch.linspace(-math.pi, math.pi, 2000)
  26. y = torch.sin(x)
  27. # 实例化上面定义的类,构造我们的模型。
  28. model = Polynomial3()
  29. # 构造我们的损失函数(loss function)和一个优化器(Optimizer)。在 SGD 构造器里
  30. # 调用 model.parameters(),构造器将包含 nn.Linear Module 的可学习的参数(learnable parameters)
  31. # 其是模型(model)的成员变量。
  32. criterion = torch.nn.MSELoss(reduce='sum')
  33. optimizer = torch.optim.SGD(model.parameters(), lr=1e-6)
  34. for t in range(2000):
  35. # 前向传播:传入 x 到模型计算预测的 y
  36. y_pred = model(x)
  37. # 计算并打印损失值(loss)
  38. loss = criterion(y_pred, y)
  39. if t % 100 == 99:
  40. print(t, loss.item())
  41. # 清零梯度,执行反向传播,更新参数。
  42. optimizer.zero_grad()
  43. loss.backward()
  44. optimizer.step()
  45. print(f'Result: {model.string()}')
  1. 99 62.18498229980469
  2. 199 58.930118560791016
  3. 299 55.8530387878418
  4. 399 52.94403076171875
  5. 499 50.19393539428711
  6. 599 47.59403991699219
  7. 699 45.136138916015625
  8. 799 42.812469482421875
  9. 899 40.61569595336914
  10. 999 38.538875579833984
  11. 1099 36.575462341308594
  12. 1199 34.71923828125
  13. 1299 32.964359283447266
  14. 1399 31.305295944213867
  15. 1499 29.736791610717773
  16. 1599 28.2539005279541
  17. 1699 26.85195541381836
  18. 1799 25.52651023864746
  19. 1899 24.273393630981445
  20. 1999 23.08867073059082
  21. Result: y = -1.397053837776184 + -0.8716074824333191 x + 0.35939672589302063 x^2 + -0.23259805142879486 x^3

PyTorch:控制流 + 权重(参数)共享

作为一个动态图和权重共享的例子,我们实现一个非常强大的模型:一个 3-5 阶的多项式,在前向传播时选择一个 3-5 之间的随机数,并且使用许多阶,多次重复使用相同的权重计算第四阶和第五阶。

对于这个模型,我们可以使用典型的 Python 控制流实现循环,并且当定义前向传播时,我们可以简单地复用相同的参数多次实现权重共享。

我们可以继承 Module 类轻松地实现这个模型。

  1. import random
  2. import torch
  3. import math
  4. class DynamicNet(torch.nn.Module):
  5. def __init__(self):
  6. """
  7. 在这个构造器中,我们实例化五个参数并且将它们赋值给成员变量。
  8. """
  9. super().__init__()
  10. self.a = torch.nn.Parameter(torch.randn(()))
  11. self.b = torch.nn.Parameter(torch.randn(()))
  12. self.c = torch.nn.Parameter(torch.randn(()))
  13. self.d = torch.nn.Parameter(torch.randn(()))
  14. self.e = torch.nn.Parameter(torch.randn(()))
  15. def forward(self, x):
  16. """
  17. 对于模型的前向传播,我们随机选择 4 或 5 并复用参数 e 计算这些阶的贡献(contribution)。
  18. 因为每一次前向传播都构建了一个动态的计算图,当定义模型的前向传播时,
  19. 我们可以使用常规的 Python 控制流操作符,像循环或条件语句。
  20. 这里我们也看到了当定义一个计算图时重复使用相同的参数多次是相当安全。
  21. """
  22. y = self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3
  23. for exp in range(4, random.randint(4, 6)):
  24. y = y + self.e * x ** exp
  25. return y
  26. def string(self):
  27. """
  28. 就像 Python 中的任意一个类一样,你也可以随便定义任何的方法(method)在 PyTorch Modules中。
  29. """
  30. return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3 + {self.e.item()} x^4 ? + {self.e.item()} x^5 ?'
  31. # 创建持有输入输出的 Tensor。
  32. x = torch.linspace(-math.pi, math.pi, 2000)
  33. y = torch.sin(x)
  34. # 通过实例化上面定义的类,构造我们的模型。
  35. model = DynamicNet()
  36. # 构造我们的损失函数(loss fcuntion)和一个优化器(Optimizer)。使用毫无特色的
  37. # 随机梯度下降(stochastic gradient descent)训练这个强大的模型是艰难的,
  38. # 所以我们使用动量(momentum)。
  39. criterion = torch.nn.MSELoss(reduce='sum')
  40. optimizer = torch.optim.SGD(model.parameters(), lr=1e-8, momentum=0.9)
  41. for t in range(30000):
  42. # 前向传播:传入 x 到 model 计算预测的 y。
  43. y_pred = model(x)
  44. # 计算并打印损失值(loss)
  45. loss = criterion(y_pred, y)
  46. if t % 2000 == 1999:
  47. print(t, loss.item())
  48. # 清零梯度、执行反向传播、更新参数
  49. optimizer.zero_grad()
  50. loss.backward()
  51. optimizer.step()
  52. print(f'Result: {model.string()}')
  1. 1999 31.825870513916016
  2. 3999 30.36163330078125
  3. 5999 28.165559768676758
  4. 7999 4.4947919845581055
  5. 9999 25.11688804626465
  6. 11999 4.422863960266113
  7. 13999 22.777265548706055
  8. 15999 21.440027236938477
  9. 17999 20.374134063720703
  10. 19999 19.437679290771484
  11. 21999 18.513486862182617
  12. 23999 17.685436248779297
  13. 25999 16.829214096069336
  14. 27999 16.081615447998047
  15. 29999 15.38708782196045
  16. Result: y = 0.5208832621574402 + -2.605482578277588 x + 0.06938754767179489 x^2 + 0.6473004221916199 x^3 + -0.033068109303712845 x^4 ? + -0.033068109303712845 x^5 ?

通过实例学习 PyTorch的更多相关文章

  1. Python实例学习-文件备份

    1. 介绍 通过实例学习Python的使用,该实例来自文献[1]中的第11章解决问题. 由于没有搞清楚Win7下如何通过命令行调用zip命令,所以采用7z[2],采用7-zip命令行版本[3],版本号 ...

  2. [转载] 跟着实例学习zookeeper 的用法

    原文: http://ifeve.com/zookeeper-curato-framework/ zookeeper 的原生客户端库过于底层, 用户为了使用 zookeeper需要编写大量的代码, 为 ...

  3. cocos2d-x 3.0游戏实例学习笔记 《跑酷》 完结篇--源代码放送

    说明:这里是借鉴:晓风残月前辈的博客,他是将泰然网的跑酷教程,用cocos2d-x 2.X 版本号重写的,眼下我正在学习cocos2d-X3.0 于是就用cocos2d-X 3.0重写,并做相关笔记 ...

  4. .Net 2.0实例学习:WebBrowser页面与WinForm交互技巧

    原文:.Net 2.0实例学习:WebBrowser页面与WinForm交互技巧 最近看到博客园入门教学文章比较流行,自己最近又偷懒比较多,没啥心得,不妨写一篇没啥深度的入门文章吧. 话说有了WebB ...

  5. 实例学习SSIS(五)--理论介绍SSIS

    原文:实例学习SSIS(五)--理论介绍SSIS 导读: 实例学习SSIS(一)--制作一个简单的ETL包 实例学习SSIS(二)--使用迭代 实例学习SSIS(三)--使用包配置 实例学习SSIS( ...

  6. 实例学习SSIS(三)--使用包配置

    原文:实例学习SSIS(三)--使用包配置 导读: 实例学习SSIS(一)--制作一个简单的ETL包 实例学习SSIS(二)--使用迭代 实例学习SSIS(三)--使用包配置 实例学习SSIS(四)- ...

  7. 实例学习SSIS(四)--使用日志记录和错误流重定向

    原文:实例学习SSIS(四)--使用日志记录和错误流重定向 导读: 实例学习SSIS(一)--制作一个简单的ETL包 实例学习SSIS(二)--使用迭代 实例学习SSIS(三)--使用包配置 实例学习 ...

  8. 实例学习SSIS(二)--使用迭代

    原文:实例学习SSIS(二)--使用迭代 导读: 实例学习SSIS(一)--制作一个简单的ETL包 实例学习SSIS(二)--使用迭代 实例学习SSIS(三)--使用包配置 实例学习SSIS(四)-- ...

  9. 实例学习SSIS(一)--制作一个简单的ETL包

    原文:实例学习SSIS(一)--制作一个简单的ETL包 导读: 实例学习SSIS(一)--制作一个简单的ETL包 实例学习SSIS(二)--使用迭代 实例学习SSIS(三)--使用包配置 实例学习SS ...

随机推荐

  1. 团队作业4-Day2

    团队作业4-Day2 项目git地址 1. 站立式会议 2. 项目燃尽图 3. 适当的项目截图(部分) 4. 代码/文档签入记录(部分) 5. 每人每日总结 吴梓华:今日进行了小程序与网页代码编写的区 ...

  2. MySQL事务(一)认识事务

    简单来说,事务就是要保证一组数据库操作,要么全部完成,要么全部失败. 为什么要有事务 数据库中的数据是共享资源,因此数据库系统通常要支持多个用户的或不同应用程序的访问,会出现并发存取数据的现象. 数据 ...

  3. Tomcat 知识点总结

    Tomcat 学习笔记.本文相关配置均为 tomcat8 下,其他版本可能略有不同.如有错误请多包涵. 架构 首先,看一下整个架构图 接下来简单解释一下. Server:服务器.Tomcat 就是一个 ...

  4. 最简 Spring IOC 容器源码分析

    前言 BeanDefinition BeanFactory 简介 Web 容器启动过程 bean 的加载 FactoryBean 循环依赖 bean 生命周期 公众号 前言 许多文章都是分析的 xml ...

  5. 用Ubuntu和树莓派系统部署kubernetes集群后的一些心得

    方案 环境 操作系统:Ubuntu 16.04 & Raspbian GNU/Linux 9(Stretch Desktop) kubernetes :1.15.3 flannel:0.11. ...

  6. spark有个节点特别慢,解决办法

    除解决数据倾斜问题外,还要开启推测执行,寻找另一个executor执行task,哪个先完成就取哪个结果,再kill掉另一个.

  7. centos7网卡bond配置--自己另一篇文章的补充

    这篇文章是自己另一篇文章的第二种方法的一个完善的补充 https://www.cnblogs.com/zzf0305/p/9588585.html 1 备份网卡配置文件2 使用nmcli命令配置bon ...

  8. STL——容器(Map & multimap)的删除

    Map & multimap 的删除 map.clear();           //删除所有元素 map.erase(pos);      //删除pos迭代器所指的元素,返回下一个元素的 ...

  9. Spring中毒太深,离开Spring我居然连最基本的接口都不会写了

    前言 随着 Spring 的崛起以及其功能的完善,现在可能绝大部分项目的开发都是使用 Spring(全家桶) 来进行开发,Spring也确实和其名字一样,是开发者的春天,Spring 解放了程序员的双 ...

  10. 纯HTML + CSS制作个人资料卡

    总体预览: 材料:背景图与头像.jpg IDE:VS Code 外部链接:CDN加速的font-awesome图标 <link rel="stylesheet" href=& ...