python機器學習——線性迴歸方法

基本原理:

線性迴歸是機器學習建模中最爲簡單的模型,也是計算起來最爲直觀的模型

所謂線性迴歸,我們要建立的是這樣的模型:對一組數據,每組數據形如$(x_{1},...,x_{n},y)$,我們希望構造一個線性函數$h_{\theta}(X)=\sum_{i=0}^{n}\theta_{i}x_{i}$,使得$h_{\theta}(X)$與$y$的差距最小

對於上述式子,我們給一個記號:$X=(x_{0},...,x_{n})$,爲了體現線性函數中常數項的存在,我們令$x_{0}=1$,因此$X=(1,x_{1},...,x_{n})$,$\theta=(\theta_{0},...,\theta_{n})$

那麼我們把二者都看做列向量(常用的約定),上式實際上在說:$h_{\theta}(X)=\theta^{T}X$

我們約定損失函數爲$L^{2}$損失函數(這裏可以用最大似然估計證明選取該損失函數的合理性,之後有空填坑吧...),即對一組參數$\theta$,損失函數$J(\theta)=\dfrac{1}{2}\sum_{i=1}^{m}(y_{i}-h_{\theta}(X_{i}))^{2}$

這表示一共有$m$組形如$(X_{i},y_{i})$的數據的總損失

那麼我們令$Y=(y_{1},...,y_{m})$(視作一個列向量),$X=(X_{1},...,X_{m})$($X_{i}$爲一個$(n+1)$維列向量,$X$是一個$(n+1)*m$的矩陣)上式即爲:$J(\theta)=\dfrac{1}{2}(X^{T}\theta-Y)^{T}(X^{T}\theta-Y)$(目前前面出現的$X_{i},Y,\theta$三個向量都視作列向量,始終不要忘記這一點)

那麼我們現在想求出$J(\theta)$最小時$\theta$的取值,那麼我們對$J(\theta)$求偏導,得到:

$\dfrac{\partial J(\theta)}{\partial \theta}=\dfrac{1}{2}\dfrac{\partial(\theta^{T}XX^{T}\theta -Y^{T}X^{T}\theta-\theta^{T} XY+Y^{T}Y)}{\partial \theta}$

那麼也就是:(梯度向量如果按列向量解釋):

$\dfrac{\partial J(\theta)}{\partial \theta}=\dfrac{1}{2}(2XX^{T}\theta -XY- XY)=XX^{T}\theta-XY$

對這步推導感興趣的話可以參考多元函數微分學,這裏我們只應用了一些非常簡單的結論,如:

$\dfrac{\partial \alpha^{T} \beta}{\partial \alpha}=\dfrac{\partial \beta^{T}\alpha}{\partial \alpha}=\beta$(當然,由於我們先天規定了梯度向量是行向量還是列向量,所以結果可能是$\beta$或$\beta^{T}$)

欲使$J(\theta)$取得極小值,我們要求各個偏導數爲0,因此我們有:

$XX^{T}\theta-XY=0$,即:$\theta=(XX^{T})^{-1}XY$

但是當我們的$X$很大的時候,無論是矩陣乘法還是矩陣求逆的計算量都太大了(僅考慮一個含有$10000$組$1000$維數據的數據集,其矩陣乘法的運算量就很巨大,更遑論矩陣求逆的計算量了)

梯度下降:

考慮一個一元函數,其在$x_{0}$點處的導數是$f^{'}(x_{0})$,那麼這個導數值決定了這個函數的走勢:如果導數值小於零,那麼我們沿正向前進一小段函數值就會下降,而反之我們沿負向前進一小段函數值就會下降(這一點可以由一元微分公式決定)

那麼對於多元函數也是同理的,但是對於多元函數而言,我們有很多個可以前進的方向,那麼我們選取一個能使函數值下降最快的方向,由多元函數微分學告訴我們,這實際上就是函數的在這一點梯度的方向,所以我們使用的公式是:

$\hat{\theta}=\theta-\alpha \dfrac{\partial J(\theta)}{\partial \theta}$

這裏的$\alpha$被稱爲學習率,表示我們具體會在梯度方向上前進多遠

當然,我們也可以具體到每個參數:

$\hat{\theta_{j}}=\theta_{j}-\alpha \dfrac{\partial J(\theta)}{\partial \theta_{j}}$

而我們總是有:

$\dfrac{\partial J(\theta)}{\partial \theta_{j}}=\dfrac{\partial \sum_{i=1}^{n}(h_{\theta}(X_{i})-Y_{I})^{2})}{2\partial \theta_{j}}=\dfrac{\sum_{i=1}^{m}(h_{\theta}(X_{i})-Y_{i}) \partial (h_{\theta}(X_{i})-Y_{i})}{\partial  \theta_{j} }$

同時我們最開始就有:

$h_{\theta}(X_{i})=\sum_{k=0}^{n}\theta_{k}x_{ik}$,其中$X_{i}=(1,x_{i1},...,x_{in})$

於是我們就得到了表達式:

 $\dfrac{\partial (h_{\theta}(X_{i})-Y_{i})}{\partial  \theta_{j}}=x_{ij}$

這樣我們就有:

$\dfrac{\partial J(\theta)}{\partial \theta_{j}}=\sum_{i=1}^{m}(h_{\theta}(X_{i})-Y_{i})x_{ij}$

