參考資料:
- 《動手學深度學習》
3.1 線性回歸
3.1.1 線性回歸的基本元素
樣本: n n n 表示樣本數(shù), x ( i ) = [ x 1 ( i ) , x 2 ( i ) , ? ? , x d ( i ) ] x^{(i)}=[x^{(i)}_1,x^{(i)}_2,\cdots,x^{(i)}_d] x(i)=[x1(i)?,x2(i)?,?,xd(i)?] 表示第 i i i 個樣本。
預(yù)測: y ^ = w T x + b \hat{y}=w^Tx+b y^?=wTx+b 表示單個樣本的預(yù)測值, y ^ = X w + b \hat{y}=Xw+b y^?=Xw+b 表示所有樣本的預(yù)測值。
損失函數(shù):
L
(
w
,
b
)
=
∑
i
=
1
n
1
2
(
y
^
(
i
)
?
y
(
i
)
)
L(w,b)=\sum\limits_{i=1}^{n}\frac12\Big(\hat{y}^{(i)}-y^{(i)}\Big)
L(w,b)=i=1∑n?21?(y^?(i)?y(i))
隨機梯度下降:在每次迭代中,我們首先隨機抽樣一個小批量
B
\mathcal{B}
B , 它是由固定數(shù)量的訓(xùn)練樣本組成的。然后按照如下方式更新參數(shù):
(
w
,
b
)
←
(
w
,
b
)
?
η
∣
B
∣
∑
i
∈
B
?
(
w
,
b
)
l
(
i
)
(
w
,
b
)
(\mathbf{w},b) \leftarrow (\mathbf{w},b) - \frac{\eta}{|\mathcal{B}|} \sum\limits_{i \in \mathcal{B}} \partial_{(\mathbf{w},b)} l^{(i)}(\mathbf{w},b)
(w,b)←(w,b)?∣B∣η?i∈B∑??(w,b)?l(i)(w,b)
其中,
η
\eta
η 為學習率,是一個超參數(shù)。
3.1.2 矢量化加速
盡可能使用效率較高的線性代數(shù)庫。
3.1.3 正態(tài)分布與平方損失
假設(shè)觀測存在噪聲
?
\epsilon
? :
y
=
w
?
x
+
b
+
?
,
y = \mathbf{w}^\top \mathbf{x} + b + \epsilon,
y=w?x+b+?,
且
?
~
N
(
0
,
σ
2
)
\epsilon \sim N(0, \sigma^2)
?~N(0,σ2)。
此時有條件概率為:
P
(
y
∣
x
)
=
1
2
π
σ
2
exp
?
(
?
1
2
σ
2
(
y
?
w
?
x
?
b
)
2
)
P(y \mid \mathbf{x}) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp\left(-\frac{1}{2 \sigma^2} (y - \mathbf{w}^\top \mathbf{x} - b)^2\right)
P(y∣x)=2πσ2?1?exp(?2σ21?(y?w?x?b)2)
于是似然函數(shù)為:
L
(
w
,
b
)
=
∏
i
=
1
n
p
(
y
(
i
)
∣
x
(
i
)
)
L(w,b) = \prod\limits_{i=1}^{n} p(y^{(i)}|\mathbf{x}^{(i)})
L(w,b)=i=1∏n?p(y(i)∣x(i))
取對數(shù)再加負號,得:
?
l
(
w
,
b
)
=
∑
i
=
1
n
(
1
2
log
?
(
2
π
σ
2
)
+
1
2
σ
2
(
y
(
i
)
?
w
?
x
(
i
)
?
b
)
2
)
.
-l(w,b) = \sum\limits_{i=1}^n \bigg(\frac{1}{2} \log(2 \pi \sigma^2) + \frac{1}{2 \sigma^2} \left(y^{(i)} - \mathbf{w}^\top \mathbf{x}^{(i)} - b\right)^2\bigg).
?l(w,b)=i=1∑n?(21?log(2πσ2)+2σ21?(y(i)?w?x(i)?b)2).
由于
π
,
σ
\pi,\sigma
π,σ 均為常數(shù),故由上式可知,對線性模型的最小化均方誤差等價于極大似然估計。
3.1.4 從線性回歸到深度網(wǎng)絡(luò)
3.2 線性回歸的從零開始實現(xiàn)
3.2.1 生成數(shù)據(jù)集
假定我們要生成一個包含 1000 個樣本的數(shù)據(jù)集,每個樣本包含從標準正態(tài)分布中采樣的 2 個特征,樣本的標簽為:
y
=
X
w
+
b
+
?
\mathbf{y}= \mathbf{X} \mathbf{w} + b + \mathbf\epsilon
y=Xw+b+?
其中,
w
=
[
2
,
?
3.4
]
?
\mathbf{w} = [2, -3.4]^\top
w=[2,?3.4]?、
b
=
4.2
b = 4.2
b=4.2 ,
?
\epsilon
? 服從均值為 0 ,標準差為 0.01 的正態(tài)分布。
def synthetic_data(w, b, num_examples): #@save
"""生成y=Xw+b+噪聲"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
# 如果沒有y.reshape,那么y將只有一個維度
return X, y.reshape((-1, 1))
3.2.2 讀取數(shù)據(jù)集
由于隨機梯度下降法要求我們每次從樣本中隨機抽取一部分樣本,所以我們可以定義 data_iter
用于樣本的抽?。?/p>
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 這些樣本是隨機讀取的,沒有特定的順序
random.shuffle(indices)
# 在一輪訓(xùn)練中要用到所有的樣本
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
# 每次參數(shù)更新只用到一小部分樣本
yield features[batch_indices], labels[batch_indices]
上面的代碼僅用于理解抽取樣本的過程,實際實現(xiàn)時可以使用內(nèi)置的迭代器。
3.2.3 初始化參數(shù)
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
3.2.4 定義模型
def linreg(X, w, b):
"""線性回歸模型"""
return torch.matmul(X, w) + b
3.2.5 定義損失函數(shù)
def squared_loss(y_hat, y):
"""均方損失"""
# 這里的y.reshape其實是沒有必要的,因為labels在前面已經(jīng)reshape過了
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
3.2.6 定義優(yōu)化算法
def sgd(params, lr, batch_size):
"""小批量隨機梯度下降"""
# 表示下一個代碼塊不需要進行梯度計算
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
# 清空梯度
param.grad.zero_()
3.2.7 訓(xùn)練
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量損失
# 因為l形狀是(batch_size,1),而不是一個標量。l中的所有元素被加到一起,
# 并以此計算關(guān)于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用參數(shù)的梯度更新參數(shù)
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
3.3 線性回歸的簡潔實現(xiàn)
3.3.1 生成數(shù)據(jù)
這部分和 3.2.1 相同。
3.3.2 讀取數(shù)據(jù)集
from torch.utils import data
我們可以直接使用 data
中的 API 來進行樣本抽樣:
def load_array(data_arrays, batch_size, is_train=True):
"""構(gòu)造一個PyTorch數(shù)據(jù)迭代器"""
# TensorDataset相當于把所有tensor打包,傳入的tensor的第0維必須相同
# *的作用是“解壓”參數(shù)列表
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
# 訪問數(shù)據(jù)
for input,label in data_iter:
print(input,label)
3.3.3 定義模型
# nn是神經(jīng)網(wǎng)絡(luò)的縮寫
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
上面的代碼中,Sequential
可以將多個層串聯(lián)在一起;Linear
實現(xiàn)了全連接層,其參數(shù) 2,1
指定了輸入的形狀和輸出的形狀。
3.3.4 初始化模型參數(shù)
# net[0]表示選中網(wǎng)絡(luò)中的第0層
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
3.3.5 定義損失函數(shù)
# 返回所有樣本損失的均值
loss = nn.MSELoss()
3.3.6 定義優(yōu)化算法
# SGD的輸入為參數(shù)和超參數(shù)
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
3.3.7 訓(xùn)練
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
# 使用優(yōu)化器對參數(shù)進行更新
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
3.4 softmax回歸
3.4.1 分類問題
一般將不同的類別用獨熱編碼表示。
3.4.2 網(wǎng)絡(luò)架構(gòu)
假設(shè)每個樣本有 4 個特征和 3 種可能的類別,則 softmax 回歸的網(wǎng)絡(luò)結(jié)構(gòu)如下圖所示:
3.4.3 全連接層的參數(shù)開銷
一般而言全連接層有 d d d 個輸入和 q q q 個輸出,則其參數(shù)開銷為 O ( d p ) O(dp) O(dp) 。
3.4.4 softmax運算
對于分類問題,我們想得到的是輸入屬于每一種類別的概率,所以我們要對輸出進行一定的處理,使之滿足概率基本公理:
y
^
=
s
o
f
t
m
a
x
(
o
)
其中
y
^
j
=
exp
?
(
o
j
)
∑
k
exp
?
(
o
k
)
\hat{\mathbf{y}} = \mathrm{softmax}(\mathbf{o})\quad \text{其中}\quad \hat{y}_j = \frac{\exp(o_j)}{\sum\limits_k \exp(o_k)}
y^?=softmax(o)其中y^?j?=k∑?exp(ok?)exp(oj?)?
顯然,
y
^
\hat{\mathbf{y}}
y^? 的每個分量恒正且和為
1
1
1 ,且 softmax 不會改變
o
\mathbf{o}
o 之間的大小順序。
3.4.5 小批量樣本的矢量化
KaTeX parse error: Expected 'EOF', got '&' at position 13: \mathbf{O} &?= \mathbf{X} \m…
3.4.6 損失函數(shù)
softmax 回歸的似然函數(shù)為:
L
(
θ
)
=
∏
i
=
1
n
P
(
y
(
i
)
∣
x
(
i
)
)
L(\theta)=\prod\limits_{i=1}^n P(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)})
L(θ)=i=1∏n?P(y(i)∣x(i))
取負對數(shù),得:
?
log
?
L
(
θ
)
=
∑
i
=
1
n
?
log
?
P
(
y
(
i
)
∣
x
(
i
)
)
=
∑
i
=
1
n
∑
j
=
1
q
?
y
j
log
?
y
^
j
\begin{align} -\log L(\theta)&=\sum\limits_{i=1}^n -\log P(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)})\notag\\ &=\sum\limits_{i=1}^n\sum\limits_{j=1}^q-y_j\log \hat{y}_j \end{align}
?logL(θ)?=i=1∑n??logP(y(i)∣x(i))=i=1∑n?j=1∑q??yj?logy^?j???
解釋以下上面的式子:因為樣本的標簽是一個長度為 q q q 的獨熱編碼,所以里面的求和實際上就是求由輸入推出其標簽的條件的概率的負對數(shù),這與 ? log ? P ( y ( i ) ∣ x ( i ) ) -\log P(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)}) ?logP(y(i)∣x(i)) 是等價的。
稱:
l
(
y
,
y
^
)
=
∑
j
=
1
q
?
y
j
log
?
y
^
j
l(\mathbf{y}, \hat{\mathbf{y}})=\sum\limits_{j=1}^q-y_j\log \hat{y}_j
l(y,y^?)=j=1∑q??yj?logy^?j?
為交叉熵損失(cross-entropy loss)。
l
(
y
,
y
^
)
=
?
∑
j
=
1
q
y
j
log
?
exp
?
(
o
j
)
∑
k
=
1
q
exp
?
(
o
k
)
=
∑
j
=
1
q
y
j
log
?
∑
k
=
1
q
exp
?
(
o
k
)
?
∑
j
=
1
q
y
j
o
j
=
log
?
∑
k
=
1
q
exp
?
(
o
k
)
?
∑
j
=
1
q
y
j
o
j
?
o
j
l
(
y
,
y
^
)
=
exp
?
(
o
j
)
∑
k
=
1
q
exp
?
(
o
k
)
?
y
j
=
s
o
f
t
m
a
x
(
o
)
j
?
y
j
\begin{aligned} l(\mathbf{y}, \hat{\mathbf{y}}) &= - \sum_{j=1}^q y_j \log \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)}\notag \\ &= \sum_{j=1}^q y_j \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j\notag\\ &= \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j\notag\\ \partial_{o_j} l(\mathbf{y}, \hat{\mathbf{y}}) &= \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)} - y_j = \mathrm{softmax}(\mathbf{o})_j - y_j\notag \end{aligned}
l(y,y^?)?oj??l(y,y^?)?=?j=1∑q?yj?log∑k=1q?exp(ok?)exp(oj?)?=j=1∑q?yj?logk=1∑q?exp(ok?)?j=1∑q?yj?oj?=logk=1∑q?exp(ok?)?j=1∑q?yj?oj?=∑k=1q?exp(ok?)exp(oj?)??yj?=softmax(o)j??yj??
可以看出,梯度是觀測值
y
y
y 和估計值
y
^
\hat{y}
y^? 之間的差異,這使梯度計算在實踐中變得容易很多。
3.5 圖像分類數(shù)據(jù)集
3.5.2 讀取小批量數(shù)據(jù)
batch_size = 256
def get_dataloader_workers():
"""使用4個進程來讀取數(shù)據(jù)"""
return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
3.6 softmax回歸的從零開始實現(xiàn)
3.6.1 初始化模型參數(shù)
輸入是 28*28 的圖像,可以看作長度為 784 的向量;輸出為屬于 10 個可能的類別的概率,故 W W W 應(yīng)為 784*10 的矩陣, b b b 為 1*10 的行向量:
num_inputs = 784
num_outputs = 10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
3.6.2 定義softmax操作
實現(xiàn)softmax由三個步驟組成:
- 對每個項求冪;
- 對每一行求和(小批量中每個樣本是一行),得到每個樣本的規(guī)范化常數(shù);
- 將每一行除以其規(guī)范化常數(shù),確保結(jié)果的和為1。
對應(yīng)的代碼為:
def softmax(X):
X_exp = torch.exp(X)
# 確保求和之后張量的維度不變
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 這里應(yīng)用了廣播機制
3.6.3 定義模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
這里的輸入為什么只是一張圖像呢?
3.6.4 定義損失函數(shù)
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y)
其中,y
為標簽列表,代表樣例的類別編號,如 [0,1,3]
。
3.6.5 分類精度
精度(accuracy)= 正確預(yù)測數(shù)量 / 總預(yù)測數(shù)量
def accuracy(y_hat, y):
"""計算預(yù)測正確的數(shù)量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
上面的代碼表示:如果 y_hat
是矩陣,那么假定第二個維度存儲每個類的預(yù)測分數(shù)。 我們使用 argmax
獲得每行中最大元素的索引來獲得預(yù)測類別。 然后我們將預(yù)測類別與真實 y
元素進行比較。 由于等式運算符 “==
” 對數(shù)據(jù)類型很敏感, 因此我們將 y_hat
的數(shù)據(jù)類型轉(zhuǎn)換為與 y
的數(shù)據(jù)類型一致。 結(jié)果是一個包含0(錯)和1(對)的張量。 最后,我們求和會得到正確預(yù)測的數(shù)量。
3.7 softmax回歸的簡潔實現(xiàn)
3.7.1 初始化模型參數(shù)
# PyTorch不會隱式地調(diào)整輸入的形狀。因此,
# 我們在線性層前定義了展平層(flatten),來調(diào)整網(wǎng)絡(luò)輸入的形狀
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
# apply會對net里的每一層執(zhí)行init_weights函數(shù)
# 所以init_weights函數(shù)里的m是用來限定只初始化Linear層參數(shù)的
net.apply(init_weights);
3.7.2 定義損失函數(shù)
CorssEntropyLoss
的輸入為
o
\mathbf{o}
o (未經(jīng)過 softmax)和標簽列表,輸出為交叉熵。也就是說,我們在計算損失的時候不需要將輸出通過 softmax 轉(zhuǎn)化為概率,這是因為 softmax 中的指數(shù)運算非常容易溢出。文章來源:http://www.zghlxwxcb.cn/news/detail-522498.html
# none表示不合并結(jié)果,即loss為一個列表,元素為每個樣本的交叉熵
# 這里之所以選擇none,是因為后面既要用到損失的總和,又要用到損失的均值
loss = nn.CrossEntropyLoss(reduction='none')
3.7.3 優(yōu)化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
3.7.4 訓(xùn)練
# 累加器類
class Accumulator:
"""在n個變量上累加"""
def __init__(self, n):
self.data = [0.0] * n
# 將參數(shù)列表逐個加到累加器里
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
def train_epoch_ch3(net, train_iter, loss, updater):
"""訓(xùn)練模型一個迭代周期(定義見第3章)"""
# 將模型設(shè)置為訓(xùn)練模式
if isinstance(net, torch.nn.Module):
net.train()
# 訓(xùn)練損失總和、訓(xùn)練準確度總和、樣本數(shù)
metric = Accumulator(3)
for X, y in train_iter:
# 計算梯度并更新參數(shù)
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch內(nèi)置的優(yōu)化器和損失函數(shù)
updater.zero_grad()
l.mean().backward()
updater.step()
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回訓(xùn)練損失和訓(xùn)練精度
return metric[0] / metric[2], metric[1] / metric[2]
def evaluate_accuracy(net, data_iter): #@save
"""計算在指定數(shù)據(jù)集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 將模型設(shè)置為評估模式
metric = Accumulator(2) # 正確預(yù)測數(shù)、預(yù)測總數(shù)
with torch.no_grad():
for X, y in data_iter:
# 這里的accuracy出自3.6.5
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
"""訓(xùn)練模型(定義見第3章)"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
# 訓(xùn)練一輪
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
# 在測試集上測試精度
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
# 這條代碼的意思是:如果train_loss<0.5則繼續(xù)執(zhí)行,否則報錯,報錯內(nèi)容為"train_loss"
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
3.7.5 預(yù)測
使用 y_hat.argmax(axis=1)
即可。文章來源地址http://www.zghlxwxcb.cn/news/detail-522498.html
到了這里,關(guān)于《動手學深度學習》——線性神經(jīng)網(wǎng)絡(luò)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!