從零開始學機器學習——線性和多項式迴歸

努力的小雨發表於2024-09-29

首先給大家介紹一個很好用的學習地址:https://cloudstudio.net/columns

在之前的學習中,我們已經對資料的準備工作以及資料視覺化有了一定的瞭解。今天,我們將深入探討基本線性迴歸和多項式迴歸的概念與應用。

如果在過程中涉及到一些數學知識,大家也不必感到畏懼,我會逐步為大家進行詳細的講解,以便大家能夠更好地理解這些內容。希望透過今天的學習,能夠幫助大家建立起對這兩種迴歸方法的清晰認識,並掌握它們在實際問題中的應用。

線性和多項式迴歸

通常情況下,迴歸分析主要分為兩種型別:線性迴歸和多項式迴歸。線性迴歸旨在透過一條直線來描述變數之間的關係,而多項式迴歸則允許我們使用多項式函式來更靈活地捕捉資料的複雜趨勢。為了幫助大家直觀地理解這兩種迴歸方法,我們可以透過圖片進行展示。

image

其實,線性迴歸和多項式迴歸之間的區別,可以簡單地歸結為直線與曲線的差異。

基本線性迴歸

線性迴歸練習的目標在於能夠繪製出一條理想的迴歸線,那麼什麼才算是“完美的線”呢?簡而言之,完美的線是指使所有分散的資料點到這條線的距離最短的情況。通常,我們使用最小二乘法來實現這一目標。

理想情況下,這個總和應儘可能小,以確保迴歸線能夠最佳地代表資料的趨勢。接下來,讓我們先看一下相關的數學公式,以便更深入地理解這一過程。

$ sum_{i=1}^{n} (y_i - f(x_i))^2$

我們希望求出一組未知引數,使得樣本點與擬合線之間的總誤差(即距離)最小化。最直觀的感受如下圖:

image

因為相減的結果可能會出現負數的情況,為了確保我們計算出的值始終為正,因此在這裡引入了平方的概念。無論減法的結果是正還是負,經過平方處理後,所有的結果都將轉化為正數。

這條綠線被稱為最佳擬合線,可以用一個數學等式來表示:

Y = a + bX

X 是“解釋變數”。Y 是“因變數”。直線的斜率是 b,a 是 y 軸截距,指的是 X = 0 時 Y 的值。

一個好的線性迴歸模型將是一個用最小二乘迴歸法與直線迴歸得到的高(更接近於 1)相關係數的模型。相關係數(也稱為皮爾遜相關係數)我來解釋一下:

image

我們可以發現相關係數反映的是變數之間的線性關係和相關性的方向(第一排),而不是相關性的斜率(中間),也不是各種非線性關係(第三排)。請注意:中間的圖中斜率為0,但相關係數是沒有意義的,因為此時變數是0。

工具分析

當然,相關性這種指標的手動計算並不實際,尤其是在面對大規模資料時,手動處理不僅效率低下,還容易出錯。因此,利用現有的框架,如Scikit-learn,能夠更高效、準確地進行相關性分析,讓我們專注於更重要的任務。

pip install scikit-learn

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime
from sklearn.preprocessing import LabelEncoder

pumpkins = pd.read_csv('../data/US-pumpkins.csv')

pumpkins.head()

在獲取樣本資料後,進行分析時,通常需要篩選出我們需要的欄位,而那些不需要的則應予以丟棄,以便進一步深入分析。

pumpkins = pumpkins[pumpkins['Package'].str.contains('bushel', case=True, regex=True)]

columns_to_select = ['Package', 'Variety', 'City Name', 'Low Price', 'High Price', 'Date']
pumpkins = pumpkins.loc[:, columns_to_select]

price = (pumpkins['Low Price'] + pumpkins['High Price']) / 2

month = pd.DatetimeIndex(pumpkins['Date']).month
day_of_year = pd.to_datetime(pumpkins['Date']).apply(lambda dt: (dt-datetime(dt.year,1,1)).days)

new_pumpkins = pd.DataFrame(
    {'Month': month, 
     'DayOfYear' : day_of_year, 
     'Variety': pumpkins['Variety'], 
     'City': pumpkins['City Name'], 
     'Package': pumpkins['Package'], 
     'Low Price': pumpkins['Low Price'],
     'High Price': pumpkins['High Price'], 
     'Price': price})

