感知机(perceptron)

感知机是神经网络的起源算法,在上世纪 50 年代由科学家 Frank Rosenblatt 基于神经感知科学提出。感知机像一个感受器,可接收多个输入信号,输出一个信号。

单层感知机

单层感知机是二分类的线性分类模型,它可以简单地表示为感知机与链式法则 - 图1,下面是它的数学模型,其中 sign 激活函数我们之前已经介绍过了,因为它不可导,所以我们接下来使用 sigmoid 激活函数代替 sign 函数。

QQ截图20200201105719.png

多个输入进入单层感知机,通过加权求和后得到综合值 感知机与链式法则 - 图3,再通过激活函数得到预测值 感知机与链式法则 - 图4。通过前面的知识我们知道,我们的目的是要使得损失函数最小,更新 感知机与链式法则 - 图5感知机与链式法则 - 图6 参数,进而得到最优的 感知机与链式法则 - 图7感知机与链式法则 - 图8 参数。所以下一步,我们利用单层感知机完成一个 感知机与链式法则 - 图9感知机与链式法则 - 图10 参数更新操作。

当我们得到预测值 感知机与链式法则 - 图11 后,通过与真实值 感知机与链式法则 - 图12 比对,得到损失函数 感知机与链式法则 - 图13。这里我们使用常见的均方误差函数,那么 感知机与链式法则 - 图14。为了简单起见,我们使用一个训练值举例,也就是只有一个真实值和一个预测值,感知机与链式法则 - 图15,乘二分之一的目的是抵消掉求导时产生的 2,加与否无所谓,因为不影响梯度的方向,这样只是便于我们手动计算。

感知机与链式法则 - 图16 参数更新的公式为 感知机与链式法则 - 图17,这个我们之前接触过,感知机与链式法则 - 图18 为输入的参数,已知,感知机与链式法则 - 图19 是学习率,已知,唯一不知道的是损失函数 感知机与链式法则 - 图20感知机与链式法则 - 图21 的导数。所以,我们要计算 感知机与链式法则 - 图22感知机与链式法则 - 图23 的梯度。先求 感知机与链式法则 - 图24感知机与链式法则 - 图25 的偏导数,有几个铺陈,以便计算,感知机与链式法则 - 图26感知机与链式法则 - 图27

感知机与链式法则 - 图28

最终,感知机与链式法则 - 图29 对第 感知机与链式法则 - 图30感知机与链式法则 - 图31 的偏导数等于 感知机与链式法则 - 图32感知机与链式法则 - 图33感知机与链式法则 - 图34感知机与链式法则 - 图35 都已知,所以 感知机与链式法则 - 图36 参数可以更新完成。

下面我们使用 pytorch 实现一下。

  1. import torch
  2. from torch.nn import functional as F
  3. lr = 0.1
  4. x = torch.randn(1, 10)
  5. w = torch.randn(1, 10, requires_grad=True)
  6. print(w)
  7. # tensor([[ 1.3171, -0.2054, -1.2400, 0.1385, 0.0743, 0.9640, -0.3430, 0.3459,
  8. # -0.0509, 0.9320]], requires_grad=True)
  9. o = torch.sigmoid(x @ w.t()) # 计算预测值 o
  10. print(o.shape) # torch.Size([1, 1])
  11. loss = F.mse_loss(torch.ones(1, 1), o) # 定义 loss
  12. print(loss.shape) # torch.Size([])
  13. loss.backward()
  14. print(w.grad)
  15. # tensor([[-0.3915, -0.2115, -0.1993, 0.0623, 0.0919, 0.4512, -0.0347, -0.0664,
  16. # 0.2047, 0.1800]])
  17. w_new = w - lr * w.grad # 更新 w 参数
  18. print(w_new)
  19. # tensor([[ 1.3562, -0.1843, -1.2200, 0.1323, 0.0651, 0.9189, -0.3395, 0.3526,
  20. # -0.0713, 0.9141]], grad_fn=<SubBackward0>)

多输出感知机

在上一部分,我们创建了由一个真实值和一个预测值构成的严格意义的感知机。在本部分,我们使用多个真实值和预测值构建多输出的单层感知机,实际上多输出的单层感知机就是神经网络的全连接层中的输出层,它长下面这个样子。

QQ截图202002011612021.png

相应的,损失函数就变成了 感知机与链式法则 - 图38。重新推导之后,感知机与链式法则 - 图39 对第 感知机与链式法则 - 图40 个分支的第 感知机与链式法则 - 图41感知机与链式法则 - 图42 的偏导数等于 感知机与链式法则 - 图43。然后我们用 Pytorch 实现一下,实现方法与单输出感知机雷同。

  1. import torch
  2. from torch.nn import functional as F
  3. lr = 0.1
  4. x = torch.randn(1, 10)
  5. w = torch.randn(2, 10, requires_grad=True)
  6. print(w)
  7. # tensor([[-1.0981, 0.4161, -0.9420, 0.1112, 0.8102, 0.3344, -0.6708, -1.4195,
  8. # 0.1471, -0.7545],
  9. # [-2.3764, -0.5531, 1.1403, -0.4046, 0.0813, -0.4803, 0.3531, -1.2993,
  10. # 0.3948, 0.4814]], requires_grad=True)
  11. o = torch.sigmoid(x @ w.t()) # 计算预测值 o
  12. print(o.shape) # torch.Size([1, 2])
  13. loss = F.mse_loss(torch.ones(1, 2), o) # 定义 loss
  14. print(loss.shape) # torch.Size([])
  15. loss.backward()
  16. print(w.grad)
  17. # tensor([[ 0.0088, 0.0087, -0.0255, -0.0137, -0.0285, -0.0357, 0.0067, 0.0167,
  18. # 0.0043, -0.0181],
  19. # [ 0.0006, 0.0006, -0.0017, -0.0009, -0.0019, -0.0024, 0.0005, 0.0011,
  20. # 0.0003, -0.0012]])
  21. w_new = w - lr * w.grad # 更新 w 参数
  22. print(w_new)
  23. # tensor([[-1.0990, 0.4152, -0.9395, 0.1126, 0.8131, 0.3380, -0.6715, -1.4212,
  24. # 0.1467, -0.7526],
  25. # [-2.3765, -0.5532, 1.1405, -0.4045, 0.0815, -0.4800, 0.3530, -1.2994,
  26. # 0.3947, 0.4815]], grad_fn=<SubBackward0>)

