快速掌握簡單線性回歸:從理論到實踐

在統(tǒng)計學(xué)中,線性回歸是利用稱為線性回歸方程的最小二乘函數(shù)對一個或多個自變量和因變量之間關(guān)系進行建模的一種回歸分析。這種函數(shù)是一個或多個稱為回歸系數(shù)的模型參數(shù)的線性組合。當因變量和自變量之間高度相關(guān)時,我們就可以使用線性回歸來對數(shù)據(jù)進行預(yù)測。

一個帶有一個自變量的線性回歸方程代表一條直線,為了方便理解,今天我們就拿只有一個自變量的線性回歸方程來探討簡單線性回歸。

今天,我們有三個目標:

  1. 使用Scikit-Learn完成一項簡單線性回歸任務(wù);
  2. 理解簡單線性回歸的原理并推導(dǎo)其公式;
  3. 從零搭建我們的簡單線性回歸模型工具。

一、三分鐘快速實現(xiàn)線性回歸

sklearn庫提供了大量機器學(xué)習模型的訓(xùn)練工具,包括各種各樣的線性回歸模型。今天我們用它來演示簡單線性回歸模型的使用。

import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets, linear_model
from sklearn.cross_validation import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# 導(dǎo)入糖尿病數(shù)據(jù)
diabetes = datasets.load_diabetes()

# 僅使用第三個特征
# sklearn接受的輸入中,特征集是2維數(shù)據(jù),
# 所以我們要增加一個維度,將長度為n的一維向量,變成n*1的二維向量
X = diabetes.data.copy()[:, np.newaxis, 2]
y = diabetes.target.copy()

# 將數(shù)據(jù)分為訓(xùn)練集和測試集
X_train = X[:-20]
X_test = X[-20:]
y_train = diabetes.target[:-20]
y_test = diabetes.target[-20:]
# sklearn提供了分割訓(xùn)練集與測試集的方法,不過這次為了讓大家得到一致的結(jié)果,
# 我們直接取最后20個樣本作為測試集(我們也可以設(shè)置train_test_split方法中
# 的random_state參數(shù),用來保證每次實驗的訓(xùn)練集和測試集是一致的。)
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.05)

# 創(chuàng)建線性回歸模型對象
reg = linear_model.LinearRegression(fit_intercept=True)

# 訓(xùn)練模型
reg.fit(X_train, y_train)

# 預(yù)測測試數(shù)據(jù)
y_pred = reg.predict(X_test)

# 打印系數(shù)
print('coefficients: ', reg.coef_)
print('intercept: ', reg.intercept_)

# 打印均方誤差
print('Mean squared error: ', mean_squared_error(y_test, y_pred))

# 打印R2分數(shù)
print('Variance score: ', r2_score(y_test, y_pred))

# 畫圖
plt.scatter(X_test, y_test, color='green')
plt.plot(X_test, y_pred, '-r', linewidth=3)
plt.show()

輸出為:

coefficients:  [938.23786125]
intercept:  152.91886182616167
Mean squared error:  2548.0723987259703
Variance score:  0.47257544798227136
image

R^2分數(shù)僅有0.47,效果不是特別好,但是沒關(guān)系,我們主要是以這個例子演示一下如何快速操作簡單線性回歸。

二、簡單線性回歸的原理

我們可能聽過最小二乘法(OLS)這個詞,它就是我們理解簡單線性回歸模型的關(guān)鍵。最小二乘法,其實就是最小化殘差的平方和。

舉個例子,有兩組數(shù):

  • x = [1, 3, 5]
  • y = [5, 6, 7]

現(xiàn)在我們要找到從x到y(tǒng)的映射關(guān)系,即如何對x中的3個數(shù)進行轉(zhuǎn)換,近似得到y(tǒng)中的3個數(shù)。假設(shè)x和y之間存在這樣的關(guān)系:

\hat{y_i} = ax_i + b

其中\hat{y}是我們的y的預(yù)測值。很容易理解的一點是,我們的預(yù)測不可能總是那么完美,因此我們預(yù)測的數(shù)值與實際數(shù)值之間的差異,就是殘差,即:

error = y - \hat{y}

我們的殘差平方和,就是:

RSS(SSE) = \sum_{i=1}^{n}{(y_i-\hat{y_i})^2} = \sum_{i=1}^{n}{[y_i-(ax_i+b)]^2}

我們的目的是使得預(yù)測值盡可能地接近實際值,即殘差越小越好。也就是說,當我們找到一組(a, b),使得殘差平方和最小時,就說明在某種程度上,我們找到了預(yù)測效果最好的簡單線性回歸模型。

