機器學習回顧篇(3):線性迴歸

奧辰發表於2019-07-21

1 引言

線性迴歸演算法應該是大多數人機器學習之路上的第一站,因為線性迴歸演算法原理簡單清晰,但卻囊括了擬合、優化等等經典的機器學習思想。去年畢業時參加求職面試就被要求介紹線性迴歸演算法,但由於當初過於追求神經網路、SVN、無監督學習等更加高大尚的演算法,反而忽略了線性迴歸這些基礎演算法,當時給出的答案實在是差強人意。

這一篇關於線性迴歸的總結我也猶豫過要不要寫,如果你看了我上兩篇關於最小二乘和梯度下降演算法介紹的部落格,你就會發現,關於最小二乘法和梯度下降演算法的介紹都是以線性迴歸為例展開,所以,綜合來說,之前兩篇博文對一元線性迴歸還是多元線性迴歸演算法都已經有了還算全面的總結,再寫一篇關於線性迴歸的文章總感覺有些多餘。不過,為了讓我對機器學習的總結更加系統,還是決定專門總結一下。

2 什麼是線性迴歸

說到線性迴歸,我們得先說說迴歸與分類、線性與非線性這些概念的區別。

迴歸和分類都是有監督學習(機器學習分有監督學習和無監督學習)中的概念。從演算法的目標或者作用上看,分類的目標就如同字面上的意思,就是分類,將資料分門別類,而且類別是有限的(數學上稱為離散的)。但迴歸演算法不同,迴歸演算法的目標是預測,說詳細些就是從已有資料和結果中獲取規律,對其它資料的位置結果進行預測,預測結果的數量數無限的(數學上叫連續的)。舉個例子我們可以根據樓房的建材、面積、用途將茅草房、普通住房、廠房等等有限的幾類,這就是分類;另外,我們可以根據房屋面積、地段、裝修等因素對樓房房價進行預測,預測結果有無限種可能,可能是10000元/平米,也能是10001.1元/平米——這就是迴歸的過程。

對於線性和非線性的解釋,我至今還沒有見到過一個讓我覺得滿意的數學定義,所以,我也從直觀認識上說說我的看法。我們高中時學習函式就是從一元一次函式學起的,一元一次函式在數學上可以表示為:

\[f(x)={{\theta }_{0}}+{{\theta }_{1}}x\]

在二維座標軸上,其表現為一條直線。如果是三維空間中的二元一次函式,數學上表示為:

\[f(x)={{\theta }_{0}}+{{\theta }_{1}}{{x}_{1}}+{{\theta }_{2}}{{x}_{2}}\]

其在空間上圖形為一平面。以此類推,在更高維(n維)的平面上,多元一次函式表示式為:

\[f(x)={{\theta }_{0}}+{{\theta }_{1}}{{x}_{1}}+{{\theta }_{2}}{{x}_{2}}+\ldots +{{\theta }_{n}}{{x}_{n}}\]

在多維空間上,其圖形我們稱為為超平面。

如果一個資料模型能夠用上述二維直線、三維平面或者更多維空間上的超平面去擬合,我們就可以說這個模型是線性模型。

最後,綜合上面內容總結一下什麼是西安線性迴歸:

線性迴歸(Linear Regression)是一種通過屬性的線性組合來進行預測的迴歸模型,其目的是找到一條直線或者一個平面或者更高維的超平面,使得預測值與真實值之間的誤差最小化。

3 最優解

上面提到,線性迴歸的目的就是找到一個線性模型,是的預測值與真實值之間的誤差最小,這就是一個優化的問題。為什麼要優化呢?看看下圖:

正如你眼前所見,圖中的點代表資料,能夠對這些資料點進行大概擬合的直線不止一條,到底哪一條才是最好呢?以上圖中資料為例,假設此時模型為${y}'=f(x)={{\theta }_{0}}+{{\theta }_{1}}{{x}_{1}}$,影象如下所示,我們要對其擬合程度進行評價:

 

$A({{x}_{i}},{{y}_{i}})$為資料集中某一點,使用模型預測時,結果為${A}'({{x}_{i}},{{{{y}'}}_{i}})$。我們用高中時學過,對於${A}$${A}'$之間的誤差,我們可以用它們之間的歐氏距離的平方來表示:

\[E(A,{A}')={{({{x}_{i}}-{{x}_{i}})}^{2}}+{{({{y}_{i}}-{{y}_{i}})}^{2}}={{({{\theta }_{0}}+{{\theta }_{1}}{{x}_{i}}-{{y}_{i}})}^{2}}\]

這是對一個點的擬合程度評價,但資料集中可不止$A({{x}_{i}},{{y}_{i}})$一個資料點,所以,我們需要對每個點進行誤差評估,然後求和,也就是誤差平方和作為這個線性模型對資料集的總體擬合程度評估:

\[J({{\theta }_{0}},{{\theta }_{1}})=\sum\limits_{i=1}^{m}{{{({{\theta }_{0}}+{{\theta }_{1}}{{x}_{i}}-{{y}_{i}})}^{2}}}\]

我們將$J({{\theta }_{0}},{{\theta }_{1}})$成為損失函式,也有資料中稱為目標函式。這裡,${{\theta }_{0}},{{\theta }_{1}}$是模型中$x$的引數。在不同的模型中,${{\theta }_{0}},{{\theta }_{1}}$取不同值,我們要做的就是求目標函式$J({{\theta }_{0}},{{\theta }_{1}})$取最小值時的${{\theta }_{0}},{{\theta }_{1}}$確切值,換句話說就是求$J({{\theta }_{0}},{{\theta }_{1}})$的最優解問題。

關於求最優解問題,最常用的演算法就是最小二乘法和梯度下降法,這兩種方法在前兩篇博文中都有詳細介紹,如果你尚不清楚其中原理和過程,還是去看看吧,這很重要,也很基礎。

上面說的是一元線性迴歸模型,對於多元線性迴歸模型,原理也是一樣的,只不過需要求解的引數不止${{\theta }_{0}},{{\theta }_{1}}$兩個了就是,這裡不再多說。下面我們用程式碼實現一元線性迴歸模型。

4 程式碼實現

以下程式碼參考《深度學習之Pytorch》一書中內容,程式碼採用Python的pytorch實現。

在實現模型之前,首先我們要製造一個資料集,在[0, 10]區間範圍內,隨機生成100個數:

import torch
x = torch.unsqueeze(torch.linspace(0, 10, 100), dim=1)
print(x)
輸出為:
tensor([[ 0.0000],
[ 0.1010],
[ 0.2020],
……
[ 9.7980],
[ 9.8990],
[10.0000]])

輸出的內容有100行,這就是一個列向量,我們以則100個數作為資料集中的自變數,也就是X,假設我們需要擬合的模型為:$y=f(x)=0.5x+15$,為了更加符合真實資料集中隨機誤差的效果,我們在生成y的時候,加上一些服從高斯分佈隨機誤差:

y = 0.5 * x + 15 + torch.rand(x.size())
print(x)

輸出結果為:

tensor([[15.0477],

[15.0557],

[15.3653],

……

[20.7046],

[20.1751]])

在二維座標軸上,這些點如下所示:

資料有了,我們可以開始搭建我們的模型了:

class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1, 1)  # 因為是一元線性迴歸,所以輸入和輸出的維度都是1
    def forward(self, x):
        out = self.linear(x)
        return out


if torch.cuda.is_available():
    model = LinearRegression().cuda()
else:
    model = LinearRegression()

criterion = nn.MSELoss()  # 這裡選擇使用誤差平方和作為損失函式,就是上文中說的函式J
optimizer = torch.optim.SGD(model.parameters(), lr=1e-2)  # 定義梯度下降演算法進行優化


num_epochs = 1000  # 定義迭代次數
for epoch in range(num_epochs):
    if torch.cuda.is_available():
        inputs = Variable(x).cuda()
        target = Variable(y).cuda()
    else:
        inputs = Variable(x)
        target = Variable(y)
    # 向前傳播
    out = model(inputs)
    loss = criterion(out, target)
    # 向後傳播
    optimizer.zero_grad()  # 注意每次迭代都需要清零
    loss.backward()
    optimizer.step()
    if (epoch + 1) % 20 == 0:
        print('Epoch[{}/{}], loss:{:.6f}'.format(epoch + 1, num_epochs, loss.item()))
model.eval()
if torch.cuda.is_available():
    predict = model(Variable(x).cuda())
    predict = predict.data.cpu().numpy()
else:
    predict = model(Variable(x))
    predict = predict.data.numpy()
plt.plot(x.numpy(), y.numpy(), 'ro', label='Original Data')
plt.plot(x.numpy(), predict, label='Fitting Line')
plt.show()
print(str(model.state_dict()['linear.weight']) + "\t" + str(model.state_dict()['linear.bias']))

訓練出來的模型效果如下所示:

程式碼最後一行輸出為:

tensor([[0.5206]], device='cuda:0') tensor([15.3803], device='cuda:0')

也就是說,模型訓練結果中,${{\theta }_{0}},{{\theta }_{1}}$分別為15.3803和0.5206.

 

相關文章