1.線性回歸的簡(jiǎn)潔實(shí)現(xiàn)
實(shí)踐中,我們通常可以用比上分段更簡(jiǎn)潔的代碼來(lái)實(shí)現(xiàn)同樣的模型屁倔。在本節(jié)中,我們將介紹如何使用PyTorch更方便地實(shí)現(xiàn)線性回歸的訓(xùn)練暮胧。
1.1生成數(shù)據(jù)集
我們生成與上一級(jí)中相同的數(shù)據(jù)集锐借。其中features
是訓(xùn)練數(shù)據(jù)特征问麸,labels
是標(biāo)簽。
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
1.2讀取數(shù)據(jù)
PyTorch提供了data
包來(lái)讀取數(shù)據(jù)钞翔。由于data
經(jīng)常使用變量名严卖,我們將導(dǎo)入的data
模塊用代替Data
。在每一次重復(fù)中布轿,我們將隨機(jī)讀取包含10個(gè)數(shù)據(jù)樣本的小批量哮笆。
import torch.utils.data as Data
batch_size = 10
# 將訓(xùn)練數(shù)據(jù)的特征和標(biāo)簽組合
dataset = Data.TensorDataset(features, labels)
# 隨機(jī)讀取小批量
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
讓我們讀取并打印第一個(gè)小批量數(shù)據(jù)樣本。
for X, y in data_iter:
print(X, y)
break
輸出:
tensor([[-2.7723, -0.6627],
[-1.1058, 0.7688],
[ 0.4901, -1.2260],
[-0.7227, -0.2664],
[-0.3390, 0.1162],
[ 1.6705, -2.7930],
[ 0.2576, -0.2928],
[ 2.0475, -2.7440],
[ 1.0685, 1.1920],
[ 1.0996, 0.5106]])
tensor([ 0.9066, -0.6247, 9.3383, 3.6537, 3.1283, 17.0213, 5.6953, 17.6279,
2.2809, 4.6661])
1.3定義模型
首先汰扭,引入torch.nn
模塊稠肘。實(shí)際上,“ nn”是神經(jīng)網(wǎng)絡(luò)(神經(jīng)網(wǎng)絡(luò))的縮寫萝毛。顧名思義项阴,該模塊定義了串聯(lián)神經(jīng)網(wǎng)絡(luò)的層。nn
就是利用autograd
來(lái)定義模型珊泳。nn
的核心數(shù)據(jù)結(jié)構(gòu)是Module
鲁冯,它是一個(gè)抽象概念,既可以表示神經(jīng)網(wǎng)絡(luò)中的某個(gè)層(layer)色查,也可以表示一個(gè)包含很多層的神經(jīng)網(wǎng)絡(luò)薯演。在實(shí)際使用中,最常見(jiàn)的做法是繼承nn.Module
秧了,編寫自己的網(wǎng)絡(luò)/層跨扮。一個(gè)nn.Module
實(shí)例應(yīng)該包含一些層以及返回輸出的前向傳播(forward)方法。下面先來(lái)看看如何用nn.Module
實(shí)現(xiàn)一個(gè)線性回歸模型验毡。
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1)
# forward 定義前向傳播
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
print(net) # 使用print可以打印出網(wǎng)絡(luò)的結(jié)構(gòu)
輸出:
LinearNet(
(linear): Linear(in_features=2, out_features=1, bias=True)
)
我們也可以用nn.Sequential
來(lái)更加網(wǎng)求方便地搭建網(wǎng)絡(luò)衡创,Sequential
是一個(gè)有序的容器,層網(wǎng)絡(luò)將按照在傳入Sequential
的順序依次被添加到計(jì)算圖產(chǎn)品中晶通。
# 寫法一
net = nn.Sequential(
nn.Linear(num_inputs, 1)
# 此處還可以傳入其他層
)
# 寫法二
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......
# 寫法三
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
('linear', nn.Linear(num_inputs, 1))
# ......
]))
print(net)
print(net[0])
輸出:
Sequential(
(linear): Linear(in_features=2, out_features=1, bias=True)
)
Linear(in_features=2, out_features=1, bias=True)
可以通過(guò)net.parameters()
來(lái)查看模型所有的可學(xué)習(xí)參數(shù)璃氢,此函數(shù)將返回一個(gè)生成器。
for param in net.parameters():
print(param)
輸出:
Parameter containing:
tensor([[-0.0277, 0.2771]], requires_grad=True)
Parameter containing:
tensor([0.3395], requires_grad=True)
回顧圖1.1中線性回歸在神經(jīng)網(wǎng)絡(luò)圖中的表示狮辽。作為一個(gè)單層神經(jīng)網(wǎng)絡(luò)一也,線性回歸輸出層中的神經(jīng)元和輸入層中各個(gè)輸入完全連接。因此喉脖,線性回歸的輸出層又叫全連接層椰苟。
注意:僅
torch.nn
支持輸入一個(gè)批處理的樣本不支持樣本樣本輸入,如果只有單個(gè)樣本树叽,可使用input.unsqueeze(0)
來(lái)添加一維舆蝴。
1..4初始化模型參數(shù)
在使用net
前,我們需要初始化模型參數(shù),如線性回歸模型中的權(quán)重和偏差洁仗。PyTorch在init
模塊中提供了多種參數(shù)初始化方法层皱。這里的init
是initializer
的縮寫形式。我們init.normal_
將權(quán)重參數(shù)每個(gè)元素初始化為隨機(jī)采樣于均值0京痢,標(biāo)準(zhǔn)差為0.01的正態(tài)分布奶甘。偏差會(huì)初始化為零。
from torch.nn import init
init.normal_(net[0].weight, mean=0, std=0.01)
init.constant_(net[0].bias, val=0) # 也可以直接修改bias的data: net[0].bias.data.fill_(0)
注:如果這里的
net
是用3.3.3節(jié)一開(kāi)始的代碼自定義的祭椰,那么上面代碼會(huì)報(bào)錯(cuò)臭家,net[0].weight
應(yīng)替換net.linear.weight
,bias
亦然方淤。因?yàn)?code>net[0]這樣根據(jù)下標(biāo)訪問(wèn)子模塊的寫法只有當(dāng)net
是一個(gè)ModuleList
或者Sequential
實(shí)例時(shí)才可以钉赁,詳見(jiàn)4.1節(jié)。
1.5定義損失函數(shù)
PyTorch在nn
模塊中提供了各種損失函數(shù)携茂,這些損失函數(shù)可稱為是一種特殊的層你踩,PyTorch也將這些損失函數(shù)實(shí)現(xiàn)為nn.Module
的子類。函數(shù)讳苦。
loss = nn.MSELoss()
1.6 定義優(yōu)化算法
同樣带膜,我們也無(wú)須自己實(shí)現(xiàn)小批量隨機(jī)梯度下降算法。torch.optim
模塊提供了很多常用的優(yōu)化算法比如SGD鸳谜,亞當(dāng)和RMSProp等膝藕。我們下面創(chuàng)建33一個(gè)用于優(yōu)化net
所有參數(shù)的優(yōu)化器實(shí)例,并指定學(xué)習(xí)率為0.03的小批量隨機(jī)梯度下降(SGD)為優(yōu)化算法咐扭。
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
輸出:
SGD (
Parameter Group 0
dampening: 0
lr: 0.03
momentum: 0
nesterov: False
weight_decay: 0
)
我們還可以為不同子網(wǎng)絡(luò)設(shè)置不同的學(xué)習(xí)率芭挽,這在finetune時(shí)經(jīng)常用到。
optimizer =optim.SGD([
# 如果對(duì)某個(gè)參數(shù)不指定學(xué)習(xí)率蝗肪,就使用最外層的默認(rèn)學(xué)習(xí)率
{'params': net.subnet1.parameters()}, # lr=0.03
{'params': net.subnet2.parameters(), 'lr': 0.01}
], lr=0.03)
有時(shí)候我們不想讓學(xué)習(xí)率固定成一個(gè)常數(shù)袜爪,那如何調(diào)整學(xué)習(xí)率呢?主要有兩種做法薛闪。一種是修改optimizer.param_groups
中對(duì)應(yīng)的學(xué)習(xí)率辛馆,另一種是更簡(jiǎn)單也是推薦的做法-新建優(yōu)化器,由于optimizer極其輕量級(jí)豁延,體積很小怀各,故而可以構(gòu)建新的optimizer。震蕩等情況术浪。
# 調(diào)整學(xué)習(xí)率
for param_group in optimizer.param_groups:
param_group['lr'] *= 0.1 # 學(xué)習(xí)率為之前的0.1倍
1.7訓(xùn)練模型
在使用Gluon訓(xùn)練模型時(shí),我們通過(guò)調(diào)用optim
實(shí)例的step
函數(shù)來(lái)轉(zhuǎn)換模型參數(shù)寿酌。按照小批量隨機(jī)遞減的定義胰苏,我們?cè)?code>step函數(shù)中指定尺寸大小,從而對(duì)批量中樣本梯度求平均醇疼。
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # 梯度清零硕并,等價(jià)于net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
輸出:
epoch 1, loss: 0.000457
epoch 2, loss: 0.000081
epoch 3, loss: 0.000198
下面我們分別比較比較到到模型參數(shù)和真實(shí)的模型參數(shù)法焰。我們從net
獲得需要的層,并訪問(wèn)其權(quán)重(weight
)和偏差(bias
)倔毙。學(xué)到的參數(shù)和真實(shí)的參數(shù)很接近埃仪。
dense = net[0]
print(true_w, dense.weight)
print(true_b, dense.bias)
輸出:
[2, -3.4] tensor([[ 1.9999, -3.4005]])
4.2 tensor([4.2011])
小結(jié)
- 使用PyTorch可以更簡(jiǎn)潔地實(shí)現(xiàn)模型。
-
torch.utils.data
模塊提供了有關(guān)數(shù)據(jù)處理的工具陕赃,torch.nn
模塊定義了神經(jīng)網(wǎng)絡(luò)的層卵蛉,torch.nn.init
模塊定義了各種初始化方法,torch.optim
模塊提供了很多常用的優(yōu)化算法么库。
2 softmax回歸的簡(jiǎn)潔實(shí)現(xiàn)
(線性回歸的簡(jiǎn)潔實(shí)現(xiàn))中已經(jīng)了解了使用Pytorch實(shí)現(xiàn)模型的便利傻丝。下面,讓我們?cè)俅问褂肞ytorch來(lái)實(shí)現(xiàn)一個(gè)softmax回歸模型诉儒。首先引入所需的包或模塊葡缰。
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
2.1獲取和讀取數(shù)據(jù)
我們?nèi)匀皇褂肍ashion-MNIST數(shù)據(jù)集和上段中設(shè)置的批量大小。
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
2.2定義和初始化模型
softmax回歸的輸出層是一個(gè)全連接層忱反,所以我們用一個(gè)線性模塊就可以了泛释。因?yàn)榍懊嫖覀償?shù)據(jù)返回的每個(gè)批量樣本x
的形狀為(batch_size,1温算, 28怜校,28),所以我們要先用view()
將x
的形狀轉(zhuǎn)換成(batch_size米者,784)才送入全連接層韭畸。
num_inputs = 784
num_outputs = 10
class LinearNet(nn.Module):
def __init__(self, num_inputs, num_outputs):
super(LinearNet, self).__init__()
self.linear = nn.Linear(num_inputs, num_outputs)
def forward(self, x): # x shape: (batch, 1, 28, 28)
y = self.linear(x.view(x.shape[0], -1))
return y
net = LinearNet(num_inputs, num_outputs)
我們將對(duì)x
的形狀轉(zhuǎn)換的這個(gè)功能自定義一個(gè)FlattenLayer
并記錄在d2lzh_pytorch
中方便后面使用。
# 本函數(shù)已保存在d2lzh_pytorch包中方便以后使用
class FlattenLayer(nn.Module):
def __init__(self):
super(FlattenLayer, self).__init__()
def forward(self, x): # x shape: (batch, *, *, ...)
return x.view(x.shape[0], -1)
這樣我們就可以更方便地定義我們的模型:
from collections import OrderedDict
net = nn.Sequential(
# FlattenLayer(),
# nn.Linear(num_inputs, num_outputs)
OrderedDict([
('flatten', FlattenLayer()),
('linear', nn.Linear(num_inputs, num_outputs))
])
)
然后蔓搞,我們使用均值0胰丁,標(biāo)準(zhǔn)差為0.01的正態(tài)分布隨機(jī)初始化模型的權(quán)重參數(shù)。
init.normal_(net.linear.weight, mean=0, std=0.01)
init.constant_(net.linear.bias, val=0)
2.3 softmax和交叉熵?fù)p失函數(shù)
因此喂分,PyTorch提供了一個(gè)包括softmax計(jì)算和交叉熵計(jì)算的函數(shù)锦庸。它的數(shù)值穩(wěn)定性更好。
loss = nn.CrossEntropyLoss()
2.4定義優(yōu)化算法
我們使用學(xué)習(xí)逐步0.1的小批量隨機(jī)梯度下降作為優(yōu)化算法蒲祈。
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
2.5訓(xùn)練模型
接下來(lái)甘萧,我們使用上一級(jí)中定義的訓(xùn)練函數(shù)來(lái)訓(xùn)練模型。
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
輸出:
epoch 1, loss 0.0031, train acc 0.745, test acc 0.790
epoch 2, loss 0.0022, train acc 0.812, test acc 0.807
epoch 3, loss 0.0021, train acc 0.825, test acc 0.806
epoch 4, loss 0.0020, train acc 0.832, test acc 0.810
epoch 5, loss 0.0019, train acc 0.838, test acc 0.823
小結(jié)
- PyTorch提供的函數(shù)往往具有更好的數(shù)值穩(wěn)定性梆掸。
- 可以使用PyTorch更簡(jiǎn)潔地實(shí)現(xiàn)softmax回歸扬卷。
3 多層感知機(jī)的簡(jiǎn)潔實(shí)現(xiàn)
下面我們使用PyTorch來(lái)實(shí)現(xiàn)上分段中的多層感知機(jī)。首先引入所需的包或模塊酸钦。
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
3.1定義模型
和softmax回歸唯一的不同在于怪得,我們多加了一個(gè)全連接層作為隱藏層。它的隱藏單元個(gè)數(shù)為256,并使用ReLU函數(shù)作為激活函數(shù)徒恋。
num_inputs, num_outputs, num_hiddens = 784, 10, 256
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.Linear(num_hiddens, num_outputs),
)
for params in net.parameters():
init.normal_(params, mean=0, std=0.01)
3.2讀取數(shù)據(jù)并訓(xùn)練模型
我們使用與3.7節(jié)中訓(xùn)練softmax回歸幾乎相同的步驟來(lái)讀取數(shù)據(jù)并訓(xùn)練模型蚕断。
注:由于這里使用的是PyTorch的SGD而不是d2lzh_pytorch里面的sgd,所以就不存在3.9節(jié)那樣學(xué)習(xí)率看起來(lái)很大的問(wèn)題了入挣。
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
輸出:
epoch 1, loss 0.0030, train acc 0.712, test acc 0.744
epoch 2, loss 0.0019, train acc 0.823, test acc 0.821
epoch 3, loss 0.0017, train acc 0.844, test acc 0.842
epoch 4, loss 0.0015, train acc 0.856, test acc 0.842
epoch 5, loss 0.0014, train acc 0.864, test acc 0.818
小結(jié)
- 通過(guò)PyTorch可以更簡(jiǎn)潔地實(shí)現(xiàn)多層感知機(jī)亿乳。