學習理論後,重要的是如何用程式碼實現,這一篇文章我會用 Python 練習手刻梯度下降法,上傳自己的檔案練習。中間有發現數據集跑出來的結果跟我想的不一樣,我也分享如何解決(感謝AI)最後再用 Scikit-learn 加上 Matplotlib 視覺畫圖表,期望之後可以在工作使用到。
使用 Colaboratory 練習,使用 AI 工具輔助,並使用 kaggle 開源資料庫練習
加上程式碼就可以簡單的練習,不需要架設其他的環境。
假設我們要做 簡單線性回歸。y = ax+b ,數據我先隨機設定。
實際上應用可以用自己的 Excel 或是 CSV,
import numpy as np
# 生成範例資料 (x:輸入特徵, y:實際值)
X = np.array([1, 2, 7.2, 4.3, 6]) # 單一特徵
y = np.array([3, 7, 8, 9, 11]) # 我隨機設定數字做示範
# 初始化參數
theta_0 = 0 # 截距項 (bias)
theta_1 = 0 # 斜率 (weight)
alpha = 0.01 # 學習率
epochs = 1000 # 迭代次數
m = len(X) # 樣本數 X
# 執行梯度下降
for epoch in range(epochs):
# 計算預測值
y_pred = theta_0 + theta_1 * X
# 計算損失函數 (MSE) #**是次方,所以**2是二次方
loss = np.mean((y_pred - y) ** 2)
# 計算梯度 (偏導數)
d_theta_0 = (2/m) * np.sum(y_pred - y) # 對 theta_0 求偏導
d_theta_1 = (2/m) * np.sum((y_pred - y) * X) # 對 theta_1 求偏導
# 更新參數
theta_0 -= alpha * d_theta_0
theta_1 -= alpha * d_theta_1
# 每 100 次輸出一次 loss
if epoch % 100 == 0:
print(f"Epoch {epoch}, Loss: {loss:.5f}, theta_0: {theta_0:.5f}, theta_1: {theta_1:.5f}")
print(f"最終結果: theta_0 = {theta_0:.5f}, theta_1 = {theta_1:.5f}")
輸出結果
Loss
,讓我們觀察模型收斂過程。最後輸出最終的 theta_0
和 theta_1
,得到最佳的線性回歸參數。
用 Colaboratory 跑出的結果
Epoch 0, Loss: 44.00000, theta_0: 0.22000, theta_1: 0.66000
Epoch 100, Loss: 0.00313, theta_0: 0.01634, theta_1: 1.98496
Epoch 200, Loss: 0.00000, theta_0: 0.00919, theta_1: 1.99563
...
Epoch 900, Loss: 0.00000, theta_0: 0.00003, theta_1: 1.99999
最終結果: theta_0 = 0.00003, theta_1 = 1.99999
from google.colab import files
import pandas as pd
# 上傳 CSV 檔案
uploaded = files.upload()
# 讀取 CSV exel 要改成pd.read_excel
df = pd.read_csv(next(iter(uploaded))) # 取得上傳的檔案名稱
print(df.head()) # 顯示前幾筆資料
挖 失敗! 出現 NAN
NaN
是 "Not a Number" 的縮寫,代表「無效數值」,通常出現在以下情況:
例如 0 、 inf 這種無法計算的數值。
你的 NaN 問題可能來自 Loss 變成 inf 之後發生數值錯誤。
如果 X 或 y 有 NaN,運算時會導致 NaN 傳播。
但你的 df.dropna() 已經移除 NaN,所以這應該不是主要原因。
你的 theta_1 在 Epoch 100 之後變得超大 (10^308 這種等級),導致 Loss 變成 inf,進一步影響計算。
這通常是 學習率 (alpha) 過大,讓梯度下降失控,導致 theta_0 和 theta_1 在幾次迭代後爆炸。
你的問題應該是 學習率過大,導致 Loss 變成 inf
,進而產生 NaN
。透過:
alpha = 0.0001
)X
和 y
theta_0
和 theta_1
的範圍我先說我的嘗試,因為0.0001,Theta_0 結果出現 0 ,有可能是均值接近0,所以可以先嘗試
👉 解決方法:
試著先檢查 X
和 y
的均值:
如果 y_pred
和 y
的均值相等,那麼梯度接近 0,theta_0
幾乎不變。
print("X mean:", np.mean(X))
print("y mean:", np.mean(y))
如果 X
或 y
的均值接近 0,可以嘗試:
X
進行標準化,但 不要對 y 標準化:X = (X - np.mean(X)) / np.std(X)
測試後發現 X mean
和 y mean
都非常接近 0 (在 e-17
和 e-16
級別),這表示 X 和 y 已經標準化了。
因為標準化後的 X
是均值為 0 的對稱分佈,梯度下降在更新 theta_0
時可能變得極小,導致 theta_0
始終維持在 0。
theta_0
斜率= 0?如果 X
已標準化 (mean ≈ 0
),則:
(0, mean(y))
,但因為 mean(y) ≈ 0
,所以 theta_0 = 0
。theta_0
的梯度 (d_theta_0
) 會接近 0,因此 theta_0
幾乎沒有變化。這是數學上的結果,不是程式錯誤。
在我把 y 標準化的程式碼註解掉就可以看到變化
# 標準化數據
X_mean, X_std = np.mean(X), np.std(X)
y_mean, y_std = np.mean(y), np.std(y)
X = (X - X_mean) / X_std
# 因為我的數據標準化會使截距貼近零,所以暫時不標準化 y = (y - y_mean) / y_std
print("X mean:", np.mean(X))
print("y mean:", np.mean(y))
在公司很常會希望用圖表報告,用 Matplotib 就可以繪製
用 Matplotlib 繪製梯度下降過程:
我先把這段程式碼貼到我前面的數據庫中
import matplotlib.pyplot as plt
# 繪製數據點 #一般的二維數據就可以跑
plt.scatter(X, y, color='blue', label='Actual data')
# 繪製最終擬合線
y_pred_final = theta_0 + theta_1 * X
plt.plot(X, y_pred_final, color='red', label='Fitted line')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.title('Linear Regression using Gradient Descent')
plt.show()
剛好我選的數據集成正比
試著亂改資料點xD
如果不想手刻梯度下降,可以使用 scikit-learn
的其中一個功能 LinearRegression
直接做回歸:
我用別的數據庫測試 https://scikit-learn.org/stable/datasets/toy_dataset.html
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# 1. 載入糖尿病數據集
diabetes = datasets.load_diabetes()
# 2. 取出其中一個特徵 (只用 BMI 來預測疾病指數)
X = diabetes.data[:, np.newaxis, 2] # 選擇第3個特徵
y = diabetes.target # 目標變數
# 3. 分割數據集為訓練集和測試集 (80% 訓練, 20% 測試)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 4. 建立並訓練線性回歸模型
model = LinearRegression()
model.fit(X_train, y_train)
# 5. 預測測試數據
y_pred = model.predict(X_test)
# 6. 視覺化結果
plt.scatter(X_test, y_test, color="black", label="Actual Data") # 真實值
plt.plot(X_test, y_pred, color="red", linewidth=2, label="Predicted Line") # 預測直線
plt.xlabel("BMI")
plt.ylabel("Disease Progression")
plt.legend()
plt.show()
# 7. 輸出模型參數
print(f"Intercept (theta_0): {model.intercept_}")
print(f"Slope (theta_1): {model.coef_[0]}")
補充說明語法:
X = diabetes.data[:, np.newaxis, 2] # 選擇第3個特徵
[:]
是 Python 中的「切片語法(slicing)」,在這行程式碼裡的作用是選擇所有的列(rows),而 2
則是選擇第 3 個特徵(索引從 0 開始算)。因為我們只做二維的回歸,所以只能其中一個特徵(BMI) 做疾病指數的預測。
diabetes.data
numpy.ndarray
,維度為 (442, 10)
,代表 442 筆資料,每筆資料有 10 個特徵。[:, np.newaxis, 2]
:
→ 選取 所有的列(rows),也就是 442 筆資料 全部選取。np.newaxis
→ 這個會增加一個維度,讓 X
變成 (442, 1) 的 2D 矩陣,而不是原本的 (442,) 1D 陣列。2
→ 取出 索引 2 的特徵(第 3 個特徵)。