房屋價格預測
以下以「**根據房屋坪數、屋齡、房間數與距離市中心距離,預測房屋價格**」為例,使用 PyTorch 建立一個多層感知器(MLP)迴歸模型。
---
## 一、問題定義
假設每筆房屋資料包含以下特徵:
| 特徵 | 說明 |
|---|---|
| `area` | 房屋面積 |
| `age` | 屋齡 |
| `rooms` | 房間數 |
| `distance` | 距離市中心距離 |
模型輸出:
- `price`:房屋價格
這是一個典型的監督式學習迴歸問題,損失函數可以使用均方誤差(MSE)。
---
## 二、完整實作程式碼
### 1. 匯入套件與建立資料
以下使用 `make_regression` 建立一組模擬房價資料。實務上可以將這段替換成讀取 CSV 或資料庫的程式。
```python
import copy
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
```
```python
# 固定亂數,讓結果容易重現
np.random.seed(42)
torch.manual_seed(42)
# 建立模擬房價資料
# 4 個特徵:坪數、屋齡、房間數、距離市中心距離
X, y = make_regression(
n_samples=1000,
n_features=4,
n_informative=4,
noise=12,
random_state=42
)
# 將房價設定為二維陣列,方便後續標準化
y = y.reshape(-1, 1)
print("X shape:", X.shape)
print("y shape:", y.shape)
```
---
### 2. 切分訓練集、驗證集與測試集
資料切分方式如下:
- 訓練集:70%
- 驗證集:15%
- 測試集:15%
```python
# 先切分出 70% 訓練集與 30% 暫存集
X_train, X_temp, y_train, y_temp = train_test_split(
X, y,
test_size=0.30,
random_state=42
)
# 再將暫存集切成驗證集與測試集,各佔 15%
X_val, X_test, y_val, y_test = train_test_split(
X_temp, y_temp,
test_size=0.50,
random_state=42
)
print("Train:", X_train.shape)
print("Validation:", X_val.shape)
print("Test:", X_test.shape)
```
---
### 3. 特徵標準化
神經網路通常對特徵尺度較敏感,因此將輸入特徵標準化為平均值 0、標準差 1。
需要注意:
> `StandardScaler` 只能使用訓練集進行 `fit`,不能使用全部資料,避免資料洩漏。
同時也將目標值進行標準化,使模型訓練更加穩定。
```python
x_scaler = StandardScaler()
y_scaler = StandardScaler()
# 只使用訓練集 fit
X_train_scaled = x_scaler.fit_transform(X_train)
X_val_scaled = x_scaler.transform(X_val)
X_test_scaled = x_scaler.transform(X_test)
y_train_scaled = y_scaler.fit_transform(y_train)
y_val_scaled = y_scaler.transform(y_val)
y_test_scaled = y_scaler.transform(y_test)
```
---
### 4. 轉換成 PyTorch Tensor 與 DataLoader
```python
# 轉為 float32,適合 PyTorch 模型
X_train_tensor = torch.tensor(X_train_scaled, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train_scaled, dtype=torch.float32)
X_val_tensor = torch.tensor(X_val_scaled, dtype=torch.float32)
y_val_tensor = torch.tensor(y_val_scaled, dtype=torch.float32)
X_test_tensor = torch.tensor(X_test_scaled, dtype=torch.float32)
y_test_tensor = torch.tensor(y_test_scaled, dtype=torch.float32)
# 建立 Dataset
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
val_dataset = TensorDataset(X_val_tensor, y_val_tensor)
test_dataset = TensorDataset(X_test_tensor, y_test_tensor)
# 建立 DataLoader
train_loader = DataLoader(
train_dataset,
batch_size=32,
shuffle=True
)
val_loader = DataLoader(
val_dataset,
batch_size=32,
shuffle=False
)
test_loader = DataLoader(
test_dataset,
batch_size=32,
shuffle=False
)
```
---
## 三、建立迴歸模型
這裡建立一個簡單的多層感知器:
```text
輸入層:4 個特徵
↓
Linear(4 → 64)
↓
ReLU
↓
Linear(64 → 32)
↓
ReLU
↓
Linear(32 → 1)
↓
預測價格
```
迴歸問題的輸出層通常不使用 Sigmoid 或 Softmax,直接輸出連續數值即可。
```python
class HousePriceRegressor(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.network = nn.Sequential(
nn.Linear(input_dim, 64),
nn.ReLU(),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
def forward(self, x):
return self.network(x)
model = HousePriceRegressor(input_dim=4)
print(model)
```
---
## 四、設定損失函數與最佳化器
```python
# 迴歸常用損失函數
criterion = nn.MSELoss()
# Adam 通常適合用於快速建立基準模型
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001,
weight_decay=1e-4
)
```
主要設定說明:
| 設定 | 說明 |
|---|---|
| `MSELoss` | 衡量預測值與真實值平方差 |
| `Adam` | 常用且穩定的最佳化器 |
| `lr=0.001` | 學習率 |
| `weight_decay=1e-4` | L2 正則化,降低過擬合 |
---
## 五、模型訓練
訓練時需要進行以下步驟:
1. 從 DataLoader 取得一批資料
2. 將資料送入模型
3. 計算預測值與損失
4. 反向傳播
5. 更新模型參數
6. 使用驗證集評估模型
```python
num_epochs = 500
train_losses = []
val_losses = []
best_val_loss = float("inf")
best_model_state = None
for epoch in range(num_epochs):
# -------------------------
# Training
# -------------------------
model.train()
total_train_loss = 0.0
for batch_X, batch_y in train_loader:
# 清除上一批資料的梯度
optimizer.zero_grad()
# 前向傳播
predictions = model(batch_X)
# 計算損失
loss = criterion(predictions, batch_y)
# 反向傳播
loss.backward()
# 更新參數
optimizer.step()
total_train_loss += loss.item() * batch_X.size(0)
avg_train_loss = total_train_loss / len(train_loader.dataset)
# -------------------------
# Validation
# -------------------------
model.eval()
total_val_loss = 0.0
with torch.no_grad():
for batch_X, batch_y in val_loader:
predictions = model(batch_X)
loss = criterion(predictions, batch_y)
total_val_loss += loss.item() * batch_X.size(0)
avg_val_loss = total_val_loss / len(val_loader.dataset)
train_losses.append(avg_train_loss)
val_losses.append(avg_val_loss)
# 保存驗證集表現最好的模型
if avg_val_loss < best_val_loss:
best_val_loss = avg_val_loss
best_model_state = copy.deepcopy(model.state_dict())
if (epoch + 1) % 50 == 0:
print(
f"Epoch [{epoch + 1:3d}/{num_epochs}] "
f"Train Loss: {avg_train_loss:.4f}, "
f"Val Loss: {avg_val_loss:.4f}"
)
# 載入驗證集表現最好的模型
model.load_state_dict(best_model_state)
```
---
## 六、使用測試集評估模型
因為訓練時對 `y` 做了標準化,因此模型輸出也是標準化後的價格。評估時要先轉回原始價格尺度,再計算指標。
```python
model.eval()
with torch.no_grad():
test_predictions_scaled = model(X_test_tensor).numpy()
# 還原成原本的價格尺度
test_predictions = y_scaler.inverse_transform(
test_predictions_scaled
)
test_actual = y_scaler.inverse_transform(
y_test_scaled
)
# 計算評估指標
mse = mean_squared_error(test_actual, test_predictions)
rmse = np.sqrt(mse)
mae = mean_absolute_error(test_actual, test_predictions)
r2 = r2_score(test_actual, test_predictions)
print("\nTest Results")
print(f"MSE : {mse:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"MAE : {mae:.4f}")
print(f"R2 : {r2:.4f}")
```
### 評估指標說明
#### MSE
對較大的預測誤差懲罰較重。
#### RMSE
與目標值使用相同單位,較容易解讀。
#### MAE
表示平均預測誤差的大小,對離群值較不敏感。
#### \(R^2\)
表示模型解釋資料變異的能力:
- 越接近 1,通常代表模型越好
- 等於 0,代表和直接預測平均值差不多
- 小於 0,代表模型表現不佳
---
## 七、查看實際預測結果
```python
for i in range(10):
print(
f"實際價格: {test_actual[i][0]:8.2f}, "
f"預測價格: {test_predictions[i][0]:8.2f}"
)
```
---
## 八、繪製訓練曲線
訓練曲線可以觀察模型是否過擬合。
```python
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
plt.plot(train_losses, label="Training Loss")
plt.plot(val_losses, label="Validation Loss")
plt.xlabel("Epoch")
plt.ylabel("MSE Loss")
plt.title("Training and Validation Loss")
plt.legend()
plt.grid()
plt.show()
```
判斷方式:
- 訓練損失與驗證損失都下降:模型正在學習
- 訓練損失持續下降,但驗證損失上升:可能過擬合
- 兩者都很高且不下降:可能模型太簡單、學習率不適當,或資料本身噪聲太大
---
## 九、若使用真實 CSV 資料
假設資料檔案名稱為 `house_prices.csv`,欄位如下:
```text
area,age,rooms,distance,price
```
可以將資料建立部分改成:
```python
import pandas as pd
df = pd.read_csv("house_prices.csv")
X = df[["area", "age", "rooms", "distance"]].values
y = df[["price"]].values
```
後續的資料切分、標準化、Tensor 建立與模型訓練程式可以維持不變。
---
## 十、關鍵步驟總結
1. **準備資料**
- 找出輸入特徵與預測目標
- 處理缺失值與異常值
2. **切分資料**
- 訓練集用於更新模型參數
- 驗證集用於選擇模型與調整超參數
- 測試集只能在最後使用
3. **標準化資料**
- 只用訓練集估計平均值與標準差
- 驗證集、測試集只能使用相同轉換方式
4. **建立模型**
- 使用 `nn.Linear` 建立全連接層
- 使用 `ReLU` 增加非線性表達能力
- 迴歸輸出層通常輸出一個連續數值
5. **設定訓練方法**
- 損失函數:`MSELoss`
- 最佳化器:`Adam`
- 適當設定學習率、批次大小與訓練回合數
6. **模型評估**
- 使用 RMSE、MAE、\(R^2\)
- 評估前將標準化後的預測值轉回原始單位
7. **避免過擬合**
- 使用驗證集
- 保存最佳模型
- 使用 `weight_decay`
- 必要時加入 Dropout 或 Early Stopping
這個模型適合作為房價、銷售量、能源消耗量或產品需求量等連續數值預測問題的基礎 PyTorch 實作。
相關學習地圖、教學課程
Python 人工智慧