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