為什么要用殘差的平方和,而不是殘差呢?因為殘差有正有負,他們的和不能代表真正的誤差;那為什么不用殘差的絕對值呢?其實殘差的絕對值是一個很好的指標。但是對機器和數(shù)學(xué)來說,計算、表示殘差的平方和時,遠比計算和表示殘差的絕對值要來得方便;同時,由于平方和的數(shù)據(jù)形態(tài)是一個U型的曲線,方便我們通過求導(dǎo)、隨機梯度下降等方式得到最小值。

那么接下來我們就來求解我們的系數(shù)(a, b),如前邊所言,殘差平方和的數(shù)據(jù)形態(tài)是一個U型曲線,因此當且僅當導(dǎo)數(shù)為0時,我們得到最低點。

1. 求解b

J(a, b)代表損失函數(shù),在這個例子中,就是我們上邊提到的殘差平方和。先對b求導(dǎo),并使導(dǎo)數(shù)等于0:

\begin{aligned} &J(a,b) = \sum_{i=1}^{n}{[y_i-(ax_i+b)]^2}\\ &=> \frac{\partial{J(a,b)}}{\partial}=\sum_{i=1}^{n}{2[y_i-(ax_i+b)](-1)}=0\\ &=> \sum_{i=1}^{n}{(y_i-ax_i-b)}=0\\ &=> \sum_{i=1}^{n}{y_i}-a\sum_{i=1}^{n}{x_i}-\sum_{i=1}^{n}=0\\ &=> b=\frac{1}{n}\sum_{i=1}^{n}{y_i}-\frac{1}{n}a\sum_{i=1}^{n}{x_i}\\ &=> b=\overline{y}-a\overline{x} \end{aligned}

2. 求解a

\begin{aligned} &\frac{\partial{J(a,b)}}{\partial{a}}=\sum_{i=1}^{n}{2[y_i-(ax_i+b)](-x_i)}=0\\ &=>\sum_{i=1}^{n}{(y_ix_i-ax_i^2-bx_i)}=0\\ b=\overline{y}-a\overline{x}\quad&=>\quad\sum_{i=1}^{n}{(y_ix_i-ax_i^2-\overline{y}x_i+a\overline{x}x_i)}=0\\ &=>\sum_{i=1}^{n}{(y_ix_i-\overline{y}x_i)}-a\sum_{i=1}^{n}{(x_i^2-\overline{x}x_i)}=0\\ &=>a=\frac{\sum_{i=1}^{n}{(y_ix_i-\overline{y}x_i)}}{\sum_{i=1}^{n}{(x_i^2-\overline{x}x_i)}}\\ 又&\quad\sum_{i=1}^{n}x_i\overline{y}=n\overline{x}\overline{y},\quad\sum_{i=1}^{n}x_i\overline{x}=n\overline{x}^2\\ =>a&=\frac{\sum_{i=1}^{n}{(y_ix_i-\overline{y}x_i-\overline{x}y_i+\overline{x}\overline{y})}}{\sum_{i=1}^{n}{(x_i^2-\overline{x}x_i-\overline{x}x_i+\overline{x}^2)}}\\ &=\frac{\sum_{i=1}^{n}{(x_i-\overline{x})(y_i-\overline{y})}}{\sum_{i=1}^{n}{(x_i-\overline{x})^2}} \end{aligned}

這兩個推導(dǎo)過程,不熟悉的同學(xué)最好還是在草稿紙上演算一下,有不明白的地方可以留言溝通。

現(xiàn)在我們成功地推導(dǎo)出了(a, b)的求解公式,并且轉(zhuǎn)換成了比較友好的形式,即:

\begin{aligned} a&=\frac{\sum_{i=1}^{n}{(x_i-\overline{x})(y_i-\overline{y})}}{\sum_{i=1}^{n}{(x_i-\overline{x})^2}}\\ \quad\\ b&=\overline{y}-a\overline{x} \end{aligned}

那么接下來,我們就嘗試將求解公式實現(xiàn)一下。需要注意的是,如果所有的點分布在一條與x軸垂直的直線上,那對于所有點的橫坐標,有x_i=\overline{x},則我們的求解公式是無解的,因為它的斜率是無限的。

三、從零搭建一個簡單線性回歸模型工具

import numpy as np
import matplotlib.pyplot as plt


