使用PyTorch實(shí)現(xiàn)線性回歸的全面指南
摘要
核心內(nèi)容包括:
- ? 深入剖析線性回歸的數(shù)學(xué)原理和梯度下降算法
- ? 兩種實(shí)現(xiàn)方式:從零實(shí)現(xiàn) vs PyTorch高級API
- ? 完整的數(shù)據(jù)準(zhǔn)備、模型訓(xùn)練、評估流程
- ? 4個實(shí)戰(zhàn)案例:房價預(yù)測、股票分析、醫(yī)療費(fèi)用、工業(yè)優(yōu)化
- ? 模型優(yōu)化技巧和超參數(shù)調(diào)優(yōu)指南
適合人群:
- PyTorch初學(xué)者
- 機(jī)器學(xué)習(xí)入門者
- 需要快速上手線性回歸的數(shù)據(jù)分析師
- 準(zhǔn)備面試的算法工程師
通過本文的學(xué)習(xí),你將掌握使用PyTorch實(shí)現(xiàn)線性回歸的完整技能,并能夠應(yīng)用到實(shí)際項(xiàng)目中。
第1章 線性回歸理論基礎(chǔ)
1.1 為什么用 PyTorch 學(xué)線性回歸?
線性回歸是機(jī)器學(xué)習(xí)入門的必修課,本質(zhì)是通過尋找特征與目標(biāo)值之間的線性關(guān)系來進(jìn)行預(yù)測。而在 PyTorch 中,這個過程被動態(tài)圖機(jī)制簡化——每個環(huán)節(jié)都變得可視化且易于調(diào)試。
雖然線性回歸可以用 scikit-learn 幾行代碼實(shí)現(xiàn),但用 PyTorch 手動構(gòu)建能深入理解神經(jīng)網(wǎng)絡(luò)的基本工作原理,這對后續(xù)學(xué)習(xí)更復(fù)雜模型至關(guān)重要。伊利諾伊大學(xué)的 ECE 364 課程甚至用整整三周(Week 6-8)專門講授線性回歸在 PyTorch 中的實(shí)現(xiàn)及其與數(shù)據(jù)集、數(shù)據(jù)加載器等組件的結(jié)合。
1.2 PyTorch 兩大核心特性
與 NumPy 相比,PyTorch 張量額外具備兩大關(guān)鍵特性:
- 自動微分:當(dāng)你在 PyTorch 中定義模型時,實(shí)際上在構(gòu)建一個計(jì)算圖——這個圖會記錄所有操作步驟,并在反向傳播時自動計(jì)算梯度
- GPU 加速:張量可以無縫遷移到 GPU 上進(jìn)行并行計(jì)算,顯著加速訓(xùn)練過程
線性回歸可以使用單層神經(jīng)網(wǎng)絡(luò)來實(shí)現(xiàn),所需要用到的概念和技巧也適用于更復(fù)雜的深度學(xué)習(xí)模型。
1.3 線性回歸的核心概念
線性回歸是機(jī)器學(xué)習(xí)中最基礎(chǔ)、最重要的算法之一,它假設(shè)目標(biāo)變量 y 與輸入特征 x 之間存在線性關(guān)系。
基本公式:
對于單變量線性回歸:
y = w * x + b + ε
對于多變量線性回歸:
y = w?x? + w?x? + ... + w?x? + b + ε
參數(shù)說明:
y:預(yù)測的目標(biāo)值(連續(xù)型變量)x?, x?, ..., x?:輸入特征w?, w?, ..., w?:權(quán)重參數(shù),表示各特征對目標(biāo)的影響程度b:偏置項(xiàng)(截距)ε:噪聲項(xiàng),表示模型無法解釋的隨機(jī)誤差
1.4 數(shù)學(xué)原理與公式推導(dǎo)
1.4.1 損失函數(shù)(均方誤差)
線性回歸使用均方誤差(MSE)作為損失函數(shù),衡量預(yù)測值與真實(shí)值之間的差距:
L(w, b) = (1/2n) * Σ(y? - ??)2
為什么要除以2?
- 為了在求導(dǎo)時抵消平方項(xiàng)的系數(shù)2,使梯度計(jì)算更簡潔
- 不影響優(yōu)化結(jié)果,因?yàn)檫@只是對損失函數(shù)的縮放
1.4.2 梯度計(jì)算
對損失函數(shù)求偏導(dǎo),得到梯度:
?L/?w? = (1/n) * Σ(?? - y?) * x?? ?L/?b = (1/n) * Σ(?? - y?)
1.4.3 梯度下降更新規(guī)則
w? ← w? - α * ?L/?w? b ← b - α * ?L/?b
其中 α 是學(xué)習(xí)率,控制每次更新的步長。
1.5 損失函數(shù)與優(yōu)化算法
1.5.1 常見損失函數(shù)對比
| 損失函數(shù) | 公式 | 適用場景 |
|---|---|---|
| MSE | (1/2n)Σ(y-?)² | 回歸問題,對異常值敏感 |
| MAE | (1/n)Σ | y-? |
| Huber | 分段函數(shù) | 回歸問題,平衡MSE和MAE |
1.5.2 優(yōu)化算法對比
| 優(yōu)化器 | 特點(diǎn) | 適用場景 |
|---|---|---|
| SGD | 簡單直接,需要手動調(diào)學(xué)習(xí)率 | 小數(shù)據(jù)集,簡單模型 |
| SGD with Momentum | 加入動量,加速收斂 | 一般場景 |
| Adam | 自適應(yīng)學(xué)習(xí)率,收斂快 | 大多數(shù)場景 |
| RMSprop | 適合非平穩(wěn)目標(biāo) | RNN等 |
第2章 PyTorch框架入門
2.1 PyTorch核心特性
- 動態(tài)計(jì)算圖:運(yùn)行時構(gòu)建計(jì)算圖,調(diào)試方便
- Pythonic:API設(shè)計(jì)符合Python習(xí)慣
- GPU加速:無縫支持CUDA,加速計(jì)算
- 自動求導(dǎo):自動計(jì)算梯度,簡化實(shí)現(xiàn)
2.2 環(huán)境配置與安裝
# 創(chuàng)建虛擬環(huán)境 python -m venv pytorch_env source pytorch_env/bin/activate # Linux/Mac # pytorch_env\Scripts\activate # Windows # 安裝PyTorch(CPU版本) pip install torch torchvision torchaudio # 安裝PyTorch(GPU版本,CUDA 11.8) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安裝其他依賴 pip install numpy pandas matplotlib seaborn jupyter
2.3 張量操作基礎(chǔ)
import torch
# 創(chuàng)建張量
x = torch.tensor([1.0, 2.0, 3.0]) # 從列表創(chuàng)建
y = torch.zeros(3) # 零張量
z = torch.ones(2, 3) # 全1張量
a = torch.randn(2, 2) # 標(biāo)準(zhǔn)正態(tài)分布
# 張量屬性
print(f"形狀: {x.shape}")
print(f"數(shù)據(jù)類型: {x.dtype}")
print(f"設(shè)備: {x.device}")
# 基本運(yùn)算
x = torch.tensor([1.0, 2.0, 3.0])
y = torch.tensor([4.0, 5.0, 6.0])
print(f"加法: {x + y}")
print(f"乘法: {x * y}")
print(f"點(diǎn)積: {torch.dot(x, y)}")
# 矩陣運(yùn)算
A = torch.randn(3, 2)
B = torch.randn(2, 4)
C = torch.matmul(A, B) # 矩陣乘法
print(f"矩陣乘法結(jié)果形狀: {C.shape}")
第3章 從零實(shí)現(xiàn)線性回歸
3.1 數(shù)據(jù)生成與準(zhǔn)備
import torch
import numpy as np
# 設(shè)置隨機(jī)種子,確??蓮?fù)現(xiàn)
torch.manual_seed(42)
np.random.seed(42)
# 定義真實(shí)的模型參數(shù)
true_w = torch.tensor([[2.0], [-3.4]]) # 真實(shí)權(quán)重
true_b = 4.2 # 真實(shí)偏置
# 生成數(shù)據(jù)
num_examples = 1000 # 樣本數(shù)量
num_inputs = 2 # 特征數(shù)量
# 從標(biāo)準(zhǔn)正態(tài)分布中隨機(jī)生成輸入特征
X = torch.normal(0, 1, (num_examples, num_inputs))
# 根據(jù)真實(shí)公式計(jì)算標(biāo)簽,并加入噪聲
noise = torch.normal(0, 0.01, (num_examples, 1))
y = torch.matmul(X, true_w) + true_b + noise
print(f"數(shù)據(jù)集形狀: X={X.shape}, y={y.shape}")
print(f"前3個樣本:\n{X[:3]}")
print(f"對應(yīng)標(biāo)簽:\n{y[:3]}")
3.2 模型參數(shù)初始化
# 初始化權(quán)重 w,從均值為0,標(biāo)準(zhǔn)差為0.01的正態(tài)分布中采樣
w = torch.normal(0, 0.01, size=(num_inputs, 1), requires_grad=True)
# 初始化偏置 b 為 0
b = torch.zeros(1, requires_grad=True)
print(f"初始權(quán)重 w:\n{w}")
print(f"初始偏置 b: ")
print(f"w 需要梯度: {w.requires_grad}")
print(f"b 需要梯度: {b.requires_grad}")
關(guān)鍵點(diǎn):
requires_grad=True告訴 PyTorch 這些參數(shù)需要計(jì)算梯度- 權(quán)重通常用小的隨機(jī)數(shù)初始化,避免對稱性問題
- 偏置可以初始化為0
3.3 前向傳播與損失計(jì)算
def linreg(X, w, b):
"""線性回歸模型的前向傳播"""
return torch.matmul(X, w) + b
def squared_loss(y_hat, y):
"""均方損失函數(shù)"""
# 將 y 的形狀調(diào)整為和 y_hat 一致
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
# 測試前向傳播
y_hat = linreg(X[:5], w, b)
print(f"預(yù)測值:\n{y_hat}")
# 測試損失計(jì)算
loss = squared_loss(y_hat, y[:5])
print(f"損失值:\n{loss}")
print(f"平均損失: {loss.mean()}")
3.4 反向傳播與參數(shù)更新
def sgd(params, lr, batch_size):
"""
小批量隨機(jī)梯度下降優(yōu)化器
參數(shù):
params: 需要更新的參數(shù)列表,如 [w, b]
lr: 學(xué)習(xí)率
batch_size: 批量大小
"""
with torch.no_grad(): # 更新參數(shù)時,不需要記錄梯度
for param in params:
# 梯度下降更新公式
param -= lr * param.grad / batch_size
# 更新后,手動將梯度清零
param.grad.zero_()
# 示例:假設(shè)我們有梯度
w.grad = torch.tensor([[0.1], [0.2]])
b.grad = torch.tensor([0.05])
lr = 0.03
print(f"更新前 w: {w.flatten()}")
print(f"更新前 b: {b.item()}")
sgd([w, b], lr, 10)
print(f"更新后 w: {w.flatten()}")
print(f"更新后 b: {b.item()}")
3.5 完整訓(xùn)練循環(huán)
import random
def data_iter(batch_size, features, labels):
"""
生成小批量數(shù)據(jù)的迭代器
"""
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices) # 打亂樣本順序
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)]
)
yield features[batch_indices], labels[batch_indices]
# 超參數(shù)設(shè)置
lr = 0.03 # 學(xué)習(xí)率
num_epochs = 5 # 訓(xùn)練輪數(shù)
batch_size = 10 # 批量大小
# 重新初始化參數(shù)
w = torch.normal(0, 0.01, size=(num_inputs, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# 開始訓(xùn)練
print("=" * 60)
print("開始訓(xùn)練線性回歸模型...")
print("=" * 60)
for epoch in range(num_epochs):
for X_batch, y_batch in data_iter(batch_size, X, y):
# 1. 前向傳播:計(jì)算預(yù)測值
y_hat = linreg(X_batch, w, b)
# 2. 計(jì)算損失
loss = squared_loss(y_hat, y_batch).mean()
# 3. 反向傳播:計(jì)算梯度
loss.backward()
# 4. 更新參數(shù)
sgd([w, b], lr, batch_size)
# 每個epoch結(jié)束后,計(jì)算在整個數(shù)據(jù)集上的平均損失
with torch.no_grad():
train_loss = squared_loss(linreg(X, w, b), y).mean()
print(f'Epoch {epoch + 1}/{num_epochs}, Loss: {train_loss:.6f}')
# 訓(xùn)練結(jié)束,查看學(xué)習(xí)到的參數(shù)
print("\n" + "=" * 60)
print("訓(xùn)練完成!模型參數(shù)對比:")
print("=" * 60)
print(f'真實(shí)的權(quán)重 w: {true_w.flatten().numpy()}')
print(f'學(xué)到的權(quán)重 w: {w.detach().flatten().numpy()}')
print(f'權(quán)重誤差: {((w - true_w).abs() / true_w.abs()).mean().item():.4%}')
print()
print(f'真實(shí)的偏置 b: {true_b:.4f}')
print(f'學(xué)到的偏置 b: {b.item():.4f}')
print(f'偏置誤差: {abs(b.item() - true_b) / abs(true_b):.4%}')
輸出示例:
============================================================ 開始訓(xùn)練線性回歸模型... ============================================================ Epoch 1/5, Loss: 0.042567 Epoch 2/5, Loss: 0.000152 Epoch 3/5, Loss: 0.000058 Epoch 4/5, Loss: 0.000051 Epoch 5/5, Loss: 0.000050 ============================================================ 訓(xùn)練完成!模型參數(shù)對比: ============================================================ 真實(shí)的權(quán)重 w: [ 2. -3.4] 學(xué)到的權(quán)重 w: [ 1.9998 -3.3995] 權(quán)重誤差: 0.0123% 真實(shí)的偏置 b: 4.2000 學(xué)到的偏置 b: 4.1998 偏置誤差: 0.0048%
第4章 PyTorch高級API實(shí)現(xiàn)
4.1 nn.Module模型定義
import torch.nn as nn
class LinearRegressionModel(nn.Module):
def __init__(self, input_dim):
super(LinearRegressionModel, self).__init__()
# 定義線性層
self.linear = nn.Linear(input_dim, 1)
def forward(self, x):
# 前向傳播
return self.linear(x)
# 創(chuàng)建模型
model = LinearRegressionModel(num_inputs)
print(model)
print(f"\n模型參數(shù):")
for name, param in model.named_parameters():
print(f" {name}: {param.shape}")
輸出:
LinearRegressionModel( (linear): Linear(in_features=2, out_features=1, bias=True) ) 模型參數(shù): linear.weight: torch.Size([1, 2]) linear.bias: torch.Size([1])
4.2 內(nèi)置損失函數(shù)與優(yōu)化器
# 定義損失函數(shù)
criterion = nn.MSELoss()
# 定義優(yōu)化器
optimizer = torch.optim.SGD(model.parameters(), lr=0.03)
# 訓(xùn)練循環(huán)
num_epochs = 5
for epoch in range(num_epochs):
for X_batch, y_batch in data_iter(batch_size, X, y):
# 前向傳播
y_pred = model(X_batch)
# 計(jì)算損失
loss = criterion(y_pred, y_batch)
# 反向傳播
optimizer.zero_grad() # 梯度清零
loss.backward() # 計(jì)算梯度
# 更新參數(shù)
optimizer.step()
# 打印訓(xùn)練進(jìn)度
if (epoch + 1) % 1 == 0:
with torch.no_grad():
y_pred = model(X)
epoch_loss = criterion(y_pred, y).item()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss:.6f}')
# 查看學(xué)習(xí)到的參數(shù)
print("\n學(xué)到的參數(shù):")
print(f"w: {model.linear.weight.data.numpy()}")
print(f"b: {model.linear.bias.item()}")
4.3 DataLoader數(shù)據(jù)加載器
from torch.utils.data import TensorDataset, DataLoader
# 創(chuàng)建數(shù)據(jù)集
dataset = TensorDataset(X, y)
# 創(chuàng)建數(shù)據(jù)加載器
data_loader = DataLoader(
dataset=dataset,
batch_size=batch_size,
shuffle=True, # 每個epoch打亂數(shù)據(jù)
num_workers=0 # 數(shù)據(jù)加載的子進(jìn)程數(shù)
)
# 使用 DataLoader 訓(xùn)練
model = LinearRegressionModel(num_inputs)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
for epoch in range(num_epochs):
for X_batch, y_batch in data_loader:
y_pred = model(X_batch)
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 1 == 0:
with torch.no_grad():
y_pred = model(X)
epoch_loss = criterion(y_pred, y).item()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss:.6f}')
4.4 訓(xùn)練過程可視化
import matplotlib.pyplot as plt
# 記錄訓(xùn)練過程
train_losses = []
model = LinearRegressionModel(num_inputs)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
for epoch in range(20):
epoch_loss = 0
for X_batch, y_batch in data_loader:
y_pred = model(X_batch)
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
avg_loss = epoch_loss / len(data_loader)
train_losses.append(avg_loss)
# 繪制損失曲線
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(train_losses) + 1), train_losses, marker='o')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.grid(True)
plt.savefig('training_loss.png', dpi=300, bbox_inches='tight')
plt.show()
# 預(yù)測結(jié)果可視化
with torch.no_grad():
y_pred = model(X).numpy()
plt.figure(figsize=(10, 6))
plt.scatter(y.numpy(), y_pred, alpha=0.5)
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--')
plt.xlabel('True Values')
plt.ylabel('Predictions')
plt.title('True vs Predicted Values')
plt.grid(True)
plt.savefig('predictions.png', dpi=300, bbox_inches='tight')
plt.show()
第5章 實(shí)戰(zhàn)應(yīng)用案例
5.1 房價預(yù)測項(xiàng)目
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score
# 生成模擬房價數(shù)據(jù)
np.random.seed(42)
n_samples = 1000
data = {
'area': np.random.uniform(50, 200, n_samples), # 面積
'rooms': np.random.randint(1, 6, n_samples), # 房間數(shù)
'age': np.random.uniform(0, 30, n_samples), # 房齡
'distance_to_center': np.random.uniform(1, 20, n_samples) # 距離市中心
}
# 生成價格
data['price'] = (
1.5 * data['area'] + # 面積系數(shù)
10 * data['rooms'] + # 房間數(shù)系數(shù)
-0.8 * data['age'] + # 房齡系數(shù)
-2 * data['distance_to_center'] + # 距離系數(shù)
50 + # 基礎(chǔ)價格
np.random.normal(0, 20, n_samples) # 噪聲
)
df = pd.DataFrame(data)
# 特征和標(biāo)簽
X = df[['area', 'rooms', 'age', 'distance_to_center']].values
y = df['price'].values.reshape(-1, 1)
# 劃分訓(xùn)練集和測試集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 特征標(biāo)準(zhǔn)化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 轉(zhuǎn)換為PyTorch張量
X_train_tensor = torch.FloatTensor(X_train_scaled)
y_train_tensor = torch.FloatTensor(y_train)
X_test_tensor = torch.FloatTensor(X_test_scaled)
y_test_tensor = torch.FloatTensor(y_test)
# 創(chuàng)建數(shù)據(jù)加載器
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
train_loader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)
# 定義模型
class HousePriceModel(nn.Module):
def __init__(self, input_dim):
super(HousePriceModel, self).__init__()
self.linear = nn.Linear(input_dim, 1)
def forward(self, x):
return self.linear(x)
model = HousePriceModel(X_train_scaled.shape[1])
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# 訓(xùn)練
num_epochs = 100
for epoch in range(num_epochs):
for X_batch, y_batch in train_loader:
y_pred = model(X_batch)
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 評估
model.eval()
with torch.no_grad():
y_pred_train = model(X_train_tensor).numpy()
y_pred_test = model(X_test_tensor).numpy()
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))
train_r2 = r2_score(y_train, y_pred_train)
test_r2 = r2_score(y_test, y_pred_test)
print(f"訓(xùn)練集 RMSE: {train_rmse:.2f} (萬元)")
print(f"測試集 RMSE: {test_rmse:.2f} (萬元)")
print(f"訓(xùn)練集 R2: {train_r2:.4f}")
print(f"測試集 R2: {test_r2:.4f}")
5.2 股票趨勢分析
# 模擬股票數(shù)據(jù)
dates = pd.date_range('2023-01-01', periods=365, freq='D')
np.random.seed(42)
stock_data = {
'date': dates,
'open': np.random.uniform(100, 150, 365),
'high': np.random.uniform(105, 155, 365),
'low': np.random.uniform(95, 145, 365),
'volume': np.random.uniform(1000000, 5000000, 365),
'close': np.random.uniform(100, 150, 365) + np.random.normal(0, 2, 365)
}
stock_df = pd.DataFrame(stock_data)
stock_df['return'] = stock_df['close'].pct_change() # 日收益率
stock_df = stock_df.dropna()
print("股票數(shù)據(jù)示例:")
print(stock_df.head())
print(f"\n數(shù)據(jù)集形狀: {stock_df.shape}")
5.3 醫(yī)療費(fèi)用預(yù)測
# 模擬醫(yī)療數(shù)據(jù)
n_patients = 1000
medical_data = {
'age': np.random.randint(18, 80, n_patients),
'bmi': np.random.uniform(18, 40, n_patients),
'smoker': np.random.choice([0, 1], n_patients, p=[0.8, 0.2]),
'children': np.random.randint(0, 5, n_patients),
'charges': (
200 * medical_data['age'] +
300 * medical_data['bmi'] +
10000 * medical_data['smoker'] +
500 * medical_data['children'] +
np.random.normal(0, 2000, n_patients)
)
}
medical_df = pd.DataFrame(medical_data)
print("醫(yī)療數(shù)據(jù)統(tǒng)計(jì):")
print(f"吸煙者平均費(fèi)用: {medical_df[medical_df['smoker']==1]['charges'].mean():.2f}")
print(f"非吸煙者平均費(fèi)用: {medical_df[medical_df['smoker']==0]['charges'].mean():.2f}")
print(f"費(fèi)用標(biāo)準(zhǔn)差: {medical_df['charges'].std():.2f}")
5.4 工業(yè)生產(chǎn)優(yōu)化
# 模擬生產(chǎn)數(shù)據(jù)
n_samples = 500
production_data = {
'temperature': np.random.uniform(150, 250, n_samples),
'pressure': np.random.uniform(5, 15, n_samples),
'time': np.random.uniform(10, 60, n_samples),
'speed': np.random.uniform(50, 150, n_samples),
'quality': (
0.3 * production_data['temperature'] +
0.5 * production_data['pressure'] +
0.2 * production_data['time'] -
0.1 * production_data['speed'] +
50 + np.random.normal(0, 5, n_samples)
)
}
prod_df = pd.DataFrame(production_data)
# 找出最優(yōu)參數(shù)組合
optimal_params = {
'temperature': prod_df.loc[prod_df['quality'].idxmax(), 'temperature'],
'pressure': prod_df.loc[prod_df['quality'].idxmax(), 'pressure'],
'time': prod_df.loc[prod_df['quality'].idxmax(), 'time'],
'speed': prod_df.loc[prod_df['quality'].idxmax(), 'speed'],
'quality': prod_df['quality'].max()
}
print("最優(yōu)生產(chǎn)參數(shù):")
for k, v in optimal_params.items():
print(f" {k}: {v:.2f}")
第6章 模型優(yōu)化與調(diào)參
6.1 學(xué)習(xí)率調(diào)優(yōu)策略
# 不同學(xué)習(xí)率對比
learning_rates = [0.001, 0.01, 0.03, 0.1, 0.3]
results = {}
for lr in learning_rates:
model = LinearRegressionModel(num_inputs)
optimizer = torch.optim.SGD(model.parameters(), lr=lr)
criterion = nn.MSELoss()
losses = []
for epoch in range(20):
epoch_loss = 0
for X_batch, y_batch in data_loader:
y_pred = model(X_batch)
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
losses.append(epoch_loss / len(data_loader))
results[lr] = losses[-1] # 記錄最終損失
print("不同學(xué)習(xí)率的最終損失:")
for lr, loss in results.items():
print(f" lr={lr:.3f}: {loss:.6f}")
6.2 批量大小選擇
batch_sizes = [4, 8, 16, 32, 64, 128]
results = {}
for bs in batch_sizes:
data_loader = DataLoader(dataset=dataset, batch_size=bs, shuffle=True)
model = LinearRegressionModel(num_inputs)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
criterion = nn.MSELoss()
for epoch in range(10):
for X_batch, y_batch in data_loader:
y_pred = model(X_batch)
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
final_loss = criterion(model(X), y).item()
results[bs] = final_loss
print("不同批量大小的最終損失:")
for bs, loss in results.items():
print(f" batch_size={bs}: {loss:.6f}")
6.3 正則化技術(shù)
# L2正則化(權(quán)重衰減)
model = LinearRegressionModel(num_inputs)
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.01,
weight_decay=0.01 # L2正則化系數(shù)
)
criterion = nn.MSELoss()
# 訓(xùn)練
for epoch in range(20):
for X_batch, y_batch in data_loader:
y_pred = model(X_batch)
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("L2正則化后的權(quán)重:")
print(f"w: {model.linear.weight.data.numpy()}")
print(f"權(quán)重范數(shù): {torch.norm(model.linear.weight).item():.4f}")
6.4 早停與模型保存
# 早停實(shí)現(xiàn)
class EarlyStopping:
def __init__(self, patience=5, min_delta=0.001):
self.patience = patience
self.min_delta = min_delta
self.counter = 0
self.best_loss = None
self.early_stop = False
def __call__(self, val_loss):
if self.best_loss is None:
self.best_loss = val_loss
elif val_loss > self.best_loss - self.min_delta:
self.counter += 1
if self.counter >= self.patience:
self.early_stop = True
else:
self.best_loss = val_loss
self.counter = 0
return self.early_stop
# 模型保存與加載
model = LinearRegressionModel(num_inputs)
torch.save(model.state_dict(), 'linear_model.pth')
# 加載模型
model_loaded = LinearRegressionModel(num_inputs)
model_loaded.load_state_dict(torch.load('linear_model.pth'))
model_loaded.eval()
第7章 常見問題與解決方案
| 問題 | 解決方案 |
|---|---|
| 損失不下降 | 增大學(xué)習(xí)率或檢查數(shù)據(jù)是否標(biāo)準(zhǔn)化 |
| 損失震蕩劇烈 | 減小學(xué)習(xí)率 |
| 梯度為 NaN | 學(xué)習(xí)率過大導(dǎo)致梯度爆炸,降低學(xué)習(xí)率 |
| 訓(xùn)練集和測試集差距大 | 過擬合,增加數(shù)據(jù)量或添加正則化 |
| 忘記 zero_grad() | 每次迭代前必須調(diào)用 optimizer.zero_grad() |
| GPU 不可用 | 檢查 CUDA 版本與 PyTorch 版本是否匹配 |
7.1 損失不下降
問題: 訓(xùn)練過程中損失值保持不變或震蕩
解決方案:
# 1. 檢查學(xué)習(xí)率是否合適
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 嘗試更小的學(xué)習(xí)率
# 2. 檢查數(shù)據(jù)是否標(biāo)準(zhǔn)化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. 檢查梯度是否正常
for name, param in model.named_parameters():
print(f"{name} gradient norm: {param.grad.norm().item()}")
7.2 過擬合問題
問題: 訓(xùn)練集表現(xiàn)好,測試集表現(xiàn)差
解決方案:
# 1. 使用正則化 optimizer = torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=0.01) # 2. 增加數(shù)據(jù) # 3. 減少模型復(fù)雜度 # 4. 使用早停 early_stopping = EarlyStopping(patience=5)
7.3 GPU加速
# 檢查GPU可用性
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用設(shè)備: {device}")
# 將模型和數(shù)據(jù)移動到GPU
model = model.to(device)
X = X.to(device)
y = y.to(device)
# 訓(xùn)練時同樣在GPU上
for X_batch, y_batch in data_loader:
X_batch = X_batch.to(device)
y_batch = y_batch.to(device)
y_pred = model(X_batch)
loss = criterion(y_pred, y_batch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
總結(jié)
本文從 PyTorch 的基礎(chǔ)特性出發(fā),系統(tǒng)講解了線性回歸的完整實(shí)現(xiàn)流程——從 nn.Linear 的模型定義、nn.MSELoss 的損失計(jì)算、optim.SGD 的參數(shù)優(yōu)化,到 DataLoader 的批量加載、GPU 加速的部署優(yōu)化、模型的保存與評估。核心要點(diǎn):
- PyTorch 三大核心組件:
nn.Linear(模型)、nn.MSELoss(損失)、optim.SGD(優(yōu)化器)是構(gòu)建任何深度學(xué)習(xí)模型的基石 - 訓(xùn)練循環(huán)四步法:前向傳播 → 損失計(jì)算 → 反向傳播 → 參數(shù)更新,是 PyTorch 訓(xùn)練的標(biāo)準(zhǔn)范式
- 工程化實(shí)踐:DataLoader 批量加載、訓(xùn)練測試分離、GPU 加速、模型保存加載是實(shí)際項(xiàng)目中不可或缺的技能
- 從零到框架的對應(yīng)關(guān)系:理解簡潔 API 背后的實(shí)現(xiàn)邏輯,是掌握深度學(xué)習(xí)的必經(jīng)之路
線性回歸雖簡單,但其中蘊(yùn)含的前向傳播、損失計(jì)算、梯度下降、參數(shù)更新等概念,是所有復(fù)雜深度學(xué)習(xí)模型的基礎(chǔ)。掌握 PyTorch 實(shí)現(xiàn)線性回歸的完整流水線,你將輕松過渡到更復(fù)雜的神經(jīng)網(wǎng)絡(luò)模型。
附錄
附錄A:完整代碼實(shí)現(xiàn)
"""
PyTorch線性回歸完整實(shí)現(xiàn)
"""
import torch
import numpy as np
import random
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset, DataLoader
# ==================== 1. 數(shù)據(jù)生成 ====================
def generate_data(num_examples=1000, num_inputs=2, seed=42):
"""生成帶噪聲的線性數(shù)據(jù)"""
torch.manual_seed(seed)
np.random.seed(seed)
true_w = torch.tensor([[2.0], [-3.4]])
true_b = 4.2
X = torch.normal(0, 1, (num_examples, num_inputs))
noise = torch.normal(0, 0.01, (num_examples, 1))
y = torch.matmul(X, true_w) + true_b + noise
return X, y, true_w, true_b
# ==================== 2. 數(shù)據(jù)迭代器 ====================
def data_iter(batch_size, features, labels):
"""生成小批量數(shù)據(jù)"""
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)]
)
yield features[batch_indices], labels[batch_indices]
# ==================== 3. 模型定義 ====================
def linreg(X, w, b):
"""線性回歸模型"""
return torch.matmul(X, w) + b
# ==================== 4. 損失函數(shù) ====================
def squared_loss(y_hat, y):
"""均方損失函數(shù)"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
# ==================== 5. 優(yōu)化器 ====================
def sgd(params, lr, batch_size):
"""小批量隨機(jī)梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
# ==================== 6. 訓(xùn)練函數(shù) ====================
def train_linear_regression(
X, y, true_w, true_b,
lr=0.03, num_epochs=5, batch_size=10
):
"""訓(xùn)練線性回歸模型"""
num_inputs = X.shape[1]
# 初始化參數(shù)
w = torch.normal(0, 0.01, size=(num_inputs, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
losses = []
print("開始訓(xùn)練...")
print("=" * 60)
for epoch in range(num_epochs):
for X_batch, y_batch in data_iter(batch_size, X, y):
# 前向傳播
y_hat = linreg(X_batch, w, b)
# 計(jì)算損失
loss = squared_loss(y_hat, y_batch).mean()
# 反向傳播
loss.backward()
# 更新參數(shù)
sgd([w, b], lr, batch_size)
# 計(jì)算epoch損失
with torch.no_grad():
epoch_loss = squared_loss(linreg(X, w, b), y).mean().item()
losses.append(epoch_loss)
print(f'Epoch {epoch + 1}/{num_epochs}, Loss: {epoch_loss:.6f}')
# 評估結(jié)果
print("\n" + "=" * 60)
print("訓(xùn)練完成!")
print("=" * 60)
print(f'真實(shí)權(quán)重: {true_w.flatten().numpy()}')
print(f'學(xué)到權(quán)重: {w.detach().flatten().numpy()}')
print(f'權(quán)重相對誤差: {((w - true_w).abs() / true_w.abs()).mean().item():.4%}')
print()
print(f'真實(shí)偏置: {true_b:.4f}')
print(f'學(xué)到偏置: {b.item():.4f}')
print(f'偏置相對誤差: {abs(b.item() - true_b) / abs(true_b):.4%}')
return w, b, losses
# ==================== 7. 主函數(shù) ====================
def main():
# 生成數(shù)據(jù)
X, y, true_w, true_b = generate_data()
# 訓(xùn)練模型
w, b, losses = train_linear_regression(X, y, true_w, true_b)
# 可視化損失曲線
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(losses) + 1), losses, marker='o')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.grid(True)
plt.savefig('training_loss.png', dpi=300, bbox_inches='tight')
plt.show()
if __name__ == "__main__":
main()
附錄B:API速查表
| 功能 | PyTorch API | 說明 |
|---|---|---|
| 創(chuàng)建張量 | torch.tensor() | 從數(shù)據(jù)創(chuàng)建張量 |
| 隨機(jī)張量 | torch.randn() | 標(biāo)準(zhǔn)正態(tài)分布 |
| 線性層 | nn.Linear() | 全連接層 |
| MSE損失 | nn.MSELoss() | 均方誤差 |
| SGD優(yōu)化器 | optim.SGD() | 隨機(jī)梯度下降 |
| Adam優(yōu)化器 | optim.Adam() | 自適應(yīng)矩估計(jì) |
| 數(shù)據(jù)加載 | DataLoader() | 批量數(shù)據(jù)加載 |
通過本文,你不僅掌握了線性回歸的理論和實(shí)現(xiàn),更建立了使用PyTorch進(jìn)行深度學(xué)習(xí)開發(fā)的完整技能體系。
以上就是使用PyTorch實(shí)現(xiàn)線性回歸的全面指南的詳細(xì)內(nèi)容,更多關(guān)于PyTorch實(shí)現(xiàn)線性回歸的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
pycharm 使用anaconda為默認(rèn)環(huán)境的操作
這篇文章主要介紹了pycharm 使用anaconda為默認(rèn)環(huán)境的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
python設(shè)置環(huán)境變量路徑實(shí)現(xiàn)過程
本文介紹設(shè)置Python路徑的多種方法:臨時設(shè)置(Windows用`set`,Linux/macOS用`export`)、永久設(shè)置(系統(tǒng)屬性或shell配置文件)、動態(tài)修改(`sys.path.append`)、虛擬環(huán)境管理路徑,需注意路徑格式與系統(tǒng)一致,并重啟終端生效2025-07-07
Python-copy()與deepcopy()區(qū)別詳解
這篇文章主要介紹了Python-copy()與deepcopy()區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07

