PyTorch學習筆記(二)——torch.nn解析
PyTorch提供了方便漂亮的類和模組,來幫助我們建立和訓練神經網路,例如 torch.nn, torch.optim 等。
為了更好地理解這些模組的功能和原理,我們在手動搭建的神經網路上,逐步新增這些模組,以顯示每部分模組的功能,以及每部分是如何讓程式碼更加靈活簡潔的。
1、手動搭建神經網路
使用MNIST資料集,該資料集共有50000個圖片,每一圖片大小為2828,儲存在長度為2828=784的扁平行。
#定義權重和偏執值,需要requires_grad=True,以便自動計算梯度,完成反饋
weights = torch.randn(784, 10,requires_grad=True)
bias = torch.zeros(10, requires_grad=True)
#定義啟用函式
def log_softmax(x):
return x - x.exp().sum(-1).log().unsqueeze(-1)
#定義神經網路運算過程,其中@表示點乘
def model(xb):
return log_softmax(xb @ weights + bias)
#定義代價函式
def nll(input, target):
return -input[range(target.shape[0]), target].mean()
loss_func = nll
#驗證結果的準確性
def accuracy(out, yb):
preds = torch.argmax(out, dim=1)
return (preds == yb).float().mean()
#獲取一批資料,驗證預測的結果:
bs = 64 # batch size
xb = x_train[0:bs] # a mini-batch from x
yb = y_train[0:bs]
preds = model(xb) # predictions
print(preds[0], preds.shape)
print(loss_func(preds, yb))
print(accuracy(preds, yb))
輸出:
tensor([-1.7022, -3.0342, -2.4138, -2.6452, -2.7764, -2.0892, -2.2945, -2.5480, -2.3732, -1.8915], grad_fn=<SelectBackward>) torch.Size([64, 10])
tensor(2.3783, grad_fn=<NegBackward>)
tensor(0.0938)
#因為現在的權重和偏置值是隨即取得的,所以預測結果並不好
#定義更新權值的訓練函式
def fit():
lr = 0.5
epochs = 2
for epoch in range(epochs):
for i in range((n - 1) // bs + 1):
#set_trace()
start_i = i * bs
end_i = start_i + bs
xb = x_train[start_i:end_i]
yb = y_train[start_i:end_i]
pred = model(xb)
loss = loss_func(pred, yb)
loss.backward()
with torch.no_grad():
weights -= weights.grad * lr
bias -= bias.grad * lr
#每次迭代之後,需要將梯度還原為零,否則loss.backward() 將梯度增加到已經存在的值上,而不是替代它
weights.grad.zero_()
bias.grad.zero_()
fit()
print(loss_func(model(xb), yb), accuracy(model(xb), yb))
輸出:
tensor(0.0806, grad_fn=<NegBackward>) tensor(1.)
#準確度明顯提高
現在已經手動定義了一個小型的神經網路,每次迭代,將會進行以下幾件事情:
- 選擇一批資料(mini-batch)
- 使用模型進行預測
- 計算損失 loss.backward()
- 更新模型的梯度,即權重和偏置
2、使用 torch.nn.functional重構程式碼
我們現在來重構程式碼,程式碼的功能和前邊的一樣,我們只是利用PyTorch 的 nn 類來使得程式碼更簡潔和靈活。
torch.nn.functional 中的函式可以替代我們手工編寫的啟用函式和損失函式來縮短程式碼。
該模組包含 torch.nn 庫中所有的函式(而庫的其他部分還包含類)。
除了各種損失函式和啟用函式,在還模組中你還可以發現許多用於建立神經網路的方便的函式,如池化函式等。
如果使用了負對數似然損失函式和 log softnax 啟用函式,那麼 Pytorch 提供的torch.nn.functional.cross_entropy 結合了兩者。
所以我們甚至可以從我們的模型中移除啟用函式。
#傳統寫法:
#定義啟用函式
def log_softmax(x):
return x - x.exp().sum(-1).log().unsqueeze(-1)
#定義神經網路運算過程,其中@表示點乘
def model(xb):
return log_softmax(xb @ weights + bias)
#定義代價函式
def nll(input, target):
return -input[range(target.shape[0]), target].mean()
loss_func = nll
#優化寫法:
import torch.nn.functional as F
loss_func = F.cross_entropy
def model(xb):
return xb @ weights + bias
注意,在 model 函式中我們不再需要呼叫 log_softmax。讓我們確認一下,損失和精確度與前邊計算的一樣:
print(loss_func(model(xb), yb), accuracy(model(xb), yb))
tensor(0.0806, grad_fn=<NllLossBackward>) tensor(1.)
3、使用 nn.Module 重構程式碼
可以通過繼承 nn.Module(它本身是一個類並且能夠跟蹤狀態)建立神經網路。
我們想要建立一個包含權重、偏置和前向傳播的方法的類。
nn.Module 擁有許多我們將會使用的屬性和方法(例如:.parameters() 和.zero_grad())
#之前寫法:
#定義權重和偏執值,需要requires_grad=True,以便自動計算梯度,完成反饋
weights = torch.randn(784, 10,requires_grad=True)
bias = torch.zeros(10, requires_grad=True)
#定義啟用函式
def log_softmax(x):
return x - x.exp().sum(-1).log().unsqueeze(-1)
#定義神經網路運算過程,其中@表示點乘
def model(xb):
return log_softmax(xb @ weights + bias)
#定義代價函式
def nll(input, target):
return -input[range(target.shape[0]), target].mean()
loss_func = nll
優化寫法:
from torch import nn
import torch.nn.functional as F
loss_func = F.cross_entropy
class Mnist_Logistic(nn.Module):
def __init__(self):
super().__init__()
self.weights = nn.Parameter(torch.randn(784, 10))
self.bias = nn.Parameter(torch.zeros(10))
def forward(self, xb):
return xb @ self.weights + self.bias
#之前需要按名字更新每個引數的值,並且手動將每個引數的梯度歸零:
loss.backward()
with torch.no_grad():
weights -= weights.grad * lr
bias -= bias.grad * lr
#每次迭代之後,需要將梯度還原為零,否則loss.backward() 將梯度增加到已經存在的值上,而不是替代它
weights.grad.zero_()
bias.grad.zero_()
#可以利用 model.paremeters() 和 model.zero_grad() 使得這些步驟更簡潔
model = Mnist_Logistic()
loss.backward()
with torch.no_grad():
for p in model.parameters(): p -= p.grad * lr
model.zero_grad()
4、使用 nn.Linear 重構程式碼
使用PyTorch 的 nn.Linear 類建立一個線性層,以替代手動定義和初始化 self.weights 和 self.bias、計算 xb @ self.weights + self.bias 等工作
#之前寫法:
class Mnist_Logistic(nn.Module):
def __init__(self):
super().__init__()
self.weights = nn.Parameter(torch.randn(784, 10))
self.bias = nn.Parameter(torch.zeros(10))
def forward(self, xb):
return xb @ self.weights + self.bias
#優化寫法:
class Mnist_Logistic(nn.Module):
def __init__(self):
super().__init__()
self.lin = nn.Linear(784, 10)
def forward(self, xb):
return self.lin(xb)
5、使用 optim 重構程式碼
PyTorch還有一個包含各種優化演算法的包 torch.optim 。
我們可以使用優化器中的 step 方法來執行訓練更新引數的步驟,而不是手動更新引數。
#之前寫法:
for epoch in range(epochs):
for i in range((n - 1) // bs + 1):
start_i = i * bs
end_i = start_i + bs
xb = x_train[start_i:end_i]
yb = y_train[start_i:end_i]
pred = model(xb)
loss = loss_func(pred, yb)
loss.backward()
with torch.no_grad():
for p in model.parameters():
p -= p.grad * lr
model.zero_grad()
優化寫法:
for epoch in range(epochs):
for i in range((n - 1) // bs + 1):
start_i = i * bs
end_i = start_i + bs
xb = x_train[start_i:end_i]
yb = y_train[start_i:end_i]
pred = model(xb)
loss = loss_func(pred, yb)
loss.backward()
opt.step()
opt.zero_grad()
參考博文https://blog.csdn.net/Spring_24/article/details/100128412