原理

机器学习的目标是希望从数据中学习到一些高层次的、抽象的概念,对于一个新输入的 x,能够输出一个比较符合实际情况的 y。这样的 y 有两种类型,一种是离散的值,另一种是连续的值。

连续值预测

连续值预测的问题可以归结为给出一个 线性回归问题 - 图1,经过模型的函数 f 和参数 θ 给出一个响应 线性回归问题 - 图2,使得这个响应尽量逼近真实的 线性回归问题 - 图3线性回归问题 - 图4),最好能够等同于真实的 线性回归问题 - 图5,即零误差。

  • 一元线性回归就是一个简单的连续值预测问题。


一元线性回归

对于 线性回归问题 - 图6 这个方程,我们只需要给出两组数据 线性回归问题 - 图7线性回归问题 - 图8 通过消元法就可以对这个方程精确求解。而日常生活中我们采集到数据的模型通常是未知的,并且数据是有偏差和噪音的。例如下图这组数据,我们可以假设它是符合一元二次方程的分布甚至是更加复杂的方程,也有可能只符合一个简单的一元线性回归。如果它符合 线性回归问题 - 图9 的一元线性回归,我们该如何求解?

1.png

损失函数 loss

我们构造一个新的函数叫做损失函数 线性回归问题 - 图11。之前提到,连续值预测问题的目标是使 线性回归问题 - 图12 尽量逼近于 线性回归问题 - 图13,即 线性回归问题 - 图14。所以我们构造这样一个函数 线性回归问题 - 图15线性回归问题 - 图16 越小,求得的 线性回归问题 - 图17线性回归问题 - 图18 越精确。为了达到这个目标,我们使用一种梯度下降(Gradient Descent)方法,调优公式如下:

线性回归问题 - 图19

预设 线性回归问题 - 图20线性回归问题 - 图21 的值,每计算一次,线性回归问题 - 图22线性回归问题 - 图23 都更新一次,即每次 线性回归问题 - 图24 都移动 线性回归问题 - 图25线性回归问题 - 图26 都移动 线性回归问题 - 图27线性回归问题 - 图28 衰减因子的作用是减小每次自变量移动的距离。当 线性回归问题 - 图29 的导数小于零,自变量会向右移动,当 线性回归问题 - 图30 的导数大于零,自变量会向左移动,即自变量总是向着 线性回归问题 - 图31 的极小值方向移动,这样最终总能得到一个相对准确的 线性回归问题 - 图32线性回归问题 - 图33

例如我们构造一个易于理解的损失函数 线性回归问题 - 图34,图像如下。显然 线性回归问题 - 图35 最小时,线性回归问题 - 图36。如果使用梯度下降方法,设 线性回归问题 - 图37 初始为 0,衰减因子 线性回归问题 - 图38,那么 线性回归问题 - 图39。第一次计算,线性回归问题 - 图40 由 0 变为 0.8,最优解会从点 a(0, 2) 移动到点 b(0.8, 1.04)。第二次计算,线性回归问题 - 图41 由 0.8 变为 0.96,最优解从点 b(0.8, 1.04) 移动到点 c(0.96, 1.0016),越来越趋近最优解。虽然该方法对于这种明确有可求边界的函数略显复杂,但对于求解模糊复杂函数的最优解是很有帮助的。

2.png

实现

在上一部分我们提到,求解一个线性回归问题,要使得 线性回归问题 - 图43,这里的 线性回归问题 - 图44 是输入的自变量,线性回归问题 - 图45 是真实的输出。首先要构造一个损失函数 线性回归问题 - 图46 ,不断地调整线性回归问题 - 图47线性回归问题 - 图48 使损失函数达到最小,数据量较大时,损失函数一般会取 线性回归问题 - 图49线性回归问题 - 图50 为点的个数,即 线性回归问题 - 图51。然后通过梯度下降方法构造 线性回归问题 - 图52线性回归问题 - 图53 的调优公式,每次计算出新的 线性回归问题 - 图54线性回归问题 - 图55 都使损失函数 线性回归问题 - 图56 向最小值趋近一小步,最后我们的目标就变成了求解损失函数的最小值,从而线性回归问题 - 图57取得最优的 线性回归问题 - 图58线性回归问题 - 图59。我们分别使用 Numpy 和 Pytorch 来实现。