class LinearRegression():
    def __init__(self):
        self.coef_ = 0
        self.intercept_ = 0
        self.X = None
        self.y = None
        self.y_pred = None
        self.sse = 0
        self.mse = 0
        self.rmse = 0
        self.mae = 0
        self.ssr = 0
        self.sst = 0
        self.r2 = 0

    def input_data(self, x, y):
        self.X = np.array(x)
        self.y = np.array(y)

    def solve(self):
        x = self.X
        y = self.y
        self.coef_ = sum((x - x.mean()) * (y - y.mean())) / sum((x - x.mean())**2)
        self.intercept_ = y.mean() - self.coef_ * x.mean()
        self.y_pred = np.array(x * self.coef_ + self.intercept_)

    def predict(self, x_new):
        return self.coef_ * x_new + self.intercept_

    def plot(self):
        plt.figure(figsize=(10, 6))
        plt.scatter(self.X, self.y, marker='+', color='green')
        plt.plot(self.X, self.y_pred, '-r')
        plt.text(1, 7.9, 'a:  {0:.2f}'.format(self.coef_), weight='bold', color='black', fontsize=16)
        plt.text(1, 7.7, 'b:  {0:.2f}'.format(self.intercept_), weight='bold', color='black', fontsize=16)
        plt.text(1, 7.5, 'R2: {0:.2f}'.format(self.r2), weight='bold', color='black', fontsize=16)
        plt.text(1, 7.3, 'MSE:  {0:.2f}'.format(self.mse), weight='bold', color='black', fontsize=16)
        plt.show()

    def evaluate(self):
        self.sse = sum((self.y - self.y_pred) ** 2)
        self.mse = sum((self.y - self.y_pred) ** 2) / len(self.X)
        self.rmse = np.sqrt(self.mse)
        self.mae = np.mean(np.abs(self.y - self.y_pred))
        self.ssr = sum((self.y_pred - np.mean(self.y)) ** 2)
        self.sst = sum((self.y - np.mean(self.y)) ** 2)
        self.r2 = self.ssr / self.sst

在這個類中,我們實現(xiàn)了數(shù)據(jù)輸入、求解、預(yù)測、畫圖、模型評估的過程。關(guān)于模型評估,我們稍微說明一下:

  • SSE: 殘差平方和,SSE=\sum_{i=1}^{n}{(y_i-\hat{y_i})^2}

  • MSE: 均方差,MSE=\frac{1}{n}\sum_{i=1}^{n}{(y_i-\hat{y_i})^2};

  • RMSE: 均方根,RMSE=\sqrt{\frac{1}{n}\sum_{i=1}^{n}{(y_i-\hat{y_i})^2}}
  • MAE: 平均絕對誤差,MAE=\frac{1}{n}\sum_{i=1}^{n}{|y_i-\hat{y_i}|};
  • SSR: Sum of squares of the regression,MSE=\sum_{i=1}^{n}{(\hat{y_i}-\overline{y_i})^2};
  • SST: Total sum of squares,MSE=\sum_{i=1}^{n}{(y_i-\overline{y_i})^2};
  • R^2: 確定系數(shù),取值范圍[0,1],取值越大,表明擬合效果越好。R^2=\frac{SSR}{SST}=1-\frac{SSE}{SST}。

那么接下來,我們拿一組數(shù)據(jù)測試一下。

# 訓(xùn)練數(shù)據(jù)
X = np.array([1, 3, 5, 7])
y = np.array([5, 6, 7, 8])

# 創(chuàng)建模型
lr = LinearRegression()
lr.input_data(X, y)

# 求解
lr.solve()
lr.evaluate()

# 預(yù)測
x = 10
print('當X = 10時,y =', lr.predict(x))

# 畫圖
lr.plot()

輸出為:

當X = 10時,y = 9.5
image

看來我們提供的數(shù)據(jù)太過完美,模型的預(yù)測和實際值完美重合。我們再加點料試試:

# 訓(xùn)練數(shù)據(jù)
X = np.array([1, 3, 5, 7, 4, 6, 8])
y = np.array([5, 6, 7, 8, 6, 8, 7])

# 創(chuàng)建模型
lr = LinearRegression()
lr.input_data(X, y)

# 求解
lr.solve()
lr.evaluate()

# 預(yù)測
x = 10
print('當X = 10時,y =', lr.predict(x))

# 畫圖
lr.plot()

輸出為:

當X = 10時,y = 8.737704918032787
image

R^2為0.73,表現(xiàn)還可以哦。

好了,到現(xiàn)在為止,我們就成功地從零搭建了一個簡單線性回歸模型,大家有什么疑問嗎?歡迎在下方留言!

我會經(jīng)常為大家分享一些關(guān)于數(shù)據(jù)分析、數(shù)據(jù)挖掘、機器學(xué)習、爬蟲等相關(guān)的原創(chuàng)內(nèi)容,歡迎關(guān)注 和轉(zhuǎn)發(fā)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容