new_pumpkins.loc[new_pumpkins['Package'].str.contains('1 1/9'), 'Price'] = price/1.1
new_pumpkins.loc[new_pumpkins['Package'].str.contains('1/2'), 'Price'] = price*2
new_pumpkins.iloc[:, 0:-1] = new_pumpkins.iloc[:, 0:-1].apply(LabelEncoder().fit_transform)

new_pumpkins.head()

儘管程式碼可能會讓人感到困惑,其核心邏輯其實很簡單:我們只需提取出所需的欄位,包括年月、價格、城市和包裝資訊。如果你對程式設計不太熟悉,也完全沒關係,現在有很多程式碼助手可供使用,選用任何一個都能輕鬆寫出所需的程式碼。關鍵在於保持清晰的邏輯思路,而不是被程式碼的複雜性所干擾。

import matplotlib.pyplot as plt
plt.scatter('City','Price',data=new_pumpkins)

正常來說,城市欄位應該是文字也就是城市名字,但是因為計算的原因,這裡有一段程式碼new_pumpkins.iloc[:, 0:-1] = new_pumpkins.iloc[:, 0:-1].apply(LabelEncoder().fit_transform)將資料集中的分類變數轉換為數值變數,以便於後續的機器學習建模。

image

如上所示的效果圖展示了我們的初步分析結果。當然,你可以選擇任何一個欄位來進行價格的取點分析,但透過肉眼觀察,往往難以發現變數之間的相關性。不過,這並不成問題,因為現在有很多強大的工具可以幫助我們進行深入的資料分析。

print(new_pumpkins['City'].corr(new_pumpkins['Price']))
# 0.32363971816089226

確實,整個過程簡單明瞭,一句話就可以概括。接下來,只需將剩餘的分析任務交給框架即可。在這裡,我們觀察到相關性大約只有0.3,顯示出與1的距離相當明顯,這意味著變數之間的關係並不強。

因此,我們可以嘗試更換另一個欄位來對價格進行分析,以進一步探討不同變數之間的相關性。

print(new_pumpkins['Package'].corr(new_pumpkins['Price']))
# 0.6061712937226021

image

我們清楚,影響價格的因素有很多,因此每次都單獨嘗試更換欄位並執行程式碼,以判斷哪個欄位與價格的相關性最高,顯得十分繁瑣和低效。在這種情況下,我們可以採用一種更為高效的方法——構造熱力圖。

corr = poly_pumpkins.corr()
corr.style.background_gradient(cmap='coolwarm')

程式碼其實非常簡單,僅需兩行就能實現我們的目標。接下來,讓我們一起觀察執行結果,以便更好地理解這些程式碼的效果和輸出。

image

根據這些相關性係數,你可以直觀地看到 Package 和 Price 之間的良好相關性。

迴歸模型

到目前為止,我們可以初步確認,包裝方式的價格與城市地區分佈的價格相比,更具相關性。這一發現為我們的分析奠定了基礎,因此接下來,我們將以包裝方式為核心,建立一個迴歸模型。

new_columns = ['Package', 'Price']
lin_pumpkins = new_pumpkins.drop([c for c in new_pumpkins.columns if c not in new_columns], axis='columns')
X = lin_pumpkins.values[:, :1]
y = lin_pumpkins.values[:, 1:2]

同樣的,我們只要包裝和價格欄位即可。讓後取第一列為X軸資料,第二列為Y軸資料。

接下來,開始構建迴歸模型,和第一節差不多,仍然是從樣本總抽取測試集以及訓練集,使用Python的scikit-learn庫來訓練一個線性迴歸模型,並對測試集進行預測,程式碼再寫一次:

from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
lin_reg = LinearRegression()
lin_reg.fit(X_train,y_train)

pred = lin_reg.predict(X_test)

accuracy_score = lin_reg.score(X_train,y_train)
print('Model Accuracy: ', accuracy_score)
# Model Accuracy:  0.3315342327998987

我來簡單的解釋一下:

  • 將資料集分為訓練集和測試集
  • 建立線性迴歸模型並訓練它,是的,就兩行程式碼完成了訓練,都不用我們自己寫什麼東西。
  • 使用訓練好的模型對測試集進行預測
  • 計算並列印模型的準確度

最終的分數確實不是很高,畢竟相關性也不是很好。

我們可以對已經訓練好的模型進行視覺化,以更直觀地展示其效能和預測結果。

plt.scatter(X_test, y_test,  color='black')
plt.plot(X_test, pred, color='blue', linewidth=3)

plt.xlabel('Package')
plt.ylabel('Price')

plt.show()

image

