Pytorch 核心操作全總結!零基礎必備!
在深度學習與人工智能領域,PyTorch已成為研究者與開發者手中的利劍,以其靈活高效的特性,不斷推動著新技術的邊界。對于每一位致力于掌握PyTorch精髓的學習者來說,深入了解其核心操作不僅是提升技能的關鍵,也是邁向高級應用與創新研究的必經之路。本文精心梳理了PyTorch的核心操作,這不僅是一份全面的技術指南,更是每一個PyTorch實踐者的智慧錦囊,建議收藏!
一、張量創建和基本操作
1. 張量創建
(1) 從Python列表或Numpy數組創建張量
使用torch.tensor()函數可以直接從Python列表創建張量。并且,PyTorch設計時考慮了與NumPy的互操作性,也可以使用torch.tensor()函數從NumPy數組創建張量。
import numpy as np
import torch
# 從列表創建張量
list_data = [1, 2, 3, 4]
tensor_from_list = torch.tensor(list_data)
print(tensor_from_list) # tensor([1, 2, 3, 4])
# 從NumPy數組創建張量
np_array = np.array([1, 2, 3])
tensor_from_np_tensor = torch.tensor(np_array)
print(tensor_from_np_tensor) # tensor([1, 2, 3], dtype=torch.int32)
(2) 使用固定數值創建張量
torch.zeros(shape)和torch.ones(shape):創建指定形狀的全零或全一張量。
'''
tensor([[0., 0., 0.],
[0., 0., 0.]])
'''
zeros_tensor = torch.zeros(2, 3)
'''
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
'''
ones_tensor = torch.ones(3, 4)
torch.rand(shape):創建指定形狀的隨機浮點數張量(0到1之間)或標準正態分布的張量。
'''
tensor([[0.7632, 0.9953, 0.8954],
[0.8681, 0.7707, 0.8806]])
'''
random_tensor = torch.rand(2, 3)
'''
tensor([[ 0.1873, -1.6907, 0.4717, 1.0271],
[-1.0680, 0.7490, -0.5693, 0.6490],
[ 0.0429, -1.5796, -2.3312, -0.2733]])
'''
normal_tensor = torch.randn(3, 4)
torch.arange(start, end=None, step=1, dtype=None, layout=torch.strided, device=None, requires_grad=False):創建一個等差序列張量,默認step為1。
arange_tensor = torch.arange(1, 10) #tensor([1, 2, 3, 4, 5, 6, 7, 8, 9])
2. 張量的基本操作
(1) 張量索引和切片
張量的索引和切片類似于Python列表,可以訪問和修改張量的特定元素或子集。
# 創建一個張量
tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])
# 索引單個元素
element = tensor[0, 0] # 1
# 切片
slice_1 = tensor[0, :] # [1, 2, 3]
slice_2 = tensor[:, 1] # [2, 5]
# 修改元素
tensor[0, 0] = 7
print(tensor) # [[7, 2, 3], [4, 5, 6]]
(2) 形狀操作
- shape屬性獲取張量的形狀:
shape = tensor.shape # torch.Size([2, 3])
- unsqueeze(dim)在指定維度添加一個大小為1的新維度:
expanded_tensor = tensor.unsqueeze(0) # 添加一個新維度,形狀變為[1, 2, 3]
- reshape(shape)或view(shape)改變張量的形狀:
reshaped_tensor = tensor.reshape(6) # 變為形狀為[6]的一維張量
- transpose(dim0, dim1)交換指定的兩個維度:
transposed_tensor = tensor.transpose(0, 1) # 交換第一和第二維度
(3) 數學運算
PyTorch張量支持廣泛的數學運算,包括基本的算術運算、元素級運算、矩陣運算等。以tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])為例。
- 基本運算(適用于標量、一維或多維張量):
addition = tensor + tensor # [[2, 4, 6], [8, 10, 12]]。
subtraction = tensor - tensor #[[0, 0, 0], [0, 0, 0]]
multiplication = tensor * tensor #[[1, 4, 9], [16, 25, 36]]
division = tensor / tensor #[[1, 1, 1], [1, 1, 1]]
- 元素級運算(*運算符在這種情況下表示逐元素乘法,而不是矩陣乘法):
elementwise_product = tensor * tensor # [[1, 4, 9], [16, 25, 36]]
- 矩陣運算(使用@運算符或torch.matmul()):
matrix_product = tensor @ tensor.t() # 或者 torch.matmul(tensor, tensor.t())
# tensor @ tensor.t() 結果是一個標量,因為張量是方陣,且張量與其轉置的點積等于1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32
- 廣播機制(使得不同形狀的張量能夠進行運算):
'''
結果是一個2x3的張量,其中每個元素都是tensor對應位置的元素加上1
broadcasted_addition = [[2, 3, 4], [5, 6, 7]]
'''
broadcasted_addition = tensor + torch.tensor([1, 1, 1])
- 比較運算(返回布爾張量):
# 返回一個2x3的布爾張量,所有元素都為False,因為每個元素都不大于自身。
greater_than = tensor > tensor
- 聚合運算(如求和、平均、最大值、最小值等):
sum = torch.sum(tensor) # 返回張量所有元素的總和,即1+2+3+4+5+6=21。
mean = torch.mean(tensor) #返回張量的平均值,即21/6=3.5。
# 返回最大值張量[4, 5, 6]和最大值的索引[1, 1, 1],因為第二行的所有元素都是最大值。
max_value, max_index = torch.max(tensor, dim=0) # 按列求最大值和對應索引
二、自動求導
在深度學習中,自動求導(automatic differentiation)是關鍵步驟,用于計算損失函數對模型參數的梯度。PyTorch提供了自動求導機制,可以輕松地定義和計算復雜的神經網絡。
1. 張量的requires_grad屬性
在PyTorch中,requires_grad屬性用于決定張量是否應該在計算過程中跟蹤其操作,以便進行自動求導。如果計算張量的梯度,就需要設置requires_grad=True。
import torch
# 創建一個張量并設置requires_grad=True
x = torch.tensor([1.0, 2.0], requires_grad=True)
# Original tensor: tensor([1., 2.], requires_grad=True)
print("Original tensor:", x)
2. 張量操作與計算圖
在PyTorch中,張量操作與計算圖緊密相關,因為計算圖是實現自動求導(autograd)的關鍵。當一個張量的requires_grad屬性被設置為True時,PyTorch會記錄對該張量的所有操作,形成一個計算圖。這個圖描述了從輸入張量到輸出張量的計算路徑,每個節點代表一個張量,邊代表操作。
import torch
# 創建一個需要梯度的張量
x = torch.tensor([1.0, 2.0], requires_grad=True)
# 張量操作
y = x + 2 # 加法操作
z = y * y * 3 # 乘法操作
out = z.mean() # 平均值操作
此時,計算圖已經隱含地構建完成,記錄了從x到out的所有操作。其中,x是計算圖的起點,out是終點。
3. 計算梯度
要計算梯度,只需對計算圖的最終輸出調用.backward()方法。
# 計算梯度
out.backward()
# 現在,x的梯度已經計算出來了,可以通過x.grad屬性獲取
print("Gradient of x with respect to the output:", x.grad)
out.backward()會沿著計算圖反向傳播,計算所有涉及張量的梯度。在本例中,x.grad將會給出x相對于out的梯度,即out關于x的偏導數。這在訓練神經網絡時非常有用,因為可以用來更新網絡的權重。
4. 阻止梯度追蹤
在某些場景下,比如驗證模型或計算某些不需要更新參數的中間結果時,阻止梯度追蹤可以減少內存消耗和提高效率。使用.detach()或torch.no_grad()是實現這一目的的有效手段。
- 使用.detach()方法: 返回一個與原始張量數值相同的新張量,但不跟蹤梯度。
new_tensor = original_tensor.detach()
- 使用torch.no_grad()上下文管理器。
with torch.no_grad():
# 在此區域內進行的操作不會追蹤梯度
intermediate_result = some_operation(original_tensor)
5. 控制梯度計算的上下文管理器
torch.autograd.set_grad_enabled(True|False) 是另一個強大的工具,用于全局控制是否在代碼的特定部分進行梯度計算。相比.detach()和torch.no_grad(),它提供了更多的靈活性,因為它允許在代碼的不同部分動態開啟或關閉梯度追蹤,這對于復雜的模型調試、性能優化或混合精度訓練等場景特別有用。
import torch
# 默認情況下,梯度追蹤是開啟的
print(f"當前梯度追蹤狀態: {torch.is_grad_enabled()}") # 輸出: True
# 使用set_grad_enabled(False)關閉梯度追蹤
with torch.autograd.set_grad_enabled(False):
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x * 2
print(f"在上下文中,梯度追蹤狀態: {torch.is_grad_enabled()}") # 輸出: False
print(f"y的requires_grad屬性: {y.requires_grad}") # 輸出: False
# 離開上下文后,梯度追蹤狀態恢復到之前的狀態
print(f"離開上下文后,梯度追蹤狀態: {torch.is_grad_enabled()}") # 輸出: True
# 這里x的梯度追蹤仍然是開啟的,除非在其他地方被改變
6. 優化器與自動求導結合
在訓練神經網絡模型時,通常會使用優化器(optimizer)來更新模型的參數。優化器利用自動求導計算出的梯度來調整參數,以最小化損失函數。以下是一個使用隨機梯度下降(SGD)優化器的簡單示例:
import torch
from torch import nn, optim
# 定義一個簡單的線性模型
model = nn.Linear(2, 1) # 輸入2個特征,輸出1個值
# 假設有一些輸入數據和標簽
inputs = torch.randn(10, 2) # 10個樣本,每個樣本2個特征
labels = torch.randn(10, 1) # 10個樣本,每個樣本1個標簽
# 創建優化器,指定要更新的模型參數
optimizer = optim.SGD(model.parameters(), lr=0.01) # 學習率為0.01
# 前向傳播
outputs = model(inputs)
# 損失函數
loss = nn.MSELoss()(outputs, labels)
# 反向傳播并計算梯度
loss.backward()
# 使用優化器更新參數
optimizer.step()
# 清除梯度(防止梯度累積)
optimizer.zero_grad()
三、神經網絡層
在PyTorch中,nn.Module是構建神經網絡模型的核心類,它提供了一個模塊化的框架,可以方便地組合各種層和操作。
1. 創建自定義神經網絡層
創建自定義神經網絡層是PyTorch中常見的做法。以下是如何創建一個名為CustomLayer的自定義層,它包含一個線性層和一個激活函數(例如ReLU):
import torch
import torch.nn as nn
class CustomLayer(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(CustomLayer, self).__init__()
# 創建線性層
self.linear = nn.Linear(input_size, hidden_size)
# 創建ReLU激活函數
self.relu = nn.ReLU()
# 創建輸出線性層(如果需要的話,例如對于分類任務)
self.output_linear = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 應用線性變換
x = self.linear(x)
# 應用ReLU激活函數
x = self.relu(x)
# 如果需要,可以添加更多的操作,例如另一個線性層
x = self.output_linear(x)
return x
其中,CustomLayer類繼承自nn.Module,并在__init__方法中定義了兩個線性層(一個輸入層和一個輸出層)以及一個ReLU激活函數。forward方法描述了輸入到輸出的計算流程:首先,輸入通過線性層,然后通過ReLU激活,最后通過輸出線性層。如果只需要一個線性變換和激活,可以去掉output_linear。
使用這個自定義層,可以像使用內置層一樣在模型中實例化和使用它:
input_size = 10
hidden_size = 20
output_size = 5
model = CustomLayer(input_size, hidden_size, output_size)
2. 構建復雜的神經網絡模型
構建復雜的神經網絡模型通常涉及到將多個基本層或者自定義層按照特定順序組合起來。以下是一個使用自定義SimpleLayer類來構建多層感知機(MLP)的示例:
import torch
import torch.nn as nn
class SimpleLayer(nn.Module):
def __init__(self, input_size, output_size):
super(SimpleLayer, self).__init__()
self.linear = nn.Linear(input_size, output_size)
self.relu = nn.ReLU()
def forward(self, x):
x = self.linear(x)
x = self.relu(x)
return x
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(MLP, self).__init__()
# 第一層:輸入層到隱藏層
self.layer1 = SimpleLayer(input_dim, hidden_dim)
# 第二層:隱藏層到輸出層
self.layer2 = SimpleLayer(hidden_dim, output_dim)
def forward(self, x):
x = self.layer1(x) # 第一層前向傳播
x = self.layer2(x) # 第二層前向傳播
return x
# 實例化一個MLP模型
input_dim = 784 # 假設輸入維度為784(例如,MNIST數據集)
hidden_dim = 128 # 隱藏層維度
output_dim = 10 # 輸出維度(例如,10類分類問題)
model = MLP(input_dim, hidden_dim, output_dim)
# 打印模型結構
print(model)
在以上代碼中,MLP類定義了一個包含兩個SimpleLayer實例的多層感知機。第一個SimpleLayer接收輸入數據并轉換到隱藏層空間,第二個SimpleLayer則負責從隱藏層空間映射到輸出層。通過這種方式,可以靈活地堆疊多個層來構造復雜的神經網絡模型,每增加一層,模型的表達能力就可能增強,從而能學習到更復雜的輸入-輸出映射關系。
3. 模塊的嵌套和子模塊
在PyTorch中,nn.Module的嵌套和子模塊是構建復雜神經網絡架構的關鍵。下面是一個名為ComplexModel的示例,它包含了兩個子模塊:一個CustomLayer和一個MLP:
import torch
import torch.nn as nn
class ComplexModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(ComplexModel, self).__init__()
# 創建一個CustomLayer實例
self.custom_layer = CustomLayer(input_size, hidden_size, output_size)
# 創建一個MLP實例
self.mlp = MLP(hidden_size, hidden_size, output_size)
def forward(self, x):
x = self.custom_layer(x)
x = self.mlp(x)
return x
# 實例化一個ComplexModel
input_size = 784 # 假設輸入維度為784
hidden_size = 128 # 隱藏層維度
output_size = 10 # 輸出維度
model = ComplexModel(input_size, hidden_size, output_size)
# 打印模型結構
print(model)
其中,ComplexModel類包含兩個子模塊:custom_layer和mlp。custom_layer是一個自定義層,而mlp是一個多層感知機。當在ComplexModel的forward方法中調用這些子模塊時,它們的計算將按順序進行,并且所有子模塊的參數和梯度都會被自動跟蹤。這種模塊化的方法使得代碼易于理解和維護,同時可以方便地重用和組合現有的層和模型。
4. 訪問模塊的參數
在PyTorch中,nn.Module類提供了兩種方法來訪問模型的參數:parameters()和named_parameters()。這兩個方法都可以用來遍歷模型的所有參數,但它們的區別在于返回的內容。
- parameters()方法: 返回一個可迭代的生成器,其中每個元素是一個張量,代表模型的一個參數。
model = ComplexModel()
for param in model.parameters():
print(param)
- named_parameters()方法: 返回一個可迭代的生成器,其中每個元素是一個元組,包含參數的名稱和對應的張量。
model = ComplexModel()
for name, param in model.named_parameters():
print(f"Name: {name}, Parameter: {param}")
在實際應用中,通常使用named_parameters()方法,因為這樣可以同時獲取參數的名稱,這對于調試和可視化模型參數很有用。
5. 模型的保存與加載
要保存模型的狀態字典,可以使用state_dict()方法,然后使用torch.save()將其寫入磁盤。加載模型時,首先創建一個模型實例,然后使用load_state_dict()方法加載保存的參數。
- 保存模型:
torch.save(model.state_dict(), 'model.pth')
- 加載模型:
model = ComplexModel(input_size, hidden_size, output_size)
model.load_state_dict(torch.load('model.pth'))
6. 模型的設備移動
使用to()方法可以將模型及其所有參數移到GPU或CPU上。如果設備可用,它會嘗試將模型移動到GPU上,否則保留在CPU上。將模型移動到GPU(如果可用):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
7. 自定義層和操作
要創建自定義的神經網絡層或操作,可以繼承nn.Module,再實現新的前向傳播邏輯,包括新的數學函數、正則化、注意力機制等。例如,假設要創建一個自定義的歸一化層:
class CustomNormalization(nn.Module):
def __init__(self, dim):
super(CustomNormalization, self).__init__()
self.dim = dim
def forward(self, x):
mean = x.mean(dim=self.dim, keepdim=True)
std = x.std(dim=self.dim, keepdim=True)
return (x - mean) / (std + 1e-8)
model.add_module('custom_normalization', CustomNormalization(1))
首先定義了一個新的層CustomNormalization,它計算輸入張量在指定維度上的平均值和標準差,然后對輸入進行歸一化。這個新層可以像其他任何nn.Module實例一樣添加到模型中,并在forward方法中使用。
四、優化器
在 PyTorch 中,優化器(Optimizer)是用于更新神經網絡模型參數的工具。優化器基于模型參數的梯度信息來調整參數,從而最小化或最大化某個損失函數。PyTorch 提供了多種優化器,包括隨機梯度下降(SGD)、Adam、RMSprop 等。
1. SGD
SGD是最基礎的優化算法,它根據梯度的方向逐步調整模型參數,以減少損失函數的值。在PyTorch中,可以通過以下方式創建一個SGD優化器:
import torch.optim as optim
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
其中,model.parameters()用于獲取模型的所有可學習參數。lr是學習率,決定了參數更新的步長。momentum是動量項,默認為0,可以加速學習過程并有助于跳出局部最小值。
2. Adam
Adam(Adaptive Moment Estimation)是另一種廣泛使用的優化器,它結合了動量和自適應學習率的優點,可自動調整每個參數的學習率,通常不需要手動調整學習率衰減。
optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999))
其中,betas是兩個超參數,分別控制了一階矩和二階矩估計的衰減率。
3. RMSprop
RMSprop(Root Mean Square Propagation)也是自適應學習率的一種方法,它主要根據歷史梯度的平方根來調整學習率。
optimizer = optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99)
其中,alpha是平滑常數,用于計算梯度的移動平均。
五、損失函數
損失函數(Loss Function)在機器學習和深度學習領域扮演著核心角色,它是評估模型預測質量的重要標準。損失函數量化了模型預測值與真實標簽之間的偏差,訓練過程本質上是通過優化算法(如梯度下降)不斷調整模型參數,以最小化這個損失值。PyTorch,作為一個強大的深度學習框架,內置了多種損失函數,以適應不同類型的機器學習任務,主要包括但不限于以下幾類:
1. 均方誤差損失(Mean Squared Error, MSE)
用于回歸問題,計算預測值與真實值之間差的平方的平均值。
import torch
import torch.nn as nn
# 假設模型輸出和真實標簽
outputs = model(inputs) # 模型的預測輸出
targets = labels # 真實標簽
# 定義損失函數
mse_loss = nn.MSELoss()
# 計算損失
loss = mse_loss(outputs, targets)
2. 交叉熵損失(Cross-Entropy)
用于分類問題,計算預測概率分布與真實標簽之間的交叉熵損失。
# 假設模型輸出為每個類別的概率分布
outputs = model(inputs)
# 確保標簽是類別索引(而非one-hot編碼),對于多分類問題
targets = torch.LongTensor(labels) # 確保標簽是整數
cross_entropy_loss = nn.CrossEntropyLoss()
loss = cross_entropy_loss(outputs, targets)
3. 二元交叉熵損失(Binary Cross-Entropy)
二元交叉熵損失通常用于二分類問題,其中每個樣本屬于兩個類別之一。
# 假設二分類問題,直接輸出概率
outputs = model(inputs) # 輸出已經是概率
targets = labels.float() # 標簽轉換為float,二分類通常為0或1
bce_loss = nn.BCELoss()
loss = bce_loss(outputs, targets)
如果兩類樣本數量嚴重不平衡,可以使用加權二元交叉熵損失(Weighted Binary Cross-Entropy Loss)來調整不同類別的權重,以確保模型在訓練時對較少出現的類別給予更多關注。
import torch
import torch.nn as nn
# 假設有兩類,其中類0的樣本較少
num_samples = [100, 1000] # 類別0有100個樣本,類別1有1000個樣本
weights = [1 / num_samples[0], 1 / num_samples[1]] # 計算類別權重
# 創建一個加權二元交叉熵損失函數
weighted_bce_loss = nn.BCEWithLogitsLoss(weight=torch.tensor(weights))
# 假設model是模型,inputs是輸入數據,labels是二進制標簽(0或1)
outputs = model(inputs)
labels = labels.float() # 將標簽轉換為浮點數,因為BCEWithLogitsLoss期望的是概率
# 計算加權損失
loss = weighted_bce_loss(outputs, labels)
4. K-L 散度損失(Kullback-Leibler Divergence Loss)
衡量兩個概率分布的差異,常用于生成模型訓練,如VAEs和GANs中的鑒別器部分。
Kullback-Leibler散度(KLDivLoss)是一種衡量兩個概率分布之間差異的方法,常用于信息論和機器學習中。在PyTorch中,nn.KLDivLoss是實現這一概念的模塊,用于比較預測概率分布(通常是softmax函數的輸出)與目標概率分布或“真實”分布。
KLDivLoss的數學定義為:
這里,(P)是真實分布,而(Q)是預測或近似分布。KLDivLoss總是非負的,并且只有當兩個分布完全相同時才為零。
import torch
import torch.nn as nn
# 假設有兩個概率分布,preds是模型預測的概率分布,targets是實際的概率分布
preds = torch.randn(3, 5).softmax(dim=1) # 預測概率分布,使用softmax轉換
targets = torch.randn(3, 5).softmax(dim=1) # 真實概率分布
# 初始化KLDivLoss實例,reduction參數定義了損失的聚合方式,可以是'mean'、'sum'或'none'
criterion = nn.KLDivLoss(reduction='batchmean') # 'batchmean'表示對批量數據求平均
# 計算KLDivLoss
loss = criterion(preds.log(), targets) # 注意:preds應該取對數,因為KLDivLoss默認期望log_softmax的輸出
print('KLDivLoss:', loss.item())
5. 三元組損失(Triplet Margin Loss)
三元組損失(Triplet Margin Loss)是深度學習中用于學習特征表示的一種損失函數,尤其在人臉識別、圖像檢索等領域廣泛應用。它的目標是學習到一個特征空間,在這個空間中,同類別的樣本之間的距離小于不同類別樣本之間的距離,且保持一定的邊際差(margin)。
三元組損失函數的數學定義為:
其中:
- a是錨點樣本的特征向量
- p是正樣本的特征向量
- n是負樣本的特征向量
- d(x, y)表示樣本x和樣本y之間的距離(通常是歐氏距離或余弦距離)
- m是預設的邊際值,用于保證正樣本和負樣本之間的差距至少為m。
這個損失函數的目的是最小化所有滿足的三元組的損失,這樣就能確保錨點樣本與正樣本的距離小于與負樣本的距離至少m個單位。
在PyTorch中,可以使用torch.nn.TripletMarginLoss來實現三元組損失。
import torch
import torch.nn as nn
# 設置隨機種子以獲得可復現的結果
torch.manual_seed(42)
# 假設特征維度
feature_dim = 128
# 生成隨機三元組數據
num_triplets = 10
anchors = torch.randn(num_triplets, feature_dim)
positives = torch.randn(num_triplets, feature_dim)
negatives = torch.randn(num_triplets, feature_dim)
# 將它們組合成形狀為 (num_triplets, 3, feature_dim) 的張量
triplets = torch.stack((anchors, positives, negatives), dim=1)
# 初始化三元組損失函數,設置邊際值m
triplet_loss = nn.TripletMarginLoss(margin=1.0)
# 計算損失
loss = triplet_loss(triplets[:, 0], triplets[:, 1], triplets[:, 2])
print('Triplet Margin Loss:', loss.item())
6. 使用損失函數進行訓練
在訓練循環中,通過計算模型輸出與真實標簽的損失,并調用反向傳播和優化器更新參數來訓練模型。
output = model(inputs)
loss = criterion(output, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
六、數據加載與預處理
在PyTorch中,數據加載與預處理是構建深度學習模型不可或缺的環節,它確保數據以高效、規范的方式被送入模型進行訓練或測試。
1. 數據集的定義
在PyTorch中,數據集的定義通常通過創建一個新的類來實現,這個類繼承自torch.utils.data.Dataset。這個自定義類需要實現以下兩個核心方法:
- __len__方法:這個方法返回數據集中的樣本數量。它告訴外界調用者數據集中有多少個樣本可以用來訓練或測試模型。
- __getitem__方法:這個方法根據給定的索引返回一個樣本數據及其對應的標簽(如果有)。它允許按需訪問數據集中的任意一個樣本,通常包括數據的加載和必要的預處理。
一個基本的數據集定義示例如下:
from torch.utils.data import Dataset
class CustomDataset(Dataset):
def __init__(self, data, labels, transform=None):
self.data = data
self.labels = labels
self.transform = transform
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
sample = {'data': self.data[idx], 'label': self.labels[idx]}
if self.transform:
sample = self.transform(sample)
return sample
其中,CustomDataset類接收3個參數:data和labels、transform,分別代表數據集中的樣本數據和對應的標簽及預處理變換。在__init__方法中,將這些參數存儲在類的屬性中,以便在__getitem__方法中訪問。__len__方法返回數據集的長度,即樣本數量。__getitem__方法通過索引index獲取對應的數據和標簽。
此外,為了增強數據的多樣性和模型的泛化能力,可以在數據集類中集成數據預處理邏輯,或者通過傳遞一個變換對象(如torchvision.transforms中的變換)來動態地對數據進行變換,如旋轉、縮放、裁剪等。
2. 數據加載器
數據加載器(DataLoader)在PyTorch中是一個非常重要的組件,它負責從數據集中高效地加載數據,并為模型訓練和驗證提供批次(batch)數據。DataLoader類位于torch.utils.data模塊中,提供了以下幾個關鍵功能:
- 批量加載:它能夠將數據集分割成多個小批量(batch),這是深度學習訓練過程中的標準做法,有助于提高訓練效率和內存利用率。
- 數據混洗:通過設置shuffle=True,可以在每個訓練epoch開始前隨機打亂數據集的順序,增加模型訓練的隨機性,有助于提高模型的泛化能力。
- 多線程加載:通過num_workers參數,可以在后臺使用多個線程并發地加載數據,減少數據I/O等待時間,進一步加速訓練過程。
- 內存節省:DataLoader通過按需加載數據(即僅在訓練過程中需要時才從磁盤加載數據到內存),避免一次性將整個數據集加載到內存中,這對于大規模數據集尤為重要。
創建一個DataLoader實例的基本用法如下:
from torch.utils.data import DataLoader
from your_dataset_module import YourCustomDataset
custom_dataset = CustomDataset(data, labels,transform=...)
# 創建DataLoader實例
data_loader = DataLoader(
dataset=custom_dataset, # 數據集實例
batch_size=32, # 每個批次的樣本數
shuffle=True, # 是否在每個epoch開始時打亂數據
num_workers=4, # 使用的子進程數,用于數據加載(0表示不使用多線程)
drop_last=False, # 如果數據集大小不能被batch_size整除,是否丟棄最后一個不完整的batch
)
# 使用data_loader在訓練循環中迭代獲取數據
for inputs, labels in data_loader:
# 在這里執行模型訓練或驗證的代碼
pass
3. 數據預處理與轉換
在PyTorch中,torchvision.transforms模塊提供了豐富的預處理和轉換功能,以下是一些常用的轉換操作:
(1) 常見的預處理與轉換操作:
- Resize:調整圖像大小到指定尺寸,例如,transforms.Resize((256, 256))會將圖像調整為256x256像素。
- CenterCrop:從圖像中心裁剪出指定大小的區域,如transforms.CenterCrop(224)。
- RandomCrop:隨機從圖像中裁剪出指定大小的區域,增加了數據多樣性,有利于模型學習。
- RandomHorizontalFlip:以一定概率水平翻轉圖像,是常用的數據增強手段之一。
- RandomRotation:隨機旋轉圖像一定角度,進一步增強數據多樣性。
- ToTensor:將PIL圖像或numpy數組轉換為PyTorch的Tensor,并將顏色通道從RGB調整為Tensorflow所期望的格式(HWC -> CHW)。
- Normalize:對圖像像素值進行標準化,通常使用特定數據集(如ImageNet)的均值和標準差,例如transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])。
(2) 組合變換
為了簡化應用多個變換的過程,可以使用Compose類將多個變換操作組合在一起,形成一個變換管道,如:
transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomCrop((224, 224)),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
# 將轉換應用于數據集
dataset = CustomDataset(data, labels, transform=transform)
七、模型的保存與加載
在PyTorch中,保存和加載模型是通過torch.save()和torch.load()函數完成的。以下是詳細的解釋和代碼示例:
1. 模型的保存
模型的參數和狀態可以保存為狀態字典(state_dict),或者保存整個模型對象,包括模型結構和參數。
- 保存狀態字典(僅參數):
# 定義模型
model = SimpleModel()
# 保存狀態字典
torch.save(model.state_dict(), 'model_state.pth')
- 保存整個模型(結構+參數):
# 保存整個模型
torch.save(model, 'model.pth')
2. 模型的加載
加載模型時,可以單獨加載狀態字典并重新構建模型,或者直接加載整個模型。
- 加載狀態字典并重建模型:
# 加載狀態字典
loaded_state_dict = torch.load('model_state.pth')
# 創建相同結構的新模型
new_model = SimpleModel()
# 將加載的狀態字典加載到新模型
new_model.load_state_dict(loaded_state_dict)
- 加載整個模型:
# 加載整個模型
loaded_model = torch.load('model.pth')
3. 跨設備加載模型
如果模型在GPU上訓練并保存,但在CPU上加載,可以使用map_location參數:
# 在CPU上加載GPU上保存的模型
loaded_model = torch.load('model.pth', map_location=torch.device('cpu'))
4. 保存與加載模型的結構和參數
在保存整個模型時,模型的結構和參數都會被保存。
# 保存整個模型(包括結構和參數)
torch.save(model, 'model.pth')
# 加載整個模型
loaded_model = torch.load('model.pth')
5. 僅保存與加載模型的結構
如果只想保存和加載模型的結構而不包含參數,可以使用 torch.save 時設置 save_model_obj=False。
# 保存模型結構
torch.save(model, 'model_structure.pth', save_model_obj=False)
# 加載模型結構
loaded_model_structure = torch.load('model_structure.pth')
6. 僅保存和加載模型的參數
如果只想保存和加載模型參數而不包含模型結構,可以使用 torch.save 時設置 save_model_obj=False。
# 保存模型參數
torch.save(model.state_dict(), 'model_parameters.pth')
# 加載模型參數
loaded_parameters = torch.load('model_parameters.pth')
model.load_state_dict(loaded_parameters)
八、學習率調整
學習率調整是深度學習模型訓練過程中的一個重要環節,它有助于模型在訓練過程中找到更好的權重。PyTorch 提供了torch.optim.lr_scheduler模塊來實現各種學習率調整策略。
1. StepLR
StepLR是一種基礎且常用的學習率調整策略,它按照預定的周期(通常是按 epoch 計算)來調整學習率。其中,step_size參數指定了衰減發生的時間間隔。比如,如果step_size=5,則學習率每過5個epoch就會調整一次。 gamma參數控制了每次調整時學習率的衰減比例。如果gamma=0.1,這意味著每到達一個step_size,學習率就會乘以0.1,也就是衰減到原來的10%。
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
# 初始化模型和優化器
model = YourModel()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
# 創建 StepLR 調度器
scheduler = StepLR(optimizer, step_size=5, gamma=0.1)
# 訓練循環
for epoch in range(num_epochs):
# 訓練過程...
# 在每個epoch結束時調用scheduler的step()方法來更新學習率
scheduler.step()
如上示例中,學習率會在每個第5、10、15...個epoch后自動減少為原來的10%,直到訓練結束。
2. MultiStepLR
MultiStepLR 與 StepLR 類似,都是基于周期(epoch)來調整學習率,但提供了更靈活的衰減時間點控制。通過 milestones 參數,可以精確指定學習率應該在哪些特定的周期數下降。gamma 參數則決定了每次在這些指定周期學習率下降的比例。
例如,如果設置milestones=[10, 20, 30]和gamma=0.1,則:
- 在訓練的第10個epoch結束后,學習率會首次乘以0.1,即減少到原來的10%。
- 接著,在第20個epoch后,學習率再次乘以0.1,相對于初始值衰減為原來的1%。
- 最后,在第30個epoch后,學習率又一次乘以0.1,最終相對于初始值衰減為原來的0.1%。
這種方式允許根據訓練過程中的性能變化或預期的學習曲線,更加精細地控制學習率的下降時機,有助于模型更好地收斂或避免過擬合。下面是使用 MultiStepLR 的簡單示例代碼:
import torch.optim as optim
from torch.optim.lr_scheduler import MultiStepLR
# 初始化模型和優化器
model = YourModel()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
# 創建 MultiStepLR 調度器
scheduler = MultiStepLR(optimizer, milestones=[10, 20, 30], gamma=0.1)
# 訓練循環
for epoch in range(num_epochs):
# 訓練過程...
# 每個epoch結束時調用scheduler的step()方法檢查是否需要調整學習率
scheduler.step()
3. ExponentialLR
ExponentialLR 是一種學習率調整策略,它使學習率按照指數函數的方式逐漸衰減。與 StepLR 和 MultiStepLR 在特定的周期突然改變學習率不同,ExponentialLR 在每個訓練步驟(或每個epoch,具體取決于調度器的更新頻率)后,都按照一個固定的比率逐漸減少學習率。這對于需要平滑降低學習率,以更細致地探索解空間或在訓練后期緩慢逼近最優解的場景非常有用。
使用 ExponentialLR 的代碼示例如下:
import torch.optim as optim
from torch.optim.lr_scheduler import ExponentialLR
# 初始化模型和優化器
model = YourModel()
optimizer = optim.SGD(model.parameters(), lr=initial_lr, momentum=0.9)
# 創建 ExponentialLR 調度器
# 其中 gamma 參數指定了學習率衰減的速率,例如 gamma=0.9 表示每經過一個調整周期,學習率變為原來的 90%
scheduler = ExponentialLR(optimizer, gamma=0.9)
# 訓練循環
for epoch in range(num_epochs):
# 訓練過程...
# 每個epoch結束時調用scheduler的step()方法更新學習率
scheduler.step()
其中,initial_lr即為設定的初始學習率,gamma=0.9 表示每次更新后,學習率都會乘以0.9,因此學習率會以指數形式逐漸減小。
4. 使用學習率調整器
在PyTorch中使用學習率調整器(Learning Rate Scheduler)是一個提高模型訓練效率和性能的有效策略。下面以StepLR為例展示如何使用學習率調整器。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import StepLR
# 假設的模型定義
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.linear = nn.Linear(10, 1)
def forward(self, x):
return self.linear(x)
# 實例化模型和優化器
model = SimpleModel()
optimizer = optim.SGD(model.parameters(), lr=0.1)
# 創建學習率調整器
scheduler = StepLR(optimizer, step_size=5, gamma=0.1) # 每5個epoch學習率減半
# 訓練循環
num_epochs = 30
for epoch in range(num_epochs):
# 假設的訓練步驟,這里簡化處理
for batch_idx, (data, target) in enumerate(train_loader):
optimizer.zero_grad()
output = model(data)
loss = F.mse_loss(output, target)
loss.backward()
optimizer.step()
# 每個epoch結束時更新學習率
scheduler.step()
print(f"Epoch [{epoch+1}/{num_epochs}], LR: {scheduler.get_last_lr()[0]}")
九、模型評估
模型評估是機器學習項目中不可或缺的一環,它旨在量化模型在未知數據上的表現,確保模型具有良好的泛化能力。以下是模型評估的關鍵步驟。
1. 設置模型為評估模式
在PyTorch中,通過調用model.eval()方法,模型會被設置為評估模式。這一步驟至關重要,因為它會影響到某些層的行為,比如關閉Dropout層和Batch Normalization層的訓練時特有的特性,確保模型的預測是確定性的,并且不會影響模型的內部狀態。
model.eval()
2. 使用驗證集或測試集進行推理
遍歷驗證集或測試集,對輸入數據進行前向傳播,得到模型的預測輸出。
model.eval()
with torch.no_grad():
for inputs, labels in dataloader:
outputs = model(inputs)
# 進行后續處理..
3. 計算性能指標
(1) 準確率(Accuracy)
準確率(Accuracy)是機器學習中最基本且直觀的性能評估指標之一,尤其適用于多分類問題。它定義為模型正確分類的樣本數占總樣本數的比例。
import torch
# 假設 outputs 和 labels 都是形狀為 (batch_size,) 的張量
# 對于多分類問題,outputs 通常包含每個類別的概率,需要獲取預測類別
_, predicted = torch.max(outputs.data, 1)
# 將預測和真實標簽轉換為相同的數據類型(例如,都轉為整數)
if isinstance(predicted, torch.Tensor):
predicted = predicted.cpu().numpy()
if isinstance(labels, torch.Tensor):
labels = labels.cpu().numpy()
# 計算并輸出準確率
accuracy = (predicted == labels).sum() / len(labels)
print(f'Accuracy: {accuracy}')
(2) 精確度(Precision)
精確度(Precision)是模型預測為正類的樣本中,真正為正類的比例。
from sklearn.metrics import precision_score
# 假設 predictions 和 true_labels 是形狀為 (n_samples,) 的數組
# predictions 是模型的預測結果,true_labels 是對應的真值標簽
# 如果是多分類問題,labels 參數是所有類別的列表
# 二分類問題
precision_binary = precision_score(true_labels, predictions)
# 多分類問題,宏平均
precision_macro = precision_score(true_labels, predictions, average='macro')
# 多分類問題,微平均
precision_micro = precision_score(true_labels, predictions, average='micro')
print(f'Binary Precision: {precision_binary}')
print(f'Macro Average Precision: {precision_macro}')
print(f'Micro Average Precision: {precision_micro}')
(3) 召回率(Recall)
召回率(Recall),也稱為靈敏度或真正率,衡量的是模型識別出的所有正類樣本中,正確識別的比例。
from sklearn.metrics import recall_score
# 假設 predictions 和 true_labels 是形狀為 (n_samples,) 的數組
# predictions 是模型的預測結果,true_labels 是對應的真值標簽
# 對于多分類問題,labels 參數是所有類別的列表
# 二分類問題
recall_binary = recall_score(true_labels, predictions)
# 多分類問題,宏平均
recall_macro = recall_score(true_labels, predictions, average='macro')
# 多分類問題,微平均
recall_micro = recall_score(true_labels, predictions, average='micro')
print(f'Binary Recall: {recall_binary}')
print(f'Macro Average Recall: {recall_macro}')
print(f'Micro Average Recall: {recall_micro}')
(4) F1分數(F1 Score)
F1分數是精確度(Precision)和召回率(Recall)的調和平均值,旨在提供一個綜合評價指標,特別是對于類別不平衡的數據集。
from sklearn.metrics import f1_score
# 假設 predictions 和 true_labels 是形狀為 (n_samples,) 的數組
# predictions 是模型的預測結果,true_labels 是對應的真值標簽
# 對于多分類問題,labels 參數是所有類別的列表
# 二分類問題
f1_binary = f1_score(true_labels, predictions)
# 多分類問題,宏平均
f1_macro = f1_score(true_labels, predictions, average='macro')
# 多分類問題,微平均
f1_micro = f1_score(true_labels, predictions, average='micro')
print(f'Binary F1 Score: {f1_binary}')
print(f'Macro Average F1 Score: {f1_macro}')
print(f'Micro Average F1 Score: {f1_micro}')
4. ROC曲線
ROC曲線(Receiver Operating Characteristic Curve)是一種評估二分類模型性能的方法,特別是在正負樣本比例不平衡或者對假陽性(False Positives, FP)和假陰性(False Negatives, FN)的代價不等同的場景下特別有用。ROC曲線通過改變決策閾值,展示了模型在不同閾值下的真正例率(True Positive Rate, TPR)與假正例率(False Positive Rate, FPR)之間的關系。