|
|
4 months ago | |
|---|---|---|
| .. | ||
| solution | 4 months ago | |
| README.md | 4 months ago | |
| assignment.md | 7 months ago | |
| notebook.ipynb | 7 months ago | |
README.md
使用 Scikit-learn 建立迴歸模型:四種迴歸方式
初學者筆記
當我們想要預測一個數值(例如房價、溫度或銷售額)時,會使用線性迴歸。它透過尋找一條最能代表輸入特徵與輸出之間關係的直線來運作。
在本課程中,我們將先著重理解這個概念,再進一步探討更進階的迴歸技術。

圖解由 Dasani Madipalli 提供
課前小測驗
本課程也提供 R 語言版本!
介紹
到目前為止,你已經探索過什麼是迴歸,並使用整個課程將會用到的南瓜價格資料集做範例。你也已經運用 Matplotlib 視覺化這些資料。
現在你已經準備深入了解為機器學習用的迴歸。雖然視覺化讓你可以理解資料,但機器學習的真正威力在於_訓練模型_。模型是透過歷史資料訓練,以自動捕捉資料間的依賴關係,並讓你得以對未見過的新資料進行結果預測。
本課程會介紹兩種迴歸方法:基本線性迴歸_與_多項式迴歸,並略談其背後的數學原理。這些模型將讓我們依據不同輸入資料預測南瓜價格。
🎥 點擊上方圖片,觀看線性迴歸的短片概述。
本課程假設讀者數學基礎不深,並致力於讓來自其他背景的學生也能輕鬆理解,請留意筆記、🧮 計算提示、圖示與其他助學工具。
前置條件
你現在應該已熟悉我們所探討的南瓜資料結構。本課程的 notebook.ipynb 檔案中已預載並預先清理好資料。該檔案將南瓜價格以每蒲式耳價格顯示於新的資料框中。請確保可於 Visual Studio Code 的內核中順利執行這些筆記本。
準備工作
提醒你,載入這些資料是為了向它提出問題:
- 什麼時候買南瓜最好?
- 一箱迷你南瓜價格會是多少?
- 我應該買半蒲式耳籃裝還是一箱 1 1/9 蒲式耳盒裝的?
讓我們繼續深入挖掘這些資料。
前一課你建立了一個 Pandas 資料框,並用原始資料集的一部分填充,將價格標準化為每蒲式耳價格。但這樣只能得到約 400 筆資料,且只涵蓋秋季的幾個月。
請查看本課程隨課程提供筆記本中預載的資料。資料已預載,且已繪製初始散點圖以顯示月份。也許我們可以透過更進一步的清理,來了解資料的本質。
線性迴歸線
正如你在第一課學到的,線性迴歸的目標是能繪製一條線:
- 展現變數關係。展現變數間的關係
- 做出預測。準確預測新資料點相對於此線的位置
最常用的做法是最小平方法迴歸(Least-Squares Regression)畫出這樣的一條線。“最小平方法”意指在模型中使誤差總和最小化的過程。對每一資料點,我們會測量該點與迴歸線間的垂直距離(稱為殘差)。
我們將這些距離平方,有兩個主要原因:
-
只看大小,不看方向: 我們想讓漏誤為 -5 與 +5 變成同樣的錯誤大小。平方可將所有數值轉為正數。
-
懲罰離群點: 平方會使離誤差較大的點權重更重,迫使線盡可能接近這些較遠的點。
接著,我們加總所有平方後的誤差,我們的目標是找到使此和達最小值的那條直線(故名「最小平方法」)。
🧮 數學說明
這條線,稱為_最佳擬合線_,其表達式是一個等式:
Y = a + bX
X是「解釋變數」,Y是「依變數」。線的斜率是b,而a是 y 截距,也就是當X = 0時Y的值。首先計算斜率
b。圖解由 Jen Looper 提供換句話說,針對我們南瓜資料的原始問題:「依照月份預測每蒲式耳南瓜價格」,
X是價格,Y則是銷售的月份。計算 Y 的值。如果你付出約 $4,那一定是四月!圖解由 Jen Looper 提供
計算線的數學必須顯示線的斜率,同時取決於截距,即
X = 0時Y的位置。你可以在 Math is Fun 網站看到這些數值的計算方式,也可拜訪這個最小二乘計算器來觀察數字如何影響線的表現。
相關性
另一個要理解的詞是給定 X 和 Y 變數間的相關係數。透過散佈圖,你可以快速視覺化此係數。點呈現筆直排列的散佈圖表示高度相關,但若資料點在 X 與 Y 間亂散則表示相關性低。
一個良好的線性迴歸模型會在最小平方法與迴歸線下,呈現較高(接近 1 而非 0)的相關係數。
✅ 執行本課程附帶的筆記本並查看「月份 vs 價格」的散佈圖。依據你對散佈圖的視覺判斷,南瓜銷售的月份與價格看來關聯性高還是低?如果不用 Month 而改用更細緻的量度指標,例如_一年中的日數_(即自年初後流逝的天數),這狀況會改變嗎?
下方程式碼中,我們假設已整理好資料,取得名為 new_pumpkins 的資料框,如下所示:
| ID | Month | DayOfYear | Variety | City | Package | Low Price | High Price | Price |
|---|---|---|---|---|---|---|---|---|
| 70 | 9 | 267 | PIE TYPE | BALTIMORE | 1 1/9 bushel cartons | 15.0 | 15.0 | 13.636364 |
| 71 | 9 | 267 | PIE TYPE | BALTIMORE | 1 1/9 bushel cartons | 18.0 | 18.0 | 16.363636 |
| 72 | 10 | 274 | PIE TYPE | BALTIMORE | 1 1/9 bushel cartons | 18.0 | 18.0 | 16.363636 |
| 73 | 10 | 274 | PIE TYPE | BALTIMORE | 1 1/9 bushel cartons | 17.0 | 17.0 | 15.454545 |
| 74 | 10 | 281 | PIE TYPE | BALTIMORE | 1 1/9 bushel cartons | 15.0 | 15.0 | 13.636364 |
清理資料的程式碼位於
notebook.ipynb檔案中。我們採用了前課的相同步驟,並用以下表達式計算DayOfYear欄位:
day_of_year = pd.to_datetime(pumpkins['Date']).apply(lambda dt: (dt-datetime(dt.year,1,1)).days)
了解線性迴歸背後的數學後,我們來建立一個迴歸模型,看看是否可以預測哪種南瓜包裝擁有最佳南瓜價格。採買節慶南瓜的人可能想要這些資訊,以便優化購買南瓜包裝的決策。
尋找相關性
🎥 點擊上方影片縮圖,觀看相關性簡介。
從前一課你可能已看到不同月份的平均價格長這樣:
這暗示著一定存在某種相關性,我們可以嘗試訓練線性迴歸模型來預測 Month 與 Price,或是 DayOfYear 與 Price 的關係。下圖為後者的散點圖:
讓我們用 corr 函式檢視是否存在相關性:
print(new_pumpkins['Month'].corr(new_pumpkins['Price']))
print(new_pumpkins['DayOfYear'].corr(new_pumpkins['Price']))
看來相關性相當小,按 Month 計算是 -0.15,按 DayOfYear 是 -0.17,但可能有另一個重要關係—不同南瓜品種似乎對價格產生價格群聚現象。為了驗證此猜想,我們以不同顏色標示每種南瓜品種。藉由在 scatter 繪圖函式中傳入 ax 參數,我們能將所有點畫在同一張圖上:
ax=None
colors = ['red','blue','green','yellow']
for i,var in enumerate(new_pumpkins['Variety'].unique()):
df = new_pumpkins[new_pumpkins['Variety']==var]
ax = df.plot.scatter('DayOfYear','Price',ax=ax,c=colors[i],label=var)
研判發現品種對整體價格影響較大,而非實際銷售日期。我們可以用長條圖來看:
new_pumpkins.groupby('Variety')['Price'].mean().plot(kind='bar')
目前我們先專注於一種南瓜品種,『派型』,看看日期對價格的影響:
pie_pumpkins = new_pumpkins[new_pumpkins['Variety']=='PIE TYPE']
pie_pumpkins.plot.scatter('DayOfYear','Price')
如果用 corr 函式計算 Price 與 DayOfYear 的相關性,大概會得到 -0.27,這代表訓練預測模型是有意義的。
在訓練線性迴歸模型之前,重要的是確保資料清潔。線性迴歸對缺失值表現不佳,因此建議先剔除所有空值:
pie_pumpkins.dropna(inplace=True)
pie_pumpkins.info()
另一個作法是用對應欄位的平均值填補這些空缺。
簡單線性迴歸
🎥 點擊上方圖片,觀看線性與多項式迴歸簡介。
我們將採用Scikit-learn套件訓練線性迴歸模型。
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
首先將輸入特徵與預期輸出(標籤)分別存入 numpy 陣列:
X = pie_pumpkins['DayOfYear'].to_numpy().reshape(-1,1)
y = pie_pumpkins['Price']
注意,我們對輸入資料做了
reshape,讓線性迴歸套件能正確識別。線性迴歸期望輸入為 2D 陣列,其中每一列代表一組輸入特徵向量,因為此處只有一個特徵,故需一個大小為 N×1 的陣列,N 為資料筆數。
接著,我們需要將資料拆分為訓練與測試集,好在訓練後驗證模型成效:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
最後,訓練線性迴歸模型僅需兩行程式碼。我們宣告 LinearRegression 物件,並使用 fit 方法訓練:
lin_reg = LinearRegression()
lin_reg.fit(X_train,y_train)
LinearRegression 物件在進行 fit 後包含了所有回歸的係數,可以用 .coef_ 屬性存取。在我們的例子中只有一個係數,大約是 -0.017。這表示價格隨時間似乎有輕微下降,但幅度不大,大約每天下降 2 分錢。我們也可以用 lin_reg.intercept_ 存取回歸線與 Y 軸的交叉點——在我們的例子中約為 21,表示年初的價格。
要看看我們的模型有多準確,我們可以在測試資料集上預測價格,然後衡量預測值與期望值的接近程度。這可以用均方根誤差 (RMSE) 指標來完成,該指標是所有期望值與預測值平方差的平均後開根號。
pred = lin_reg.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test,pred))
print(f'RMSE: {rmse:3.3} ({rmse/np.mean(pred)*100:3.3}%)')
我們的誤差大約是 2 點,約為 ~17%。這不算太好。另一個衡量模型品質的指標是 決定係數,可用以下程式取得:
score = lin_reg.score(X_train,y_train)
print('Model determination: ', score)
當該值為 0 時,表示模型不考慮輸入資料,僅當作最差線性預測,即結果的平均值。當該值為 1 時,表示我們可以完美預測所有期望輸出。在我們的例子中,決定係數約為 0.06,偏低。
我們也可以將測試資料與回歸線繪圖,以更好地觀察回歸的效果:
plt.scatter(X_test,y_test)
plt.plot(X_test,pred)
多項式回歸
線性回歸的另一種類型是多項式回歸。有時候變數之間呈線性關係——體積越大的南瓜價格越高——但有時這種關係無法用平面或直線描述。
✅ 這裡有更多示例可以使用多項式回歸的資料
再看一次日期與價格的關係。這個散點圖看起來一定要用直線分析嗎?價格難道不會波動?這時可以嘗試多項式回歸。
✅ 多項式是可能包含一個或多個變數與係數的數學表達式
多項式回歸會產生一條曲線,以更貼合非線性資料。在我們的情況下,如果將平方的 DayOfYear 變數包含進輸入資料,應該能以一條抛物線擬合資料,該曲線在年中的某個點有一個最小值。
Scikit-learn 包含方便的管道 API來結合不同的資料處理步驟。管道是一連串的估計器。在我們的例子中,我們會建立一個先增加多項式特徵,再訓練迴歸模型的管道:
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(PolynomialFeatures(2), LinearRegression())
pipeline.fit(X_train,y_train)
使用 PolynomialFeatures(2) 表示我們會包含所有二次多項式。就我們例子來說,就是 DayOfYear2,但對兩個輸入變數 X 和 Y,會加入 X2、XY 和 Y2。如有需要,也可用更高次方多項式。
管道可像原始的 LinearRegression 物件一樣使用,也就是我們可以在管道上呼叫 fit,然後用 predict 取得預測結果:
pred = pipeline.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test,pred))
print(f'RMSE: {rmse:3.3} ({rmse/np.mean(pred)*100:3.3}%)')
score = pipeline.score(X_train,y_train)
print('Model determination: ', score)
要繪製平滑的擬合曲線,我們用 np.linspace 產生均勻間距的輸入值範圍,而非直接在無序的測試資料上繪製(那樣會呈現鋸齒狀線):
X_range = np.linspace(X_test.min(), X_test.max(), 100).reshape(-1,1)
y_range = pipeline.predict(X_range)
plt.scatter(X_test, y_test)
plt.plot(X_range, y_range)
這是包含測試資料和擬合曲線的圖:
使用多項式回歸,我們能得到稍低的 RMSE 與稍高的決定係數,但差異不大。我們需要考慮其他特徵!
你可以看到,最低的南瓜價格大約出現在萬聖節左右。你怎麼解釋這個現象?
🎃 恭喜,你剛剛建立了一個可以幫助預測派南瓜價格的模型。你或許也可以用相同程序針對其他南瓜品種建立模型,但那會很繁瑣。現在讓我們學習如何在模型中考慮南瓜品種!
類別特徵
理想狀況下,我們希望用同一個模型預測不同南瓜品種的價格。然而,Variety 欄位與 Month 等欄位不同,它包含非數值資料。這類欄位稱為類別特徵。
🎥 點擊上方圖片觀看使用類別特徵的線性回歸短片概述。
這裡顯示平均價格如何依品種變化:
要考慮品種,我們首先需要將其轉換為數字形式,稱作編碼。有幾種方法:
- 簡單的數值編碼會建立一張各品種的表,然後用該表的索引取代品種名稱。這對線性回歸來說並不理想,因為線性回歸會直接將索引數值乘以係數相加,但在我們案例中索引號碼與價格的關係很明顯是非線性的,即使我們嘗試將索引做某種特定排序。
- 獨熱編碼則會用 4 個不同欄位取代
Variety,每個欄位對應一個品種。如果該列是特定品種,該欄位為1,反之為0。這表示線性回歸會有四個係數,分別對應四個品種,代表該品種的“起始價”(或者說“額外價格”)。
以下程式示範如何對品種做獨熱編碼:
pd.get_dummies(new_pumpkins['Variety'])
| ID | FAIRYTALE | MINIATURE | MIXED HEIRLOOM VARIETIES | PIE TYPE |
|---|---|---|---|---|
| 70 | 0 | 0 | 0 | 1 |
| 71 | 0 | 0 | 0 | 1 |
| ... | ... | ... | ... | ... |
| 1738 | 0 | 1 | 0 | 0 |
| 1739 | 0 | 1 | 0 | 0 |
| 1740 | 0 | 1 | 0 | 0 |
| 1741 | 0 | 1 | 0 | 0 |
| 1742 | 0 | 1 | 0 | 0 |
要用獨熱編碼品種作為輸入,訓練線性回歸,只要初始化 X 和 y 的資料正確即可:
X = pd.get_dummies(new_pumpkins['Variety'])
y = new_pumpkins['Price']
其餘程式碼與前面訓練線性回歸時相同。你試試看就會發現均方誤差差不多,但決定係數大幅提高到約 77%。若想更精確,可以同時考慮更多類別特徵及數值特徵,例如 Month 或 DayOfYear。要合併成一個大特徵陣列,可以用 join:
X = pd.get_dummies(new_pumpkins['Variety']) \
.join(new_pumpkins['Month']) \
.join(pd.get_dummies(new_pumpkins['City'])) \
.join(pd.get_dummies(new_pumpkins['Package']))
y = new_pumpkins['Price']
這裡我們也納入 City 和 Package 類別,得出的 RMSE 是 2.84(10.5%),決定係數為 0.94!
綜合應用
要建立最好的模型,我們可以用上述案例的合併(獨熱編碼的類別 + 數值)特徵資料,並搭配多項式回歸。以下是完整程式,方便參考:
# 設定訓練數據
X = pd.get_dummies(new_pumpkins['Variety']) \
.join(new_pumpkins['Month']) \
.join(pd.get_dummies(new_pumpkins['City'])) \
.join(pd.get_dummies(new_pumpkins['Package']))
y = new_pumpkins['Price']
# 製作訓練-測試分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# 設定並訓練流程
pipeline = make_pipeline(PolynomialFeatures(2), LinearRegression())
pipeline.fit(X_train,y_train)
# 預測測試數據結果
pred = pipeline.predict(X_test)
# 計算均方根誤差及決定係數
rmse = mean_squared_error(y_test, pred, squared=False)
print(f'RMSE: {rmse:3.3} ({rmse/pred.mean()*100:3.3}%)')
score = pipeline.score(X_train,y_train)
print('Model determination: ', score)
這應該會給我們接近 97% 的最高決定係數與 RMSE=2.23(約 8% 預測誤差)。
| 模型 | RMSE | 決定係數 |
|---|---|---|
DayOfYear 線性 |
2.77 (17.2%) | 0.07 |
DayOfYear 多項式 |
2.73 (17.0%) | 0.08 |
Variety 線性 |
5.24 (19.7%) | 0.77 |
| 所有特徵 線性 | 2.84 (10.5%) | 0.94 |
| 所有特徵 多項式 | 2.23 (8.25%) | 0.97 |
🏆 幹得好!在一堂課中建立了四個回歸模型,並將模型品質提升到 97%。在回歸的最後一節,你將學習用於分類判斷的邏輯迴歸。
🚀挑戰
在此筆記本中測試不同變數,觀察其相關性與模型準確性的關係。
課後小測驗
複習與自學
本課涵蓋了線性回歸。其他重要的回歸類型還包括階層回歸、嶺回歸、Lasso 與彈性網路。想深入學習可以參考 Stanford 統計學習課程
作業
免責聲明:
本文件使用 AI 翻譯服務 Co-op Translator 進行翻譯。雖然我們力求準確,但請注意自動翻譯可能包含錯誤或不準確之處。原始文件的母語版本應視為權威來源。對於重要資訊,建議尋求專業人工作翻譯。我們對因使用本翻譯而產生的任何誤解或誤釋不承擔任何責任。