這樣,我們基本上就成功實現了一個基礎的線性迴歸模型。

多項式迴歸

另一種重要的線性迴歸型別是多項式迴歸。儘管在許多情況下,我們觀察到變數之間存在直接的線性關係——例如,南瓜的體積越大,價格通常也會隨之上漲——但在某些情況下,這種關係可能無法簡單地用平面或直線來表示。

正如我們在上面的圖例中所見,如果我們的模型採用曲線而非直線,這可能更有效地貼合資料點的分佈,從而更準確地捕捉變數之間的複雜關係。

new_columns = ['Variety', 'Package', 'City', 'Month', 'Price']
poly_pumpkins = new_pumpkins.drop([c for c in new_pumpkins.columns if c not in new_columns], axis='columns')

我們重新看下資料,然後直接拿出Package和Price值。

X=poly_pumpkins.iloc[:,3:4].values
y=poly_pumpkins.iloc[:,4:5].values

接下來,我們將直接進行模型訓練。在這個過程中,我們使用了另一個API,即scikit-learn庫,來構建一個包含多項式特徵轉換和線性迴歸模型的管道(pipeline)。這個管道的設計旨在簡化資料處理流程,使得多項式特徵的生成和模型的訓練能夠高效地串聯在一起。

from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

pipeline = make_pipeline(PolynomialFeatures(4), LinearRegression())

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

pipeline.fit(np.array(X_train), y_train)

y_pred=pipeline.predict(X_test)

PolynomialFeatures

在這裡,我想簡要介紹一下PolynomialFeatures這一功能。PolynomialFeatures是scikit-learn庫中用於生成多項式特徵的工具。

它的主要作用是將輸入的特徵轉換為多項式形式,從而允許我們在模型中考慮變數之間的非線性關係。例如,當我們有一個單一特徵時,使用PolynomialFeatures可以建立該特徵的平方、立方,甚至更高次的特徵。

我們的程式碼中引數設定為4,意味著我們希望對輸入特徵X進行四次多項式轉換。這種轉換將使得每個原始特徵生成其四次冪的組合,從而豐富我們的特徵集。

為了幫助大家更直觀地理解這一過程,我將提供一組示例資料,展示經過PolynomialFeatures處理後,原有的X特徵資料被轉化成了什麼樣的多項式特徵。

import numpy as np

X = np.array([1, 2, 3, 4, 5])
X = X.reshape(-1, 1)
X

這是我們當前的特徵集X,包含了一組簡單的資料點。

array([[1],
       [2],
       [3],
       [4],
       [5]])
pp = PolynomialFeatures(4)
X_poly = pp.fit_transform(X)
print(X_poly)

轉換後會生成如下:

[[  1.   1.   1.   1.   1.]
 [  1.   2.   4.   8.  16.]
 [  1.   3.   9.  27.  81.]
 [  1.   4.  16.  64. 256.]
 [  1.   5.  25. 125. 625.]]

這樣做的意義就在於:線性迴歸假設特徵與目標之間的關係是線性的。透過多項式特徵轉換,可以捕捉更復雜的非線性關係。這有助於提高模型的擬合能力。

在訓練過程中,模型實際上是在學習一個非線性函式,舉個例子例如:二次多項式 \(y = ax^2 + bx + c\) 表示一個拋物線。

那麼這個模型訓練後就會產生如下效果:

df = pd.DataFrame({'x': X_test[:,0], 'y': y_pred[:,0]})
df.sort_values(by='x',inplace = True)
points = pd.DataFrame(df).to_numpy()

plt.plot(points[:, 0], points[:, 1],color="blue", linewidth=3)
plt.xlabel('Package')
plt.ylabel('Price')
plt.scatter(X,y, color="black")
plt.show()

image

接下來,我們可以基於這個非線性模型進行預測即可。簡單演示一下:

pipeline.predict( np.array([ [2.75] ]) )
# array([[46.34509342]])

總結

在探討線性迴歸和多項式迴歸的旅程中,我們不僅學習瞭如何構建模型,還理解了背後的數學原理和應用場景。透過逐步引導,希望大家對資料分析的複雜性有了更深的認識。未來,在實際問題中靈活應用這些迴歸技術,將使我們在資料驅動的決策中佔據優勢。

無論是選擇合適的迴歸方法,還是運用強大的工具,我們都能夠更有效地從資料中提煉出有價值的見解。接下來,期待大家在實踐中不斷探索、深入挖掘,最終實現對資料的深刻理解與應用。


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章