链式法则

一个完备的神经网络不会只含有一层感知机,当我们拥有多层感知机时,怎样将最终的损失函数值一层一层地输出到前面的中间层,以求得损失函数对每一层权值的梯度,更新每一层的参数?这时我们需要用到神经网络中最重要的公式——链式法则。我们在微积分中会经常见到链式法则,实际上在之前的计算中我们已经不经意地用到了这一法则。

感知机与链式法则 - 图44

我们将 感知机与链式法则 - 图45 看作是从输入层 x 到输出层 y,中间夹了一层隐藏层 u,若 感知机与链式法则 - 图46感知机与链式法则 - 图47,即 感知机与链式法则 - 图48,那么要求得 感知机与链式法则 - 图49 的梯度,计算 感知机与链式法则 - 图50。如果我们直接将 感知机与链式法则 - 图51 展开,那么 感知机与链式法则 - 图52感知机与链式法则 - 图53。表面看起来好像链式法则麻烦了些,原因是我们使用了非常简单的函数,并且没有加入激活函数,在实际应用中,链式法则是更高效的,而且通过链式法则,我们可以更清晰地求解各层中的参数。

例如一个最简单的全连接层,它包括一个输入层,一个输出层,一个隐藏层,感知机与链式法则 - 图54。那么要求得 E 对输入层中某个参数的偏导数,感知机与链式法则 - 图55。一层一层地向前推导,我们就可以清晰地求得每一层中的参数了。

  1. from torch.nn import functional as F
  2. import torch
  3. x = torch.tensor(1.)
  4. w1 = torch.tensor(2., requires_grad=True)
  5. b1 = torch.tensor(1.)
  6. w2 = torch.tensor(2., requires_grad=True)
  7. b2 = torch.tensor(1.)
  8. # 定义 o_1,o_2
  9. o1 = w1 * x + b1
  10. o2 = w2 * o1 + b2
  11. loss = F.mse_loss(torch.tensor(1.), o2) # 定义 loss
  12. # 定义链式法则
  13. dloss_do2 = torch.autograd.grad(loss, [o2], retain_graph=True)[0]
  14. do2_do1 = torch.autograd.grad(o2, [o1], retain_graph=True)[0]
  15. do1_dw1 = torch.autograd.grad(o1, [w1], retain_graph=True)[0]
  16. dloss_dw1 = torch.autograd.grad(loss, [w1], retain_graph=True)[0]
  17. # 验证
  18. d1 = dloss_do2 * do2_do1 * do1_dw1
  19. d2 = dloss_dw1
  20. print(d1, d2) # tensor(24.) tensor(24.)

反向传播

我们先来看一个简单的多层多输出的感知机,感知机与链式法则 - 图56 为输入层,感知机与链式法则 - 图57 为中间层,感知机与链式法则 - 图58 为输入层和中间层之间的权重,感知机与链式法则 - 图59 表示从输入层的 感知机与链式法则 - 图60 指向中间层的 感知机与链式法则 - 图61 的权重,同理,感知机与链式法则 - 图62 为输出层,感知机与链式法则 - 图63 为中间层与输出层之间的权重,感知机与链式法则 - 图64 为中间层 感知机与链式法则 - 图65 指向输出层 感知机与链式法则 - 图66 的权重。

QQ截图20200202114112.png

之前多输出感知机的输入层现在变成了多层多输出感知机的中间层 感知机与链式法则 - 图68。在之前的单层多输出感知机部分,我们得出过结论,单层多输出感知机的 感知机与链式法则 - 图69 对第 感知机与链式法则 - 图70 个分支的第 感知机与链式法则 - 图71感知机与链式法则 - 图72 的偏导数等于 感知机与链式法则 - 图73。如果把中间层 感知机与链式法则 - 图74 左边部分挡住,中间层作为输入层,右边部分的偏导数就变成了 感知机与链式法则 - 图75。因为 感知机与链式法则 - 图76 只由 感知机与链式法则 - 图77 决定,我们把这一部分用一个符号 感知机与链式法则 - 图78 表示,即 感知机与链式法则 - 图79

接下来,我们推导一下 感知机与链式法则 - 图80感知机与链式法则 - 图81 的偏导数。

感知机与链式法则 - 图82

所以,感知机与链式法则 - 图83感知机与链式法则 - 图84 的偏导数等于 感知机与链式法则 - 图85。同样的道理,后半部分 感知机与链式法则 - 图86 只跟 感知机与链式法则 - 图87 有关系,所以这一部分我们用符号 感知机与链式法则 - 图88 代替,即 感知机与链式法则 - 图89

将前面的内容归纳一下,当目标层为输出层时,感知机与链式法则 - 图90感知机与链式法则 - 图91。当目标层为中间层(隐藏层)时,感知机与链式法则 - 图92感知机与链式法则 - 图93感知机与链式法则 - 图94 是可以直接计算的,所以通过每层的 感知机与链式法则 - 图95 一层一层地反向传递,就能求得所有层所有权重参数的梯度,进而更新权重参数。