前面兩篇文章, 我們先是通過三個非常簡單的數學例子了解了機器學習的基本流程(訓練, 預測). 接着為大家解釋了為什么大家早就學會解方程了, 還需要用到機器學習技術. 我們接下來要講的是機器學習算法怎樣為我們在無數個可能的模型中找出最有可能正確的(最優的)那個模型.
首先在上一篇文章中, 有朋友提問 "為什么認為找出來的模型是最優的,怎么判斷它是不是最優的,依據是什么"
機器學習沒辦法為我們找到百分百正確的模型, 但是機器學習可以幫我們找到出錯概率最低的模型, 或者說將該模型應用回歷史數據時, 該模型能夠保證他的誤差比其它模型小, 或者說誤差小到你可以接受的程度.
對於這一類的問題, 在機器學習任務集里有一個單獨的任務類別, 我們稱之為回歸任務(Regression). 在統計分析中也被稱之為回歸分析(Regression_analysis). 關於回歸分析專業解釋可以參考 Wikipedia的 回歸分析(Regression_analysis)詞條 或者智庫百科的 回歸分析預測法 詞條.
回歸分析通常依據數據的特性(最終得到的數學模型是線性的還是非線性的)又分為線性回歸與非線性回歸. 不過很多復雜的社會現象或者金融模型都是基於非線性的回歸計算.
線性與非線性的區別就是線性方程式與非線性方程式的區別(再通俗點就是 在二維平面中, 直線就是線性的, 拋物線就是非線性的, 在三維空間中, 桌面就是線性的, 籃球的表面就是非線性的). 通常來說, 線性回歸比非線性回歸擬合起來會更容易一些.
回歸計算最早使用的是最小二乘法(least squares)來計算的, 而且直到現在最小二乘法仍然是最流行的分析方法之一. 最小二乘法不僅能夠用於線性回歸的擬合(性線最小二乘法也稱為普通最小二乘法 ordinary least squares (OLS) or ordinary least squares), 同樣能夠用於非線性回歸的擬合(non-linear least squares). 最小二乘法的思路是最小化所有樣本在最終模型下的錯誤平方差之和 (minimizes the sum of the squares of the errors made in the results of every single equation) . 換句話說, 最小二乘法的原理就是保證得到的最終的模型在無數個可能的模型中的誤差是最小的.
接下來我們來先通過一個簡單的例子 來使用最小平方差原理尋找最優解
假設
已知有一種植物,經過3年時間的觀察
第1年時, 植物高度到1米
第1.5年時, 植物高度為1.2米
第2年時, 植物高度為2米
第2.5年時, 植物高度為2.2米
第3年時, 植物高度為2.5米,
現在需要預測植物在第四年的高度為有多少米
將年限看作X軸, 將植物高度看作Y軸我們可得歷史坐標點 (1,1), (1.5,1.2), (2,2), (2.5,2.2), (3,2.5)
直接從圖中也可以發現, 無法作一條直接同時通過五個點
從方程組的角度,
當我們把五個點整理成方程組時, 由方程1與方程2聯立也可知該方程無解
x-y=0 ---------------(1)
1.5x-1.2y=0---------(2)
2x-2y=0-------------(3)
2.5x-2.2y=0---------(4)
3x-2.5y=0-----------(5)
因此, 常規的解方程思路在這里走不通.
但是, 這么簡單的問題, 無數位前輩們早已將其研究爛了, 所以直接給這類 普通最小二乘法給總結了公式出來了
方法一: 使用普通二乘法計算公式
(這不是我們接下去的重點, 因為這算是特例, 公式在非線性方程時就不適用了, 之所有介紹這種計算方式是因為, 在計算二元線性回歸時, 這么計算很方便很直接)
首先假設最終的模型為
$$\hat{Y}=aX+b$$
第一步: 計算x與y的平均值(也叫期望)
$$ N =5$$
$$\sum{x} = (1+1.5+2+2.5+3)=10$$
$$\sum{y} = (1+1.2+2+2.2+2.5)=8.9$$
$$\bar{X} = \frac{\sum{x}}{N}=(1+1.5+2+2.5+3)/5 = 2$$
$$\bar{Y} = \frac{\sum{y}}{N}=(1+1.2+2+2.2+2.5)/5 = 1.78$$
第二步: 計算模型中的斜率(slop) a
$$slop =\frac{Covariance(x,y)}{variance(x)}=\frac{\sum\limits_{i=1}^n(x_i-\bar{x}_i)(y_i-\bar{y}_i)}{\sum\limits_{i=1}^n(x_i-\bar{x})^2}=\frac{0.4}{0.5}=0.8$$
第三步: 計算模型中的截距(y-intercept) b
$$b=\bar{Y}-a\bar{X}=1.78-0.8\times2=0.18$$
利用得到的斜率與截距, 我們就能夠得到我們想要的數學模型了
$$\hat{Y}=0.8X+0.18$$
所以第四年的植物高度為 將x=4代入 數學模型中我們可以得到第四年的 植物高度為3.38
方法二: 使用最小化誤差的思想
前文提到的最小二乘法的思路是最小化所有樣本在最終模型下的錯誤平方差之和, 那么這次重點要講的方法就是根據這個思路進行建模的
首先, 需要明確的是, 什么是誤差
在這個例子中, 單個點的誤差最直接的想法就是通過我們估算出來的數學模型, 將x值代進去時, 計算出來的與真實Y的差,
但是因為這樣子計算的話, 差有可能是正數, 也可能是負數. 所以通常情況下我們使用的差的平方作為單個點的誤差.
$$e_i=(\hat{Y_i}-Y_i)^2$$
因此數學模型的整體誤差為
$$ e= \sum\limits_{i=1}^n(\hat{Y_i}-Y_i)^2$$
定了誤差以后, 我們還得明確我們可以容忍的多大的誤差, 因為最終的數學模型不一定可以做到零誤差(很少可以找到完美的零誤差模型, 因為零誤差的模型本身就可能是不存在的, 即便是方法一得出的結果也是有誤差的)
假設我們現在可以容忍的誤差為 0.5
在已知最終的模型為線程方程的情況下,
$$Y=aX+b$$
我們可以開始我們的第一步
第一步: 碰運氣
沒錯, 就是碰運氣, 隨便假設一下a 與b的值. 然后計算一下誤差哈
讓我們假設a=1, b=1
則我們的最終模型為
$$\hat{Y}=X+1$$
因此我們可以得到該數學模型的總誤差為7.63
$$ e= \sum\limits_{i=1}^n(\hat{Y_i}-Y_i)^2= (2-1)^2+(2.5-1.2)^2+(3-2)^2+(3.5-2.2)^2+(4-2.5)^2=7.63$$
明顯目前的誤差離我們可以容忍的誤差還有很大的差距
所以我們需要進入第二步: 接着猜
我們先嘗試調整b的值, 假設a仍為1, b=2(原來為1, 現在加上1), 可得誤差 24.83
通過誤差比對, 可以發現, 我們的方向可能錯了, 因為誤差變大了, 所以這個時候我們重新假設 a=1, b=0(原來為1, 現在減去1)的情況, 可得誤差0.43
這回誤差明確小了一大截了, 而且已經小於我們可以容忍的誤差范圍了(±0.5)
所以我們得到了第一個滿足我們條件的數學模型
$$\hat{Y}=X$$
然而人總是貪心的, 當我們得到想到的東西時, 還會想要其它更好的東西.
所以類似得, 當我們找到滿足我們要求的模型時, 我們就會想得到更好的更精確的數學模型.
於是假設我們現在需要誤差小於0.1的更為精確的數學模型
讓我們可以接着剛才的想法, 繼續調整一下b的取值 假設 a=1, b=-1可得誤差3.23
可以發現誤差這次不僅沒有變小反而又變大了,
因此, 可以斷定b的更好的取值可能是-1到1之間,
因為誤差的計算可以很容易的通過程序來代表人工計算, 所以讓我們來編寫一段簡單的python循環來愉快的盲目的尋找更好的b值吧
1 # -*- coding: utf-8 -*-
2 # regressionv1.py
3 import numpy 4
5 a = 1
6 x = [ 1 , 1.5 , 2 , 2.5 , 3 ] 7 y = [ 1 , 1.2 , 2 , 2.2 , 2.5 ] 8 minimumError = 1000000
9 minParameter = [] 10
11 for b in numpy.arange( - 1 , 1 , 0.05 ): 12 soe = 0 #sum of error
13 for i in range ( len (x)): 14 soe += (a * x[i] + b - y[i]) * (a * x[i] + b - y[i]) 15 if soe<minimumError: 16 minimumError = soe 17 minParameter = [a,b] 18 print "當a=1,b=" + str (b), "時, 可得誤差" ,soe 19 print "最小誤差為" ,minimumError, "出現於minParameter" ,minParameter
輸出的結果如下:
當a=1,b=-1.0 時, 可得誤差 3.23 當a=1,b=-0.95 時, 可得誤差 2.8525 當a=1,b=-0.9 時, 可得誤差 2.5 當a=1,b=-0.85 時, 可得誤差 2.1725 當a=1,b=-0.8 時, 可得誤差 1.87 當a=1,b=-0.75 時, 可得誤差 1.5925 當a=1,b=-0.7 時, 可得誤差 1.34 當a=1,b=-0.65 時, 可得誤差 1.1125 當a=1,b=-0.6 時, 可得誤差 0.91 當a=1,b=-0.55 時, 可得誤差 0.7325 當a=1,b=-0.5 時, 可得誤差 0.58 當a=1,b=-0.45 時, 可得誤差 0.4525 當a=1,b=-0.4 時, 可得誤差 0.35 當a=1,b=-0.35 時, 可得誤差 0.2725 當a=1,b=-0.3 時, 可得誤差 0.22 當a=1,b=-0.25 時, 可得誤差 0.1925 當a=1,b=-0.2 時, 可得誤差 0.19 當a=1,b=-0.15 時, 可得誤差 0.2125 當a=1,b=-0.1 時, 可得誤差 0.26 當a=1,b=-0.05 時, 可得誤差 0.3325 當a=1,b=8.881784197e-16 時, 可得誤差 0.43 當a=1,b=0.05 時, 可得誤差 0.5525 當a=1,b=0.1 時, 可得誤差 0.7 當a=1,b=0.15 時, 可得誤差 0.8725 當a=1,b=0.2 時, 可得誤差 1.07 當a=1,b=0.25 時, 可得誤差 1.2925 當a=1,b=0.3 時, 可得誤差 1.54 當a=1,b=0.35 時, 可得誤差 1.8125 當a=1,b=0.4 時, 可得誤差 2.11 當a=1,b=0.45 時, 可得誤差 2.4325 當a=1,b=0.5 時, 可得誤差 2.78 當a=1,b=0.55 時, 可得誤差 3.1525 當a=1,b=0.6 時, 可得誤差 3.55 當a=1,b=0.65 時, 可得誤差 3.9725 當a=1,b=0.7 時, 可得誤差 4.42 當a=1,b=0.75 時, 可得誤差 4.8925 當a=1,b=0.8 時, 可得誤差 5.39 當a=1,b=0.85 時, 可得誤差 5.9125 當a=1,b=0.9 時, 可得誤差 6.46 當a=1,b=0.95 時, 可得誤差 7.0325 最小誤差為 0.19 出現於minParameter [1, -0.19]
可以發現點我們, 在步長為0.05時, 當b的取值為-1到1之間時, 最小的誤差為0.19, 出現在b=-0.2時
而誤差0.19雖然已經開始接近我們可以容忍的程度, 但是總歸還是差了點, 不過還好, 我們還有一個參數還沒有參與"調試"
姑且先讓我們假設b=-0.2, 繼續使用小程序來調整a的值跑一下誤差
當a=-1.0,b=-0.2時, 可得誤差 87.39 當a=-0.95,b=-0.2時, 可得誤差 83.01625 當a=-0.9,b=-0.2時, 可得誤差 78.755 當a=-0.85,b=-0.2時, 可得誤差 74.60625 當a=-0.8,b=-0.2時, 可得誤差 70.57 當a=-0.75,b=-0.2時, 可得誤差 66.64625 當a=-0.7,b=-0.2時, 可得誤差 62.835 當a=-0.65,b=-0.2時, 可得誤差 59.13625 當a=-0.6,b=-0.2時, 可得誤差 55.55 當a=-0.55,b=-0.2時, 可得誤差 52.07625 當a=-0.5,b=-0.2時, 可得誤差 48.715 當a=-0.45,b=-0.2時, 可得誤差 45.46625 當a=-0.4,b=-0.2時, 可得誤差 42.33 當a=-0.35,b=-0.2時, 可得誤差 39.30625 當a=-0.3,b=-0.2時, 可得誤差 36.395 當a=-0.25,b=-0.2時, 可得誤差 33.59625 當a=-0.2,b=-0.2時, 可得誤差 30.91 當a=-0.15,b=-0.2時, 可得誤差 28.33625 當a=-0.1,b=-0.2時, 可得誤差 25.875 當a=-0.05,b=-0.2時, 可得誤差 23.52625 當a=8.881784197e-16,b=-0.2時, 可得誤差 21.29 當a=0.05,b=-0.2時, 可得誤差 19.16625 當a=0.1,b=-0.2時, 可得誤差 17.155 當a=0.15,b=-0.2時, 可得誤差 15.25625 當a=0.2,b=-0.2時, 可得誤差 13.47 當a=0.25,b=-0.2時, 可得誤差 11.79625 當a=0.3,b=-0.2時, 可得誤差 10.235 當a=0.35,b=-0.2時, 可得誤差 8.78625 當a=0.4,b=-0.2時, 可得誤差 7.45 當a=0.45,b=-0.2時, 可得誤差 6.22625 當a=0.5,b=-0.2時, 可得誤差 5.115 當a=0.55,b=-0.2時, 可得誤差 4.11625 當a=0.6,b=-0.2時, 可得誤差 3.23 當a=0.65,b=-0.2時, 可得誤差 2.45625 當a=0.7,b=-0.2時, 可得誤差 1.795 當a=0.75,b=-0.2時, 可得誤差 1.24625 當a=0.8,b=-0.2時, 可得誤差 0.81 當a=0.85,b=-0.2時, 可得誤差 0.48625 當a=0.9,b=-0.2時, 可得誤差 0.275 當a=0.95,b=-0.2時, 可得誤差 0.17625 當a=0.951,b=-0.2時, 可得誤差 0.1754225 當a=0.952,b=-0.2時, 可得誤差 0.17464 當a=0.953,b=-0.2時, 可得誤差 0.1739025 當a=0.954,b=-0.2時, 可得誤差 0.17321 當a=0.955,b=-0.2時, 可得誤差 0.1725625 當a=0.956,b=-0.2時, 可得誤差 0.17196 當a=0.957,b=-0.2時, 可得誤差 0.1714025 當a=0.958,b=-0.2時, 可得誤差 0.17089 當a=0.959,b=-0.2時, 可得誤差 0.1704225 當a=0.96,b=-0.2時, 可得誤差 0.17 當a=0.961,b=-0.2時, 可得誤差 0.1696225 當a=0.962,b=-0.2時, 可得誤差 0.16929 當a=0.963,b=-0.2時, 可得誤差 0.1690025 當a=0.964,b=-0.2時, 可得誤差 0.16876 當a=0.965,b=-0.2時, 可得誤差 0.1685625 當a=0.966,b=-0.2時, 可得誤差 0.16841 當a=0.967,b=-0.2時, 可得誤差 0.1683025 當a=0.968,b=-0.2時, 可得誤差 0.16824 當a=0.969,b=-0.2時, 可得誤差 0.1682225 當a=0.97,b=-0.2時, 可得誤差 0.16825 當a=0.971,b=-0.2時, 可得誤差 0.1683225 當a=0.972,b=-0.2時, 可得誤差 0.16844 當a=0.973,b=-0.2時, 可得誤差 0.1686025 當a=0.974,b=-0.2時, 可得誤差 0.16881 當a=0.975,b=-0.2時, 可得誤差 0.1690625 當a=0.976,b=-0.2時, 可得誤差 0.16936 當a=0.977,b=-0.2時, 可得誤差 0.1697025 當a=0.978,b=-0.2時, 可得誤差 0.17009 當a=0.979,b=-0.2時, 可得誤差 0.1705225 當a=0.98,b=-0.2時, 可得誤差 0.171 當a=0.981,b=-0.2時, 可得誤差 0.1715225 當a=0.982,b=-0.2時, 可得誤差 0.17209 當a=0.983,b=-0.2時, 可得誤差 0.1727025 當a=0.984,b=-0.2時, 可得誤差 0.17336 當a=0.985,b=-0.2時, 可得誤差 0.1740625 當a=0.986,b=-0.2時, 可得誤差 0.17481 當a=0.987,b=-0.2時, 可得誤差 0.1756025 當a=0.988,b=-0.2時, 可得誤差 0.17644 當a=0.989,b=-0.2時, 可得誤差 0.1773225 當a=0.99,b=-0.2時, 可得誤差 0.17825 當a=0.991,b=-0.2時, 可得誤差 0.1792225 當a=0.992,b=-0.2時, 可得誤差 0.18024 當a=0.993,b=-0.2時, 可得誤差 0.1813025 當a=0.994,b=-0.2時, 可得誤差 0.18241 當a=0.995,b=-0.2時, 可得誤差 0.1835625 當a=0.996,b=-0.2時, 可得誤差 0.18476 當a=0.997,b=-0.2時, 可得誤差 0.1860025 當a=0.998,b=-0.2時, 可得誤差 0.18729 當a=0.999,b=-0.2時, 可得誤差 0.1886225 當a=1.0,b=-0.2時, 可得誤差 0.19
可以發現, 不管我們現在如何嘗試, 誤差最小也只能達到0.168 出現於a=0.969,b=-0.2時
莫非, 最小的誤差是0.168?
抱着懷疑的心態, 在有點暴力的辦法似乎走不通的情況下, 讓我們來嘗試更加暴力一些的方法,
寫一個regressionv2.py 腳本, 設定a,b每次增加的步長為0.05, 同時循環不同的a和b的取值來尋找最優解
1 # -*- coding: utf-8 -*-
2 #regressionv2.py
3 import numpy 4 x=[1,1.5,2,2.5,3] 5 y=[1,1.2,2,2.2,2.5] 6 minimumError=1000000
7 minParameter=[] 8 for a in numpy.arange(-2, 2, 0.05): 9 for b in numpy.arange(-2,2,0.05): 10 soe=0 #sum of error
11 for i in range(len(x)): 12 soe+=(a*x[i]+b-y[i])*(a*x[i]+b-y[i]) 13 if soe<minimumError: 14 minimumError=soe 15 minParameter=[a,b] 16 #print "當a="+str(a)+",b="+str(b)+"時, 可得誤差",soe
17 print "最小誤差為",minimumError,"出現於minParameter",minParameter
這點計算量, 對於程序來說還是很easy的, 我們的程序很快也給我們快跑結果了
最小誤差為 0.09 出現於minParameter [0.8, 0.2]
終於誤差是我們可以容忍的了(小於0.1),
於是我們得到了第二個符合條件更加精准的數學模型
這個時候讓我們回頭對比一下 方法一的結果,
讓我們也計算一下方法一得到的誤差, 當a=0.8, b=0.18, 它的誤差為0.88
可以發現我們當前的誤差已經離方法一的誤差(0.088)很接近了, 如果想得到方法一的結果, 我們就必須在之前的腳本中使用更小的步長
比如0.01
接着來我們實驗一下, 當我們把步長設為0.01時, 發現腳本幫我們找到了與方法一一樣的結果,
不過,
等一下!
好像我們跑腳本的時候似乎變長了?
對的, 當我們把步長變小時, 程序的計算量就變大了, 於是性能問題就顯示出來了
為了計算這個最優解, 我的腳本在我的電腦上跑了差不多5秒, 計算了160000個不同的取值才得到這個結果.
而之前步長為0.05時, 計算用的時間不會超過1秒, 總共也才計算了6400種不同的取值. 計算的狀態空間(不同的取值)擴大了25倍
可以想象, 如果我們的方程不是二元一次方程, 而是十元一次方法時, 我們的狀態空間勢必將變的更大! 計算所消耗的時間也將會更久!
又或者當我們的取值范圍不是-2到2之間 而是-10000 到10000之間時, 我們的程序還能跑得動嗎?
於是問題來了, 有沒有又快又准的找到最優解的方法?
答案是有的!
終於, 我們可以引出下一章的標題了- 快速尋找最優解!
REF:http://hotmath.com/hotmath_help/topics/line-of-best-fit.html