於是最後的迭代式即爲:

$\hat{\theta_{j}}=\theta_{j}-\alpha \sum_{i=1}^{m}(h_{\theta}(X_{i})-Y_{i})x_{ij}$

(當然你也可以寫成$\hat{\theta_{j}}=\theta_{j}+\alpha \sum_{i=1}^{m}(Y_{i}-h_{\theta}(X_{i}))x_{ij}$,但這就是個人喜好的形式問題了)

在迭代過程中,一般我們設置一個邊界,當相鄰兩次迭代的差距小於這個邊界時就認爲收斂。

在梯度下降的過程中,由於初值和學習率$\alpha$都是人工設定的,而梯度下降法對這些東西又是敏感的,因爲存在全局最小值/局部極小值/鞍點(梯度爲0但甚至不爲局部極小值的點,在一元函數中可以考察$y=x^{3}$在$x=0$的情況)的影響,同時如果學習率過大會在最小值周圍跳躍而無法收斂,如果過小則學習速度太慢。

代碼實現:

 

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def my_linear(X,Y,theta,alpha,siz,eps,dep):
    theta=(np.matrix([email protected]).I)@[email protected]
    return np.array(theta.T)

    delta=1.0

    while delta>eps:
        #temp=Y-(theta @ X)
        #new_theta=theta.copy()

        #for i in range(0,siz):
        #    new_theta+=alpha*temp[0,i]*X[:,i]
        
        new_theta=(theta.T-alpha*([email protected]@theta.T-[email protected])).T
        
        delt=new_theta-theta
        delta=(delt.T @ delt)[0,0]

        theta=new_theta

    return theta

x=np.arange(0.,10.,0.2)
siz=len(x)
y=2*x+0.5+np.random.randn(siz)
y=np.vstack([y])
x0=np.full(siz,1.)
X=np.vstack([x0,x])
theta=np.full(2,1.)
theta=np.vstack([theta])

my_theta= my_linear(X,y,theta,1e-3,siz,1e-3,0)

predict_y=(my_theta@X)

plt.plot(x,y[0,:],'g*')
plt.plot(x,predict_y[0,:],'r')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

用numpy來實現這樣的功能,給出了三種實現方式,一種是直接使用矩陣求逆(在numpy中,對於矩陣A和B,矩陣轉置是A.T,矩陣的逆是A.I,矩陣乘法是A@B)來實現,可以看到這樣的效果是最好的;

另兩種則是兩種不同的迭代方式,一種是通過矩陣計算直接整體迭代,另一種則是逐個迭代,這兩種方式其實是等價的

需要注意的是,這裏線性迴歸的表現好壞和初值與學習率的選取有直接關係,可以看到如果學習率選擇0.1那麼整個過程無法收斂,而這裏取的初值會讓迭代法最後得到的結果處於一個局部極小值或鞍點而無法得到全局最小值(初值換成0.1的話能取得明顯更好的效果)

當然,這樣的常用方法也是有庫函數的,我們也可以使用不同的包

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

x=np.arange(0.,10.,0.2)
siz=len(x)
y=2*x+0.5+np.random.randn(siz)
Y=np.vstack([y]).T
X=np.vstack([x]).T

l=LinearRegression()
l.fit(X,Y)

plt.scatter(x,y,c='g')
plt.plot(X,l.predict(X),c='r')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

如果我們使用sklearn的話,我們可以用LinearRegression構造一個線性模型,然後直接把數據fit進去(這裏數據的解釋方法是這樣的:每個$X_{i}$視作行向量,對應於一個$y_{i}$,這樣拼起來之後得到的$Y$應當是一個列向量,而$X$則是一個矩陣,行數爲數據個數,列數爲維數,這是常用記法,和前面推導中使用的記法有一定區別)

實際上,我們生成一個LinearRegression對象之後,可以調用fit方法按最小化均方誤差作爲損失函數來訓練一個線性模型,而訓練結果被儲存在成員變量coef__中,即如果想查看訓練出的參數,我們應該調用l.coef__,而如果我們想對一組數據集進行預測,我們要是用的是l.predict。

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

x=np.arange(0.,10.,0.2)
siz=len(x)
y=2*x+0.5+np.random.randn(siz)
Y=np.vstack([y]).T
X=np.vstack([x]).T

slope,intercept,r_value,p_value,slope_std_error=stats.linregress(x,y)

plt.scatter(x,y,c='g')
plt.plot(x,slope*x+intercept,c='r')
plt.show()

另一方面,我們也可以用scipy裏的stats來實現線性迴歸,問題是這裏只支持一維線性迴歸,但是可以同時算出r,p,標準差,得到的slope是斜率,intercept是截距。

小結與改進:

利用普通最小二乘法進行多元線性迴歸時,在出現數據共線性等問題時會有較大誤差,因此在改進時可以選用偏最小二乘法——對一組數據$(x_{i1},...,x_{in},y_{i})$,令$\hat{x_{ij}}=\dfrac{x_{ij}-\overline{x_{j}}}{s_{j}}$,$\hat{y_{i}}=\dfrac{y_{i}-\overline{y}}{s_{y}}$(即每一維減去自己的均值後再除以自己的標準差,這樣可以把一組數據變成均值爲$0$,方差爲$1$的數據),然後再進行最小二乘法,這樣得到的結果效果一般更優

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章