這章感覺沒什么需要特別記住的東西,感覺忘了回來翻一翻代碼就好,
3.1 線性回歸
3.1.1 線性回歸的基本元素
1. 線性模型
用符號標識的矩陣 \(\boldsymbol{X} \in \mathbb{R}^{n\times d}\) 可以很方便地參考整個資料集中的 \(n\) 個樣本,其中 \(\boldsymbol{X}\) 地每一行是一個樣本,每一列是一種特征,
對于特征集合 \(\boldsymbol{X}\),預測值 \(\hat{\boldsymbol{y}} \in \mathbb{R}^n\) 可以通過矩陣-向量乘法表示為
\[\hat{\boldsymbol{y}} = \boldsymbol{Xw} + b \]然后求和的程序使用廣播機制,另外,即使確信特征與標簽的潛在關系是線性的,也會加入一個噪聲項以考慮觀測誤差帶來的影響,
2. 損失函式
這里采用的損失函式為平方誤差函式,當樣本 \(i\) 的預測值為 \(\hat{y}^{(i)}\),其相應的真實標簽為 \(y^{(i)}\) 時,平方誤差可以定義為:
\[l^{(i)}(\boldsymbol{w}, b) = \frac{1}{2} (\hat{y}^{(i)}-y^{(i)})^2 \]這里的系數 \(\frac{1}{2}\) 的目的是為了求導后常數為 \(1\),
因此,整個資料集上的損失均值為:
\[L(\boldsymbol{w}, b) = \frac{1}{n} \sum_{i=1}^n l^{(i)}(\boldsymbol{w}, b) = \frac{1}{n} \sum_{i=1}^n \frac{1}{2} (\boldsymbol{w}^T \boldsymbol{x}^{(i)} + b - y^{(i)})^2 \]最后在訓練模型時要找一組引數 \((\boldsymbol{w}^*, b^*)\) ,最小化總損失,即如:
\[\boldsymbol{w}^*, b^* = {\arg \min}_{\boldsymbol{w}, b} L(\boldsymbol{w}, b) \]3. 決議解
這里原書寫的不是很清楚,具體合并大概是
\[\boldsymbol{X} \leftarrow \begin{bmatrix} x_{11} & x_{12} & \cdots & x_{1d} & 1\\ x_{21} & x_{22} & \cdots & x_{2d} & 1\\ \vdots & \vdots & \ddots & \vdots & \vdots \\ x_{n1} & x_{n2} & \cdots & x_{nd} & 1 \end{bmatrix} , ~~ \boldsymbol{w} \leftarrow \begin{bmatrix} w_1 \\ w_2 \\ \vdots \\ w_d \\ b \end{bmatrix} \]是這樣合并的,然后問題就轉化為最小化 \(||\boldsymbol{y} - \boldsymbol{Xw}||^2\),然后令損失關于 \(\boldsymbol{w}\) 的導數設為 \(0\),那么有決議解:
\[\boldsymbol{w}^* = (\boldsymbol{X}^T\boldsymbol{X})^{-1} \boldsymbol{X}^T\boldsymbol{y} \]當然了,一般的深度學習問題也沒有決議解能給你求出來(233),
4. 隨機梯度下降
在每次需要計算更新的時候隨機抽取一小批樣本,這種變體叫做小批量隨機梯度下降(minibatch stochastic gradient descent),
大致可以寫作如下公式:
\[(\boldsymbol{w},b) \leftarrow (\boldsymbol{w}, b) - \frac{\eta}{|B|} \sum_{i \in B} \partial_{(\boldsymbol{w}, b)} l^{(i)} (\boldsymbol{w}, b) \]其中,\(|B|\) 是 batch size,\(\eta\) 是學習率,
5. 用模型進行預測
由于在統計學中,推斷(inference)更多地表示基于資料集估計引數,所以請盡量將給定特征的情況下估計目標的程序稱為預測,
3.1.2 向量化加速
先定義一下 Timer 類,這個類可以丟進小本本里,
class Timer:
def __init__(self):
self.times = []
self.start()
def start(self):
self.tik = time.time()
def stop(self):
self.times.append(time.time() - self.tik)
return self.times[-1]
def avg(self):
return sum(self.times) / len(self.times)
def sum(self):
return sum(self.times)
def cumsum(self):
return np.array(self.times).cumsum().tolist()
然后用下面的 python 回圈加法和 tensor 的向量加法比較,可以發現,盡量使用 PyTorch 向量化后的 tensor 進行運算,不得不感慨一下 python 是真的慢啊,即使是 tensor 加法也還要比 C++ 慢(tensor 的基礎運算應該就是拿 C++ 實作的),這也側面證明向量加法在 CPU 環境下應該沒有涉及到應用多核,
n = 10000
a = torch.ones(n)
b = torch.ones(n)
c = torch.zeros(n)
timer = Timer()
for i in range(n):
c[i] = a[i] + b[i]
f'{timer.stop():.5f} sec'
# '0.09908 sec'
timer.start()
d = a + b
f'{timer.stop():.5f} sec'
'0.00035 sec'
3.1.3 正態分布與平方損失
正態分布概率密度函式如下:
\[p(x) = \frac{1}{\sqrt{2\pi \sigma^2}} \exp\left(-\frac{1}{2\sigma^2}(x-\mu)^2\right) \]然后本書假設觀測中包含的噪聲服從正態分布,噪聲正態分布如:\(y = \boldsymbol{w}^T\boldsymbol{x} + b + \epsilon\),其中,\(\epsilon \sim N(0, \sigma^2)\),
通過給定 \(\boldsymbol{x}\) 觀測到特定的 \(y\) 的似然為:
\[P(y|\boldsymbol{x}) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp \left(- \frac{1}{2\sigma^2}(y-\boldsymbol{w}^T\boldsymbol{x}-b)^2\right) \]那么引數 \(\boldsymbol{w}\) 和 b 的最優值是使整個資料集的似然最大的值:
\[p(\boldsymbol{y} | \boldsymbol{X}) = \prod_{i=1}^n p(y^{(i)} | \boldsymbol{x}^{(i)}) \]等價于最小化負對數似然:
\[-\log P(\boldsymbol{y} |\boldsymbol{X}) = \sum_{i=1}^n \frac{1}{2} \log (2\pi \sigma^2) + \frac{1}{2\sigma^2}(y^{(i)} - \boldsymbol{w}^T\boldsymbol{x}^{(i)} - b)^2 \]要讓上式最小,即讓最后一項最小,因此在有高斯噪聲的假設下,最小化均方誤差等價于對線性模型的極大似然估計,
3.2 線性回歸的從零開始實作
這節就不詳細寫了,有一些代碼里感覺有意思的點寫在這里好了,
- 假如說有一個 numpy 陣列或者 PyTorch 的向量 \(\boldsymbol{x}\),那么可以用
y = x[torch.tensr([1, 2, 5])]來獲得一個只包含 \(x_1, x_2, x_5\) 的 \(\boldsymbol{y}\),
優化演算法代碼如下:
def sgd(params, lr, batch_size):
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
也就是說本質上是讓它在不計算梯度的情況下,更新 param,然后讓它的梯度更新成零,
訓練用的代碼為:
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)
l.sum().backward()
sgd([w, b], lr, batch_size)
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
這里有一大堆沒定義的東西在書中前文提到了,但是我這里僅提供訓練代碼僅供示意,
linreg(X, w, b)表示用 \(\boldsymbol{X}, \boldsymbol{w}, b\) 計算 \(\hat{\boldsymbol{y}}\)squared_loss(y_hat, y)為均方損失features和labels是資料,data_iter是一個生成器,負責迭代 batch_size 大小的資料,
練習中問了個很有趣的問題:
如果將權重初始化為零,會發生什么?演算法仍然有效嗎?
參考文章 談談神經網路權重為什么不能初始化為0,來回答這一問題,
-
如同書中只有一層線性層的時候:
由于
\[\hat{\boldsymbol{y}} = \boldsymbol{Xw} + b \\ l^{(i)}(\boldsymbol{w}, b) = \frac{1}{2} (\hat{y}^{(i)}-y^{(i)})^2 \\ L = \sum_{i=1}^n l^{(i)}(\boldsymbol{w}, b) \]代入,有
\[L = \sum_{i=1}^n l^{(i)}(\boldsymbol{w}, b) = \sum_{i=1}^n \frac{1}{2} (\hat{y}^{(i)}-y^{(i)})^2 = \sum_{i=1}^n \frac{1}{2} (\boldsymbol{x}^{(i)^T} \boldsymbol{w} + b -y^{(i)})^2 \]求導數,有
\[\frac{\partial L}{\partial w_0} = \sum_{i=1}^n x_0^{(i)} (x_0^{(i)}w_0 + x_1^{(i)}w_1 + b - y^{(i)}) \\ \frac{\partial L}{\partial w_1} = \sum_{i=1}^n x_1^{(i)} (x_0^{(i)}w_0 + x_1^{(i)}w_1 + b - y^{(i)}) \\ \]那么在第一次求導的時候,得
\[\frac{\partial L}{\partial w_0} = \sum_{i=1}^n x_0^{(i)} (x_0^{(i)}w_0 + x_1^{(i)}w_1 + b - y^{(i)}) = \sum_{i=1}^n x_0^{(i)} (b-y^{(i)}) \\ \frac{\partial L}{\partial w_1} = \sum_{i=1}^n x_1^{(i)} (x_0^{(i)}w_0 + x_1^{(i)}w_1 + b - y^{(i)}) = \sum_{i=1}^n x_1^{(i)} (b-y^{(i)}) \\ \]那么,導數不為 \(0\),就確實對于演算法沒有影響,
-
但是如果不是只有一層線性層的話:
不妨假設此時有兩層線性層,第一層 \(\boldsymbol{W^{(0)} \in \mathbb{R}^{2 \times 2}}\),第二層 \(\boldsymbol{W}^{(1)} \in \mathbb{R}^2\),過第一個線性層后輸出的值就與輸入 \(\boldsymbol{X}\) 無關了,那么再過第二個線性層后得到的結果就僅與第一個線性層的偏置以及第二個線性層有關了,那么,第二個線性層的權重關于第一個線性層權重的 Jacobi 矩陣是什么樣子的呢?由于 \(y_{i} = (\sum_{j=1}^n w_{ij} x_j) + b\),因此 \(\frac{\mathrm{d}y_i}{\mathrm{d}x_j} = w_{ij} = 0\),所以該矩陣是 \(0\) 矩陣,因此無法更新第一個線性層,然后又由于過了第一個線性層就與輸入 \(\boldsymbol{X}\) 無關,所以權重不可以初始化為 \(0\),
3.3 線性回歸的簡潔實作
仍然不全抄,只寫一些有趣的代碼放在這里,
torch.utils.data.DataLoader 回傳的是一個可迭代物件(Iterable)而不是一個迭代器(Iterator),
可以用 iter() 函式構造 Python 迭代器,并使用 next() 函式從迭代器中獲取第一項,如下所示:
next(iter(torch.utils.data.DataLoader(dataset, batch_size, shuffle=is_train)))
然后用 net = nn.Sequential(nn.Linear(2, 1)) 得到模型,那么,除了新寫一個類初始化引數外,怎么初始化第一層的引數呢?通過 net[0] 選擇網路中第一層,然后使用 weight.data 和 bias.data 方法來訪問引數,
net[0].weight 是 torch.nn.parameter.Parameter 類的實體,這個類很有趣,一種被視為模塊引數的張量,Parameter 是 Tensor 的子類,當與 Module 類一起使用時具有非常特殊的屬性:當 Parameter 類被分配為 Module 類的屬性時,它們會自動添加到其引數串列中,并將出現在例如在 parameters() 迭代器中,但是分配一個 Tensor 就沒有這樣的效果,呼叫 net[0].weight 回傳:
net[0].weight
# Parameter containing:
# tensor([[-0.3418, -0.5904]], requires_grad=True)
呼叫 net[0].weight.data 回傳 tensor,這說明它們僅僅是張量:
net[0].weight.data
# tensor([[-0.3418, -0.5904]])
還可以使用替換方法 normal_ 和 fill_ 來重寫引數值,這兩個方法是在 torch.tensor() 里面的方法,
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
損失函式 nn.MSELoss() 及優化演算法 torch.optim.SGD(net.parameters(), lr=0.03),這里 net.parameters() 是個生成器,同時也是一個特殊的迭代器,輸出一下它:
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
print(net.parameters())
# <generator object Module.parameters at 0x716c1cf61150>
下面是訓練代碼,這段代碼先把梯度清零再做反向傳播,證明把梯度清零不會把 Jacobi 矩陣之類的中間狀態清理掉,實踐中極其不推薦像書中這么寫,最好還是在求 loss 之前就清理梯度:
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()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
"""
epoch 1, loss 0.000226
epoch 2, loss 0.000103
epoch 3, loss 0.000103
"""
3.4 softmax 回歸
希望在對硬性類別分類的同時使用軟性帶有概率的模型,
3.4.1 模型
本章介紹了表示分類資料的簡單方法:獨熱編碼(one-hot encoding),獨熱編碼是一個向量,它的分量和類別一樣多,類別對應的分量設定為 \(1\),其他所有分量設定為 \(0\),
本節的網絡架構仍為線性層,這里有和輸出一樣多的仿射函式,向量形式記作 \(\boldsymbol{o} = \boldsymbol{Wx} + \boldsymbol{b}\),具有 \(d\) 個輸入和 \(q\) 個輸出的全連接層,引數開銷為 \(O(dq)\),但是論文 Beyond Fully-Connected Layers with Quaternions: Parameterization of Hypercomplex Multiplications with \(\frac{1}{n}\) Parameters 提及可以將具有 \(d\) 個輸入和 \(q\) 個輸出的全連接層的成本減少到 \(O(dq/n)\),其中超引數 \(n\) 可以設定,以在實際應用中在引數節省和模型有效性之間進行平衡,(完全沒讀這論文說了啥233)
Softmax 函式可以表示為:
\[\hat{\boldsymbol{y}} = \text{softmax} (\boldsymbol{o}) \]其中,
\[\hat{y_j} = \frac{\exp(o_j)}{\sum_k \exp(o_k)} \]文中說,盡管 softmax 是一個非線性函式,但 softmax 回歸的輸出仍然由輸入特征的仿射變換決定,因此,softmax 回歸是一個線性模型(linear model),
3.4.2 損失函式
1. 對數似然
假設資料集 \(\{ \boldsymbol{X}, \boldsymbol{Y}\}\) 具有 \(n\) 個樣本,其中索引 \(i\) 的樣本由特征向量 \(\boldsymbol{x}^{(i)}\) 和獨熱標簽向量 \(\boldsymbol{y}^{(i)}\) 組成,因此可以將估計值與實際值比較:
\[p(\boldsymbol{Y} | \boldsymbol{X}) = \prod_{i=1}^n p(\boldsymbol{y}^{(i)} | \boldsymbol{x}^{(i)}) \]最大化似然仍然是等價于熟悉的最小化負對數似然:
\[-\log P(\boldsymbol{Y} |\boldsymbol{X}) = \sum_{i=1}^n -\log P(\boldsymbol{y}^{(i)} | \boldsymbol{x}^{(i)}) = \sum_{i=1}^n l(\boldsymbol{y}^{(i)}, \hat{\boldsymbol{y}}^{(i)}) \]其中,對于任何標簽 \(\boldsymbol{y}\) 和模型預測 \(\hat{\boldsymbol{y}}\),損失函式為:
\[l(\boldsymbol{y}, \hat{\boldsymbol{y}}) = - \sum_{j=1}^q y_j \log \hat{y}_j \]上式一般被稱為交叉熵損失,
2. softmax 及其導數
利用 softmax 定義可得:
\[l(\boldsymbol{y}, \hat{\boldsymbol{y}}) = - \sum_{j=1}^q y_j \log \frac{\exp (o_j)}{\sum_{k=1}^q \exp (o_k)} \\ = \sum_{j=1}^q y_j \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j \\ = \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j \]考慮相對于任何未規范化的預測 \(o_j\) 的導數,可以得到:
\[\partial_{o_j} l(\boldsymbol{y}, \hat{\boldsymbol{y}}) = \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)} - y_j = \text{softmax}(\boldsymbol{o})_j - y_j = \hat{y}_j - y_j \]不妨設 \(s_i = \text{softmax} (\boldsymbol{o})_i\),再求二階導:
\[\frac{\partial l(\boldsymbol{y}, \hat{\boldsymbol{y}})}{\partial o_i \partial o_j} = \frac{\partial s_i}{\partial o_j} = \begin{cases} s_i (1 - s_i),& i=j \\ -s_j s_i, & i \not = j \end{cases} \]課后題還要求 \(\text{softmax} (\boldsymbol{o})\) 給出的分布方差,并和二階導匹配起來,所以有
\[\begin{align} var(\boldsymbol{o}) &= \frac{1}{q-1} \sum_{j=1}^q (s_j - \overline{s})^2 \\ &= \frac{1}{q-1} [s_1^2 + s_2^2 + \cdots + s_q^2 + \overline{s}^2 * q - 2\overline{s}(s_1+s_2+\cdots+s_q)] \\ &= \frac{1}{q-1} \left( \sum_{j=1}^q s_j^2 - 2 \overline{s} \sum_{j=1}^q s_j\right) + \frac{q}{q-1} \overline{s}^2 \\ &= \frac{1}{q-1} \left( \sum_{j=1}^q s_j(s_j - 1) + \sum_{j=1}^q s_j - 2 \overline{s} \sum_{j=1}^q s_j \right) + \frac{q}{q-1} \overline{s}^2 \\ &= - \frac{1}{q-1} \sum_{j=1}^q \frac{\partial^2 l}{\partial o_j^2} + \frac{q}{q-1} (\overline{s} - \overline{s}^2 ) \\ &\approx - \frac{1}{q} \sum_{j=1}^q \frac{\partial^2 l}{\partial o_j^2} + (\overline{s} - \overline{s}^2 ) \end{align} \]上面式子里除以 \(q-1\) 是符合統計學中無偏估計的做法,當然和除以 \(q\) 差別也不太大,
3. 資訊論淺談
資訊論的基本想法是一個不太可能的事件居然發生了,要比一個非常可能的事件發生,能提供更多的資訊,如果要通過這種基本想法來量化資訊,可以遵循以下三個點:
- 非常可能發生的事件資訊量要比較少,并且極端情況下,確保能夠發生的事件應該沒有資訊量,
- 較不可能發生的事件具有更高的資訊量,
- 獨立事件應具有增量的資訊,例如,投擲的硬幣兩次正面朝上傳遞的資訊量,應該是投擲一次硬幣正面朝上的資訊量的兩倍,
為了滿足上述 \(3\) 個性質,因此定義一個事件 \(x\) 的自資訊(self-information)為
\[I(x) = -\log P(x) \]這里定義的 \(I(x)\) 單位是奈特(nat),一奈特是以 \(\frac{1}{e}\) 的概率觀測到一個事件時獲得的資訊量,
自資訊只處理單個的輸出,可以用香農熵對整個概率分布中不確定性總量進行量化:
\[H(x) = \mathbb{E}_{x\sim P} [I(x)] = -\mathbb{E}_{x\sim P} [\log P(x)] \]這個也可以記作 \(H(P)\),一個分布的香農熵是指遵循這個分布的事件所產生的期望資訊總量,
如果對同一個隨機變數 \(x\) 有兩個單獨的概率分布 \(P(x)\) 和 \(Q(x)\),可以使用KL散度(Kullback-Leibler divergence)來衡量這兩個分布的差異:
\[D_{KL}(P||Q) = \mathbb{E}_{x \sim P} \left[\log \frac{P(x)}{Q(x)} \right] = \mathbb{E}_{x \sim P} [\log P(x) - \log Q(x)] \]在離散型變數的情況下,KL 散度衡量的是,當使用一種被設計成能夠使得概率分布 \(Q\) 產生的訊息的長度最小的編碼,發送包含由概率分布 \(P\) 產生的符號的訊息時,所需要的額外資訊量,
KL 散度有一些有用的性質如下:
- 非負
- KL 散度為 \(0\),當且僅當 \(P\) 和 \(Q\) 在離散性變數的情況下是相同的分布,或者在連續型變數的情況下是“幾乎處處”相同的,
由于上述兩個性質,因此它經常被用作分布之間的某種距離,然而,它并不滿足交換性,即 \(D_{KL}(P||Q) \not = D_{KL}(Q||P)\),
假設此時有一個分布 \(p(x)\),并且希望用另一個分布 \(q(x)\) 來近似它,那么就可以選擇最小化 \(D_{KL}(p||q)\) 或者最小化 \(D_{KL}(q||p)\),這其中選擇哪一個 KL 散度是取決于問題的,選擇 \(D_{KL}(p||q)\) 的目的是為了讓近似分布 \(q\) 在真實分布 \(p\) 放置高概率的所有地方都放置高概率,而選擇 \(D_{KL}(q||p)\) 的目的是為了讓近似分布 \(q\) 在真實分布 \(p\) 放置低概率的所有地方都很少放置高概率,
一個和 KL 散度密切聯系的量是交叉熵,即 \(H(P,Q) = H(P) + D_{KL}(P||Q) = -\mathbb{E}_{x \sim P} \log Q(x)\),因此針對 \(Q\) 最小化交叉熵等價于最小化 KL 散度,
3.5 影像分類資料集
本章其實沒啥亮點,有趣的內容稍微寫一下:
一個用來展示圖片以及標題的函式,有 num_rows 行 num_cols 列,
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
figsize = (num_cols * scale, num_rows * scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
ax.imshow(img.numpy())
else:
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes
此外,num_workers 這個引數表示了使用子行程讀取資料的個數,如果調小 batch_size 的話即使是 CPU 運行的代碼速度也會減慢,在 num_workers=4 的時候,測驗時間長度如下表:
| batch_size | 時間 |
|---|---|
| 1 | 117.74 |
| 4 | 28 |
| 256 | 3.11 |
3.6 softmax 回歸的從零開始實作
仍然是有趣的內容:
torch.normal() 能夠回傳一個其中所有值都符合正態分布的 tensor,
Accumulator 類對多個變數進行累加,
class Accumulator:
def __init__(self, n):
self.data = https://www.cnblogs.com/bringlu/archive/2023/04/09/[0.0] * n
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]
還有一個可以在影片中繪制圖表的實用程式類 Animator,此函式僅能在 notebook 中使用,
import torch
from IPython import display
from d2l import torch as d2l
class Animator: #@save
"""在影片中繪制資料"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地繪制多條線
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函式捕獲引數
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向圖表中添加多個資料點
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
這個類應該怎么用呢?見下方代碼
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):
train_metrics = (train_loss, train_acc)
animator.add(epoch + 1, train_metrics + (test_acc,))
3.7 softmax 回歸的簡潔實作
如何在類外給所有線性層初始化?可以使用 nn.Module.apply(fn) 可以做到,它的本來作用是遞回地對所有子模塊(包括自己)做相同的操作,如:
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)
net.apply(init_weights)
即對 net 中所有 Linear 層初始化引數,
為防止由于指數函式導致的上溢位,因此再繼續 softmax 運算之前,先從所有 \(o_k\) 中減去 \(\max (o_k)\),事實上這樣不會改變 softmax 的回傳值:
\[\begin{align} \hat{y}_j &= \frac{\exp(o_j - max(o_k)) \exp(\max(o_k))}{\sum_k \exp(o_k - \max (o_k))\exp(\max(o_k))} \\ &= \frac{\exp(o_j - max(o_k))}{\sum_k \exp(o_k - \max (o_k))} \end{align} \]又由于有些 \(\exp(o_j - max(o_k))\) 具有較大的負值,可能導致求完指數函式后直接下溢位歸零,并使得 \(\log(\hat{y}_j)\) 的值變為負無窮大,反向傳播幾步之后,可能會發現滿螢屏的 nan,因此將交叉熵和 softmax 操作結合在一起:
這些具體落實到代碼上是模型過完最后一個線性層不要做 softmax 操作,直接往 PyTorch 的 CrossEntropyLoss 里面丟就行了,因為它已經結合好了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/549582.html
標籤:其他
