模型之母:簡單線性回歸的程式碼實現
- 2019 年 11 月 4 日
- 筆記
模型之母:簡單線性回歸的程式碼實現
關於作者:餅乾同學,某人工智慧公司交付開發工程師/建模科學家。專註於AI工程化及場景落地,希望和大家分享成長中的專業知識與思考感悟。
0x00 前言
在《模型之母:簡單線性回歸&最小二乘法》中,我們從數學的角度理解了簡單線性回歸,並且推導了最小二乘法。
本文內容完全承接於上一篇,我們來以程式碼的方式,實現簡單線性回歸。話不多說,碼起來
0x01 簡單線性回歸演算法的實現
首先我們自己構造一組數據,然後畫圖
# 首先要計算x和y的均值 x_mean = np.mean(x) y_mean = np.mean(y) # a的分子num、分母d num = 0.0 d = 0.0 for x_i,y_i in zip(x,y): # zip函數打包成[(x_i,y_i)...]的形式 num = num + (x_i - x_mean) * (y_i - y_mean) d = d + (x_i - x_mean) ** 2 a = num / d b = y_mean - a * x_mean
下面我們就可以根據樣本真實值,來進行預測。
實際上,我們是假設線性關係為: 這根直線,然後再根據最小二乘法算a、b的值。我們還可以假設為二次函數:。可以通過最小二乘法算出a、b、c
實際上,同一組數據,選擇不同的f(x),即模型,通過最小二乘法可以得到不一樣的擬合曲線。
不同的數據,更可以選擇不同的函數,通過最小二乘法可以得到不一樣的擬合曲線。
下面讓我們回到簡單線性回歸。我們直接假設是一條直線,模型是:
根據最小二乘法推導求出a、b的表達式:
下面我們用程式碼計算a、b:
y_hat = a * x + b plt.scatter(x,y) # 繪製散點圖 plt.plot(x,y_hat,color='r') # 繪製直線 plt.axis([0,6,0,6]) plt.show()
在求出a、b之後,可以計算出y的預測值,首先繪製模型直線:
y_hat = a * x + b plt.scatter(x,y) # 繪製散點圖plt.plot(x,y_hat,color='r') # 繪製直線plt.axis([0,6,0,6])plt.show()
然後進行預測:
x_predict = 6 y_predict = a * x_predict + b print(y_predict
5.2
0x02 向量化運算
我們注意到,在計算參數a時:
# a的分子num、分母d num = 0.0 d = 0.0 for x_i,y_i in zip(x,y): # zip函數打包成[(x_i,y_i)...]的形式 num = num + (x_i - x_mean) * (y_i - y_mean) d = d + (x_i - x_mean) ** 2 a = num / d
我們發現有這樣一個步驟:向量w和向量v,每個向量的對應項,相乘再相加。其實這就是兩個向量「點乘」
這樣我們就可以使用numpy中的dot運算,非常快速地進行向量化運算。
總的來說:
向量化是非常常用的加速計算的方式,特別適合深度學習等需要訓練大數據的領域。
對於 y = wx + b, 若 w, x都是向量,那麼,可以用兩種方式來計算,第一是for循環:
y = 0 for i in range(n): y += w[i]*x[i] y += b
另一種方法就是用向量化的方式實現:
y = np.dot(w,x) + b
二者計算速度相差幾百倍,測試結果如下:
import numpy as np import time a = np.random.rand(1000000) b = np.random.rand(1000000) tic = time.time() c = np.dot(a, b) toc = time.time() print("c: %f" % c) print("vectorized version:" + str(1000*(toc-tic)) + "ms") c = 0 tic = time.time() for i in range(1000000): c += a[i] * b[i] toc = time.time() print("c: %f" % c) print("for loop:" + str(1000*(toc-tic)) + "ms")
c: 249981.256724 vectorized version:0.998973846436ms c: 249981.256724 for loop:276.798963547ms
對於獨立的樣本,用for循環串列計算的效率遠遠低於向量化後,用矩陣方式並行計算的效率。因此:
只要有其他可能,就不要使用顯示for循環。
0x03 自實現的工程文件
3.1 程式碼
還記得我們之前的工程文件嗎?創建一個SimpleLinearRegression.py,實現自己的工程文件並調用
import numpy as np class SimpleLinearRegression: def __init__(self): """模型初始化函數""" self.a_ = None self.b_ = None def fit(self, x_train, y_train): """根據訓練數據集x_train,y_train訓練模型""" assert x_train.ndim ==1, "簡單線性回歸模型僅能夠處理一維特徵向量" assert len(x_train) == len(y_train), "特徵向量的長度和標籤的長度相同" x_mean = np.mean(x_train) y_mean = np.mean(y_train) num = (x_train - x_mean).dot(y_train - y_mean) # 分子 d = (x_train - x_mean).dot(x_train - x_mean) # 分母 self.a_ = num / d self.b_ = y_mean - self.a_ * x_mean return self def predict(self, x_predict): """給定待預測數據集x_predict,返回表示x_predict的結果向量""" assert x_predict.ndim == 1, "簡單線性回歸模型僅能夠處理一維特徵向量" assert self.a_ is not None and self.b_ is not None, "先訓練之後才能預測" return np.array([self._predict(x) for x in x_predict]) def _predict(self, x_single): """給定單個待預測數據x_single,返回x_single的預測結果值""" return self.a_ * x_single + self.b_ def __repr__(self): """返回一個可以用來表示對象的可列印字元串""" return "SimpleLinearRegression()"
3.2 調用
下面我們在jupyter中調用我們自己寫的程式:
首先創建一組數據,然後生成SimpleLinearRegression()
的對象reg1,然後調用一下
from myAlgorithm.SimpleLinearRegression import SimpleLinearRegression x = np.array([1.,2.,3.,4.,5.]) y = np.array([1.,3.,2.,3.,5,]) x_predict = np.array([6]) reg = SimpleLinearRegression() reg.fit(x,y)
輸出:SimpleLinearRegression()
reg.predict(x_predict) reg.a_ reg.a_
輸出:array([5.2]) 0.8 0.39999999999999947
y_hat = reg.predict(x) plt.scatter(x,y) plt.plot(x,y_hat,color='r') plt.axis([0,6,0,6]) plt.show()
0xFF 總結
在本篇文章中,我們實現了簡單線性回歸演算法的程式碼,並且使用了向量化運算,事實證明,向量化運算能夠提高運算效率。
同時我們發現,只要數學公式推導清楚了,實際寫程式碼時沒有太多難度的。
那麼我們思考一個問題,在之前的kNN演算法(分類問題)中,使用分類準確度來評價演算法的好壞,那麼回歸問題中如何評價好壞呢?