从寻找谷神星的过程,谈最小二乘法实现多项式拟合

  科学史上众星云集,璨若星河。这些牛人基本上都是天才,但也不乏无名之辈凭借匪夷所思、骇世惊俗的猜想而跻身于巨星之列。比如,门捷列夫,整了一张留空的元素周期表,引得全世界的化学家去做填空题。还有一位德国的中学老师,名唤约翰·提丢斯(Johann Daniel Titius)的,在1766年写下了这么一个数列:

  (0+4)/10 = 0.4

  (3+4)/10 = 0.7

  (6+4)/10 = 1.0

  (12+4)/10 = 1.6

  (24+4)/10 = 2.8

  (48+4)/10 = 5.2

  (96+4)/10 = 10.0

  (192+4)/10 = 19.6

  (384+4)/10 = 38.8

  ...

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  当时,人们已知太阳系有六大行星,即水星、金星、地球、火星、木星、土星。如果以日地距离(约1.5亿公里)为一个天文单位,则六大行星距离太阳的距离,恰好接近提丢斯的这个数列,并且留下了无限的遐想!这个数列被称为提丢斯——波得定则。

  1781年,英籍德国人赫歇尔在接近19.6的位置上(即数列中的第八项)发现了天王星,从此,人们就对这一定则深信不疑了。根据这一定则,在数列的第五项即2.8的位置上也应该对应一颗行星,只是还没有被发现。于是,许多天文学家和天文爱好者便以极大的热情,踏上了寻找这颗新行星的征程。

  1801年新年的晚上,意大利神父朱塞普·皮亚齐还在聚精会神地观察着星空。突然,他从望远镜里发现了一颗非常小的星星,正好在提丢斯——波得定则中2.8的位置上。这颗行星在几天的观测期内不断变动位置。当皮亚齐再想进一步观察这颗小行星时,他却病倒了。等到他恢复健康,再想寻找这颗小行星时,它却不知所踪。皮亚齐没有放弃这一机会,他认为这可能就是人们一直没有发现的那颗行星。

  天文学家对皮亚齐的这一发现持有不同的看法。有人认为皮亚齐是正确的,也有人认为这可能是一颗彗星,关于这颗行星的说法不在少数,天文学界议论纷纷。

  此时,又一位大神出现了,他就是数学天才高斯。根据皮亚齐的观测资料,高斯以其卓越的数学才能,只用了一个小时就算出了这颗神秘小行星的轨道形状,并指出它将于何时出现在哪一片天空里。1801年12月31日夜,德国天文爱好者奥伯斯,在高斯预言的时间里,用望远镜对准了这片天空。不出所料,这颗神秘小行星再一次奇迹般地出现了!

  从约翰·提丢斯,到神父朱塞普·皮亚齐,再到数学王子高斯、天文爱好者奥伯斯,众多牛人联手发现的这个天体,被命名为谷神星。谷神星是太阳系中最小的、也是唯一位于小行星带的矮行星。谷神星曾经被当作一个标准:凡是比它大的行星,可以视为和地球平级的行星,否则视为小行星。比如冥王星,比谷神星大,被封为太阳系的老九,直到2006年,因为被分类到矮行星,才退出了九大行星的行列。

  那么,高斯究竟是如何计算谷神星运行轨道并作出预测的呢?其实就是使用最小二乘法拟合轨迹线。高斯使用的最小二乘法的方法发表于1809年他的著作《天体运动论》中。

  让我们冒充高斯,来一次cosplay,体会一下做牛人的感觉吧。

  假定上图是皮亚齐从零时到十时的谷神星观测记录,每一个红点对应的横坐标表示观测时间,对应的纵坐标表示谷神星的位置。如果有一条曲线(非常接近地)经过了上图的11个观测点,只要找到这条曲线的方程:

  y=f(x) y = f(x)

  y=f(x)

  我们就可以随意预测11时、12时的谷神星位置了。当年高斯就是这么想的。可是,如何找到这条曲线的方程呢?这个问题难不住高斯。他用一个k次多项式 g(x) g(x)g(x) 来近似 f(x) f(x)f(x):

  f(x)≈g(x)=a0+a1x+a2x2+a3x3+...+akxk f(x) \approx g(x) = a_0 + a_1x + a_2x^2 + a_3x^3 + ... + a_kx^k

  f(x)≈g(x)=a

  0

  ​

  +a

  1

  ​

  x+a

  2

  ​

  x

  2

  +a

  3

  ​

  x

  3

  +...+a

  k

  ​

  x

  k

  通过选择合适的系数 a0,a1,a2,a3,...,ak a_0,a_1,a_2,a_3,...,a_ka

  0

  ​

  ,a

  1

  ​

  ,a

  2

  ​

  ,a

  3

  ​

  ,...,a

  k

  ​

  ,令误差:

  loss=∑10i=0(g(i)−f(i))2 loss = \sum_{i=0}^{10}(g(i)-f(i))^2

  loss=

  i=0

  ∑

  10

  ​

  (g(i)−f(i))

  2

  为最小,就可以认为 g(x) g(x)g(x) 就是我们要寻找的曲线方程了。高斯的最小二乘法,就是根据观测数据和给定的多项式次数 k kk 来寻找误差最小的一组多项式系数 a0,a1,a2,a3,...,ak a_0,a_1,a_2,a_3,...,a_ka

  0

  ​

  ,a

  1

  ​

  ,a

  2

  ​

  ,a

  3

  ​

  ,...,a

  k

  ​

  ,有了这组系数,就可以得到 g(x) g(x)g(x),进而计算出 g(11) g(11)g(11) 、 g(12) g(12)g(12) 的值 ,这样就可以预测谷神星在11时、12时的位置了。

  我们不是高斯,不会使用最小二乘法,幸好 numpy 给我们提供了强大的工具,使得我们可以继续冒充高斯。

  >>> import numpy as np

  >>> _x = np.linspace(0, 10, 11)

  >>> _y = np.array([-0.3, -0.5, -0.2, -0.3, 0, 0.4, 0.2, -0.3, 0.2, 0.5, 0.4])

  >>> np.polyfit(_x, _y, 3)

  array([ 0.00066045, -0.01072261, 0.12684538, -0.43146853])

  1

  2

  3

  4

  5

  这里,_x 是皮亚齐的观测时间序列,_y 是皮亚齐观测到的谷神星位置序列,我们选择多项式次数为3。执行np.polyfit(xs, ys, 3),就是用最小二乘法找到三次多项式的4个系数,使得这个三次多项式和观测数据的误差最小。这个三次多项式写出来是这样的:

  g(x)=−0.43146853+0.12684538x−0.01072261x2+0.00066045x3 g(x) = -0.43146853 + 0.12684538x - 0.01072261x^2 + 0.00066045x^3

  g(x)=−0.43146853+0.12684538x−0.01072261x

  2

  +0.00066045x

  3

  用这个函数去验证观测数据:

  >>> g = np.poly1d(np.polyfit(_x, _y, 3))

  >>> g(_x)

  array([-0.43146853, -0.31468531, -0.21538462, -0.12960373, -0.05337995,

  0.01724942, 0.08624709, 0.15757576, 0.23519814, 0.32307692,

  0.42517483])

  >>> loss = np.sum(np.square(g(_x)-_y))

  >>> loss

  0.4857342657342658

  >>> import matplotlib.pyplot as plt

  >>> plt.plot(_x, _y, c='r', ls='', marker='o')

  >>> plt.plot(_x, g(_x), c='g', ls=':')

  >>> plt.show()

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  用三次多项式 g(x) g(x)g(x) 代替 f(x) f(x)f(x),最小误差是0.4857。 观测数据和 g(x) g(x)g(x) 的近似数据画在一起,效果如下

  很显然,这个拟合效果是无法找到谷神星的。别着急,我们还可以尝试更高次幂的多项式,看看效果如何。下面的代码,同时画出了4至9次多项式的拟合结果和误差。

  import numpy as np

  import matplotlib.pyplot as plt

  plt.rcParams['font.sans-serif'] = ['FangSong']

  plt.rcParams['axes.unicode_minus'] = False

  _x = np.linspace(0, 10, 11)

  _y = np.array([-0.3, -0.5, -0.2, -0.3, 0, 0.4, 0.2, -0.3, 0.2, 0.5, 0.4])

  g3 = np.poly1d(np.polyfit(_x, _y, 3))

  g4 = np.poly1d(np.polyfit(_x, _y, 4))

  g5 = np.poly1d(np.polyfit(_x, _y, 5))

  g6 = np.poly1d(np.polyfit(_x, _y, 6))

  g7 = np.poly1d(np.polyfit(_x, _y, 7))

  g8 = np.poly1d(np.polyfit(_x, _y, 8))

  g9 = np.poly1d(np.polyfit(_x, _y, 9))

  loss3 = np.sum(np.square(g3(_x)-_y))

  loss4 = np.sum(np.square(g4(_x)-_y))

  loss5 = np.sum(np.square(g5(_x)-_y))

  loss6 = np.sum(np.square(g6(_x)-_y))

  loss7 = np.sum(np.square(g7(_x)-_y))

  loss8 = np.sum(np.square(g8(_x)-_y))

  loss9 = np.sum(np.square(g9(_x)-_y))

  plt.plot(_x, _y, c='r', ls='', marker='o')

  plt.plot(_x, g3(_x), label=u'三次多项式,误差%0.4f'%loss3)

  plt.plot(_x, g4(_x), label=u'四次多项式,误差%0.4f'%loss4)

  plt.plot(_x, g5(_x), label=u'五次多项式,误差%0.4f'%loss5)

  plt.plot(_x, g6(_x), label=u'六次多项式,误差%0.4f'%loss6)

  plt.plot(_x, g7(_x), label=u'七次多项式,误差%0.4f'%loss7)

  plt.plot(_x, g8(_x), label=u'八次多项式,误差%0.4f'%loss8)

  plt.plot(_x, g9(_x), label=u'九次多项式,误差%0.4f'%loss9)

  plt.legend()

  plt.show()

  1

  2

  3

  4

  5

  6

  7

  8

  9

  10

  11

  12

  13

  14

  15

  16

  17

  18

  19

  20

  21

  22

  23

  24

  25

  26

  27

  28

  29

  30

  31

  32

  33

  34

  35

  36

  可以看到9次多项式拟合的误差,已经小到了0.0010,拟合曲线近乎精准地经过了所有的观测点。这个效果足可令人满意。下图只保留了9次多项式的拟合结果,看起来更清晰。

  Well, cosplay is over. 真正的天体运行轨道不会这么变态,天体位置也不是一个单值就可以表示出来的。上面的例子,纯粹就是多项式拟合。有时候,待拟合数据是下图所示的样子,没关系,上面的拟合方法仍然是有效的。

  拟合代码如下:

  import numpy as np

  import matplotlib.pyplot as plt

  plt.rcParams['font.sans-serif'] = ['FangSong']

  plt.rcParams['axes.unicode_minus'] = False

  xs = np.linspace(-1, 1, 11)

  ys = np.array([-0.3, -0.5, -0.2, -0.3, 0, 0.4, 0.2, -0.3, 0.2, 0.5, 0.4])

  xm = np.linspace(-1, 1, 201)

  ym = ((xm**2-1)**3 + 0.5)*np.sin(2*xm) + np.random.random(201)/10 - 0.1

  fs4 = np.poly1d(np.polyfit(xs, ys, 4))

  fs5 = np.poly1d(np.polyfit(xs, ys, 5))

  fs6 = np.poly1d(np.polyfit(xs, ys, 6))

  fs7 = np.poly1d(np.polyfit(xs, ys, 7))

  fs8 = np.poly1d(np.polyfit(xs, ys, 8))

  fs9 = np.poly1d(np.polyfit(xs, ys, 9))

  fs10 = np.poly1d(np.polyfit(xs, ys, 10))

  fm2 = np.poly1d(np.polyfit(xm, ym, 2))

  fm3 = np.poly1d(np.polyfit(xm, ym, 3))

  fm4 = np.poly1d(np.polyfit(xm, ym, 4))

  fm5 = np.poly1d(np.polyfit(xm, ym, 5))

  fm6 = np.poly1d(np.polyfit(xm, ym, 6))

  fm7 = np.poly1d(np.polyfit(xm, ym, 7))

  plt.subplot(211)

  plt.plot(xs, ys, c='r', ls='', marker='o')

  plt.plot(xs, fs4(xs), label=u'四次多项式')

  plt.plot(xs, fs5(xs), label=u'五次多项式')

  plt.plot(xs, fs6(xs), label=u'六次多项式')

  plt.plot(xs, fs7(xs), label=u'七次多项式')

  plt.plot(xs, fs8(xs), label=u'八次多项式')

  plt.plot(xs, fs9(xs), label=u'九次多项式')

  plt.legend()

  郑州人流医院:http://mobile.zzyyrl.com/

  郑州人流医院那家好:http://mobile.zzyyrl.com/

  plt.subplot(212)

  plt.plot(xm, ym, c='g', ls='', marker='.')

  plt.plot(xm, fm2(xm), label=u'二次多项式')

  plt.plot(xm, fm3(xm), label=u'三次多项式')

  plt.plot(xm, fm4(xm), label=u'四次多项式')

  plt.plot(xm, fm5(xm), label=u'五次多项式')

  plt.plot(xm, fm6(xm), label=u'六次多项式')

  plt.plot(xm, fm7(xm), label=u'七次多项式')

  plt.legend()

  plt.show()

猜你喜欢

转载自www.cnblogs.com/wode1/p/Daniel.html
今日推荐