想像你在做一道料理。你有 p 種原料(輸入特徵 X₁, X₂, …, Xₚ),你想要一個味道(輸出 f(X))。線性迴歸就是每種原料加一點,加總就上桌。但真正厲害的廚師不會直接把原料混在一起,而是先用不同比例的原料做出 K 種 醬料(隱藏單元 A₁, A₂, …, Aₖ),每種醬料還要經過「火候」(活化函數)的轉化,最後再把這些醬料混合成最終味道。
這就是單層神經網路的核心概念:先從原始特徵做出 K 個衍生特徵(隱藏層),再用這些衍生特徵做線性迴歸。
一個有 K 個隱藏單元的單層神經網路,分兩步計算:
這裡 \(w_{kj}\) 是權重(weight),\(w_{k0}\) 是偏誤項(bias),\(g(\cdot)\) 是活化函數(activation function)。
合併起來,完整的模型為:
所有參數 \(\beta_0, \ldots, \beta_K\) 以及 \(w_{10}, \ldots, w_{Kp}\) 都需要從資料估計。
Sigmoid 把任何實數壓縮到 (0, 1),和邏輯回歸用的是同一個函數。從「神經元」的類比來看:輸出接近 1 代表神經元「激發」,接近 0 代表「安靜」。
ReLU 是現代神經網路的主流選擇。它比 sigmoid 更快、更容易計算和儲存。雖然在 z=0 處有「截斷」,但因為我們是對線性組合做 ReLU,常數項 wₖ₀ 會移動這個轉折點,所以模型仍有足夠彈性。
讓我們用 numpy 實作上面的數學,親眼看到「非線性活化 → 互動效應」的魔法。
# 親手打造單層神經網路:示範非線性活化如何產生互動效應
try:
from google.colab import drive
drive.mount('/content/drive')
DATA_PATH = '/content/drive/MyDrive/ISLP_data/'
except ImportError:
DATA_PATH = '/tmp/'
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# 重現課本公式 10.6-10.8 的互動效應例子
# 兩個輸入 X1, X2,兩個隱藏單元,g(z)=z²
def g_quadratic(z):
"""活化函數:平方(課本為展示互動效應用的特例)"""
return z ** 2
# 課本設定的參數(公式 10.6)
beta_0 = 0
beta_1 = 0.25
beta_2 = -0.25
w10, w11, w12 = 0, 1, 1 # h1: (X1+X2)²
w20, w21, w22 = 0, 1, -1 # h2: (X1-X2)²
def single_layer_nn(X1, X2):
"""返回 f(X1,X2);兩個隱藏單元,g(z)=z²"""
z1 = w10 + w11 * X1 + w12 * X2 # = X1+X2
z2 = w20 + w21 * X1 + w22 * X2 # = X1-X2
A1 = g_quadratic(z1)
A2 = g_quadratic(z2)
return beta_0 + beta_1 * A1 + beta_2 * A2
# 驗證:f(X1,X2) 應該等於 X1*X2
grid = np.linspace(-3, 3, 6)
for x1 in grid[::2]:
for x2 in grid[::2]:
print(f"f({x1:.0f},{x2:.0f}) = {single_layer_nn(x1,x2):.2f} | X1*X2 = {x1*x2:.2f}")
print("\n輸出完全等於 X1*X2:兩個非線性變換的線性組合 = 互動項!")
上面這段程式碼精確重現了課本公式 (10.6)-(10.8):兩個對線性組合做平方的隱藏單元,加權相減後得到 X₁X₂ 這個互動項。
# 比較 Sigmoid 與 ReLU:視覺化活化函數的行為
try:
from google.colab import drive
drive.mount('/content/drive')
DATA_PATH = '/content/drive/MyDrive/ISLP_data/'
except ImportError:
DATA_PATH = '/tmp/'
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def relu(z):
return np.maximum(0, z)
z = np.linspace(-4, 4, 200)
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(z, sigmoid(z), label='Sigmoid: 1/(1+e⁻ᶻ)', linewidth=2.2)
ax.plot(z, relu(z) / 5, label='ReLU/5 (縮放以利比較)', linewidth=2.2)
ax.axhline(y=0, color='gray', linewidth=0.5)
ax.axvline(x=0, color='gray', linewidth=0.5)
ax.set_xlabel('z')
ax.set_ylabel('g(z)')
ax.set_title('活化函數比較:Sigmoid vs ReLU')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
# 用 sklearn 建立真正的單層神經網路(MLPRegressor)
try:
from google.colab import drive
drive.mount('/content/drive')
DATA_PATH = '/content/drive/MyDrive/ISLP_data/'
except ImportError:
DATA_PATH = '/tmp/'
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# 生成非線性資料:y = sin(X) + 雜訊
np.random.seed(42)
X = np.random.uniform(-3, 3, 200).reshape(-1, 1)
y = np.sin(X.ravel()) + np.random.normal(0, 0.15, 200)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 單隱藏層神經網路(K=20 個隱藏單元,ReLU 活化)
nn = MLPRegressor(
hidden_layer_sizes=(20,),
activation='relu',
solver='adam',
max_iter=2000,
random_state=42
)
nn.fit(X_train, y_train)
y_pred = nn.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print(f"測試集 MSE: {mse:.4f}")
# 視覺化:比較原始資料與神經網路擬合
X_plot = np.linspace(-3, 3, 300).reshape(-1, 1)
y_plot = nn.predict(X_plot)
fig, ax = plt.subplots(figsize=(8, 4))
ax.scatter(X_test, y_test, s=15, alpha=0.5, label='測試資料')
ax.plot(X_plot, np.sin(X_plot), '--', color='gray', alpha=0.6, label='真實函數 sin(x)')
ax.plot(X_plot, y_plot, color='#58a6ff', linewidth=2, label=f'神經網路擬合 (K=20, ReLU)')
ax.set_xlabel('X')
ax.set_ylabel('y')
ax.set_title('單層神經網路(20 個隱藏單元)擬合 sin(x)')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
觀察上面的結果:20 個 ReLU 隱藏單元的組合就足以擬合出平滑的非線性曲線。每個 ReLU 單元像一塊「積木」,足夠多塊就能拼出任何形狀。
假設我們用 線性 活化函數 \(g(z)=z\),會發生什麼?
所有權重可以「折疊」成一個線性組合——無論疊多少層、多少隱藏單元,都等價於一個簡單的線性迴歸。
對於數值型輸出(迴歸問題),使用 平方誤差損失:
所有參數(權重 \(w_{kj}\)、偏誤 \(w_{k0}\)、輸出層權重 \(\beta_k\))透過最小化這個損失來估計。具體的最佳化方法會在 §10.7 詳述,核心概念是 梯度下降(gradient descent)——沿著誤差梯度,逐次微調每個參數。
輸入特徵:收入、負債比、信用歷史(X₁~Xₚ)。單層神經網路的 K 個隱藏單元各自捕捉不同的非線性組合(例如「高收入 + 高負債」的風險模式),比單純的邏輯回歸更靈活。
從數十個生理指標(血壓、膽固醇、年齡…)計算疾病風險。隱藏單元自動學到「血壓偏高+年齡偏大」的交互作用,不需要人工指定互動項。這正是課本 g(z)=z² 範例的精神——非線性變換自動產生互動效果。
從用戶行為(登入頻率、購買次數、客服聯絡次數…)預測流失。單層神經網路用少數隱藏單元找出關鍵的非線性組合,在中小型資料集上比深度網路更不易過擬合,且可解釋性較高。
| 方法 | 非線性能力 | 參數量 | 可解釋性 | 適用情境 |
|---|---|---|---|---|
| 線性迴歸(§3) | ❌ 純線性 | p+1 | ⭐⭐⭐⭐⭐ | 資料量少、需要解讀係數 |
| 多項式迴歸(§7.1) | ⚡ 預設形式 | 取決於階數 | ⭐⭐⭐⭐ | 已知非線性形式、低維度 |
| GAM(§7.7) | ⚡ 各變數獨立 | 中等 | ⭐⭐⭐⭐ | 需可解釋性、加性假設合理 |
| 決策樹(§8.1) | ✅ 分段常數 | 取決於深度 | ⭐⭐⭐ | 混合型態變數、需視覺化 |
| 單層神經網路(§10.1) | ✅ 任意平滑 | K(p+1)+(K+1) | ⭐⭐ | 資料量大、預測精度優先 |
| SVM(§9) | ✅ 核函數 | 取決於支援向量數 | ⭐⭐ | 高維度、分類邊界複雜 |
單層神經網路坐落在「夠靈活但不至於太複雜」的甜蜜點,適合中大型資料集的非線性問題——比線性模型強大得多,但不像深層網路那樣容易過擬合且難解釋。