Numpy 实现

  1. # # 导入 numpy 库
  2. import numpy as np
  3. # # 求解 y = wx + b,定义 loss 函数
  4. # 参数 b 和 w 分别为偏秩和斜率,points 为点的数组 [(x0,y0),(x1,y1),……,(xn,yn)]
  5. def ComputeLossforLineGivenPoints(b, w, points):
  6. totalLoss = 0
  7. N = float(len(points))
  8. for i in range(0, len(points)):
  9. # 这是 numpy 一种特殊的数组取值方法,等同于 points[i][0]
  10. x = points[i, 0]
  11. y = points[i, 1]
  12. # 计算 (w * x + b - y) 的平方和
  13. totalLoss += (w * x + b - y) ** 2
  14. # 损失函数 loss 取 1/N
  15. return totalLoss / N
  16. # # 定义 gradient descent 算法
  17. # b_current 和 w_current 是上一次调优的 b 和 w,learningRate 是衰减因子 lr
  18. def SetGradientDescent(b_current, w_current, points, learningRate):
  19. b_gradient = 0
  20. w_gradient = 0
  21. N = float(len(points))
  22. for i in range(0, len(points)):
  23. x = points[i, 0]
  24. y = points[i, 1]
  25. # loss 对 b 的偏导数求和
  26. b_gradient += (2/N) * ((w_current * x + b_current) - y)
  27. # loss 对 w 的偏导数求和
  28. w_gradient += (2/N) * x * ((w_current * x + b_current) - y)
  29. # 更新调优的 b 和 w
  30. new_b = b_current - (learningRate * b_gradient)
  31. new_w = w_current - (learningRate * w_gradient)
  32. return [new_b, new_w]
  33. # # 定义循环函数,不断地优化 b 和 w
  34. # starting_b 和 starting_w 为预设的偏秩和斜率,num_iterations 是循环的次数
  35. def GradientDescentRunner(points, starting_b, starting_w, learning_rate, num_iterations):
  36. b = starting_b
  37. w = starting_w
  38. # 不断地执行 gradient descent 算法
  39. for i in range(num_iterations):
  40. b, w = SetGradientDescent(b, w, np.array(points), learning_rate)
  41. return [b, w]
  42. def Run():
  43. # 这里使用一个随机的数据集,共有100个点,即本节第一张图所示的数据
  44. points = np.genfromtxt("data.csv", delimiter=",")
  45. learning_rate = 0.0001
  46. initial_b = 0 # 预设的偏秩
  47. initial_w = 0 # 预设的斜率
  48. num_iterations = 1000 # 设置循环1000次
  49. # 打印调优之前的 b、w 和误差
  50. print("Starting gradient descent at b = {0}, w = {1}, error = {2}"
  51. .format(initial_b, initial_w,
  52. ComputeLossforLineGivenPoints(initial_b, initial_w, points))
  53. )
  54. print("Running...")
  55. # 得到最优的 b 和 w
  56. [b, w] = GradientDescentRunner(points, initial_b, initial_w, learning_rate, num_iterations)
  57. # 打印调优之后的 b、w 和误差
  58. print("After gradient descent b = {0}, w = {1}, error = {2}".
  59. format(b, w,
  60. ComputeLossforLineGivenPoints(b, w, points))
  61. )
  62. if __name__ == '__main__':
  63. Run()
  64. # # 结果
  65. # Starting gradient descent at b = 0, w = 0, error = 5565.107834483211
  66. # Running...
  67. # After gradient descent b = 0.08893651993741346, w = 1.4777440851894448, error = 112.61481011613473

Pytorch 实现

  1. import numpy as np
  2. import torch
  3. import torch.nn as nn
  4. from torch import optim
  5. from torch.autograd import Variable
  6. points = np.genfromtxt("data.csv", delimiter=",")
  7. x_train = points[:, :1]
  8. x_train = x_train.astype(np.float32)
  9. y_train = points[:, 1:]
  10. y_train = y_train.astype(np.float32)
  11. x_train = torch.from_numpy(x_train)
  12. y_train = torch.from_numpy(y_train)
  13. class LinearRegression(nn.Module):
  14. def __init__(self):
  15. super(LinearRegression, self).__init__()
  16. self.linear = nn.Linear(1, 1) # 输入和输出都是一维
  17. def forward(self, x):
  18. out = self.linear(x)
  19. return out
  20. model = LinearRegression()
  21. criterion = nn.MSELoss()
  22. optimizer = optim.SGD(model.parameters(), lr=0.0001)
  23. num_epochs = 1000
  24. for epoch in range(num_epochs):
  25. inputs = Variable(x_train)
  26. target = Variable(y_train)
  27. # 前向传播
  28. out = model(inputs)
  29. loss = criterion(out, target)
  30. # 后向传播
  31. optimizer.zero_grad()
  32. loss.backward()
  33. optimizer.step()
  34. print("Running...")
  35. # 得到最优的 b 和 w
  36. [w, b] = list(model.parameters())
  37. b = b.tolist()
  38. w = w.tolist()
  39. # 打印调优之后的 b、w 和误差
  40. print("After gradient descent b = {0}, w = {1}, error = {2}".
  41. format(b, w,
  42. loss.item())
  43. )
  44. # # 结果
  45. # Running...
  46. # After gradient descent b = [0.792700469493866], w = [[1.463911771774292]], error = 112.21363067626953