線性迴歸
1. 數據集名詞
training set
sample
label
feature
2. 均方差損失函數
均值差的平方損失
(a-b)**2 / 2 在 batch 中求平均, 即 sum / len(batch)
爲什麼單個要除以 2 ? 其實標準的也可以不 / 2
爲什麼均方差用的這麼多? 好處壞處, 別的?
均方差叫 MSE, mean squared error, RMSE , MAE
3. 隨機梯度下降
一個小的啓發, 如果 training set 的 batch, 經過梯度回傳以後, 再算一遍 loss, 理應比梯度回傳前的小, 如果不小, 說明模型擬合 能力不夠. 在單個 batch 一定會變小, 不變小說明 lr 取得大了, 整體 loss 均值可能波動或不變, 因爲此起彼伏, 到達瓶頸了, 此時應該怎麼弄?
這一部分不是太懂.
4. 矢量計算
從 C 語言思維抽離, 或者等價, 只是需要記憶 torch 的向量數據類型和他的操作符, 並且數據角度從單個數升級爲向量和矩陣.
下面代碼測出可以視爲向量本身的操作是忽略不計的, 和 float 近似一樣(實際使用中還不知道), 這樣快個 1000和10000 倍也是很容易的, 1e6*1e6 也可以不計時間秒出 (0.002), 矩陣操作先不測, 反正就是要準備好矩陣和向量, 然後數學操作, 操作單位不用 for 循環, 而是最小的爲 torch.Tensor.
代碼片段 A.1
import torch
import time
# init variable a, b as 100000 dimension vector
n = 10000
# use float test real speed
a = torch.randn(n)
b = torch.randn(n)
# a = torch.ones(n)
# b = torch.ones(n)
c = torch.zeros(n)
s = time.time()
for i in range(n):
c[i] = a[i] * b[i]
print(time.time() - s)
s = time.time()
d = a * b
print(time.time() - s)
print(c, d)
# test *, mul, mm
c2 = torch.mul(a, b)
a3 = torch.randn(2, 3)
b3 = torch.randn(3, 1)
c3 = torch.mm(a3, b3)
print(c2, c3)
講解:
1.torch.ones(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
其中 size: 一個數, 或者 ones(2, 3) 就是多維的了; 相似的有 torch.zeros, torch.ones_like(input). 同時注意到 c 類C語言聲明的, 而一般像 d 這樣直接通過 a, b 兩個向量相乘得到, 體現了單元操作是向量/矩陣
2.torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
其中 size 一樣, 可以使 3, 也可以 (4, 5) 就是多維了, 返回值是 N(0,1). 其他的目前先不急, 還有數據類型先不管.
3. 乘號和mul是一致的, 類比 np 拓展的對應位置乘法; 而mm 是 mat-mat-multipy, 嚴格要求是 23 mm 31 這樣的. 剩下的乘法以後再說.
5. 線性數據集隨機生成和展示
生成的數據要有噪聲加上去, 同時噪聲還不能太大. 對 Norm(0, sigma) 的理解, 建立在 sigma 原則上, 大致就是噪聲會和 sigma 一個量級, 比如 sigma = 0.01, 那麼噪聲就是 ± 0.01 類似, 1-sigma 是 60%多, 而且基本符合均勻分佈, 除了中間均值比較多一點點, 3-sigma 是 9544, 3-sigma 是9974, 即幾乎 ± 0.02 以內就得了. 而本題用的 labels 大約是0~20都有的, 可以加這個噪聲.
代碼片段 A.2
%matplotlib inline
import torch
# display can display image, and latex math, just add this
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random
print(torch.__version__)
num_inputs = 2
num_examples = 10
true_w = torch.tensor([[2], [-3.4]])
true_b = 4.2
features = torch.randn(num_examples, num_inputs,
dtype=torch.float32)
# labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels = torch.mm(features, true_w) + true_b
noise = torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
dtype=torch.float32)
labels += noise
true_w = torch.squeeze(true_w)
labels = torch.squeeze(labels)
print('features:', features)
print('labels:', labels)
print('noise:', noise)
print('weight and bias:', true_w, true_b)
print(labels.size(), type(labels.size()), labels.shape, type(labels.shape))
def set_figsize(figsize=(3.5, 2.5)):
plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:, 0].numpy(), labels.numpy(), 1)
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
講解:
1.display 不管他, 反正要用.
2.以 Tensor 角度思考, 特徵是一個量, 即特徵量, 只不過有多個維度, 如上面的兩維, 所以他相加是有權重的, 參數 w 也是有兩維度, 而 torch.randn(100, 2) 正是理解爲有100個數據, 然後多一個最後的特徵維, 是2.
3.Tensor 的維度切片調用和 np 一樣, 都是 a[:, :, 0]這樣的.
4.代碼改了點, 強行使用了矩陣運算, 或者說是把構造用的 true_w 也必須和 features 整體操作, 原來是維度像 3 一樣維度拆開的, 而不拆開一是可是用我上面的, 1002 mm 21, 全是矩陣, 或者用向量對應位置乘法, 再特徵維度求和. 不過不用較真.
5.torch.squeeze(input, dim=None, out=None) → Tensor
所有的 1 維度都去掉並且: The returned tensor shares the storage with the input tensor, so changing the contents of one will change the contents of the other. 所以就是原來的等於原來的去用, 別 y = x, 然後改了 y, 又去用 x, 就說不清了. 多起個名字就好.
6.torch.tensor(data, dtype=None, device=None, requires_grad=False, pin_memory=False) → Tensor
注意這個方法會完全拷貝送進去的 data, 以及想要有 gradient, 需要人爲指定, 以及注意 GPU 的指定. 不知道爲什麼強調那麼多 copy, detach, leaf 等, 不懂.
7.np.random.normal(0, 0.01, size=labels.size()). 目前看來 labels 是 Tensor, 而 size() 是 <class ‘torch.Size’>, 可以默認轉換爲 numpy.shape. 記住就好了.
8.plt.rcParams[‘figure.figsize’], 可以設置大小, 變爲 100 * 100 的超大的, 可以看清楚, 但是不知道對應的單位是什麼, 不過意義也不大.
6. 讀取數據集
涉及到分 batch, 以及隨機讀取, 還有一次 epoch 讀完後幹什麼
代碼片段 A.3
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices) # 樣本的讀取順序是隨機的
for i in range(0, num_examples, batch_size):
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # 最後一次可能不足一個batch
print('what?', j)
# yield features[j], labels[j]
# yield features[j, :], labels[j]
yield features.index_select(0, j), labels.index_select(0, j)
batch_size = 10
steps_per_epoch = 0
for X, y in data_iter(batch_size, features, labels):
# print(X, '\n', y)
steps_per_epoch += 1
print('batch num:', steps_per_epoch)
解析:
1.data_iter 接口就這麼固定了, 最簡化的 feature 結構爲 [num, X, X…], labels 爲 [num, Y, Y…], 然後就可以避免佔用內存過大, 使用 yield 機制來繞過. 缺點是速度慢, 實際還是得開 feeder 多線程. 到時候再說.
2.features.index_select(維度, [2, 4, 5…]) 和 切片直接取區別不大, 不知道爲什麼. 關鍵是他是拷貝出來了一份, 跟原有的 Tensor 無關, 而且需要 index 是 LongTensor.
3.torch.LongTensor, 就是整數 Tensor 的意思, 記住 torch.LongTensor 這個轉換.
Tensor.long() 也是可以的.
7. 定義model, loss, opt
關鍵是 pytorch 的 Tensor 是真正的變量, 在 def 之內的, 之後求到最後, 再返回來求梯度時, 會提前被銷燬, 只是算了一遍而已 (當然, 是以目前我接觸的 Pytorch 而言, 下一節課會怎樣特別說明如 def 臨時變量也可記錄, 則與 Tensorflow 先刻好板子就有些像了, 即不管變量在哪裏, 變量只負責刻板子, 得到一個板子即是靜態圖, 那麼動態圖又是什麼呢?)
代碼片段 A.4
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
def test(x):
bb = torch.ones(1) * 2
return x * bb
x = torch.ones(1)
y = test(x)
# print(b)
print(y)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
def linreg(X, w, b):
return torch.mm(X, w) + b
# def linreg(X):
# global w, b
# return torch.mm(X, w) + b
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.size())) ** 2 / 2
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size # 注意這裏更改param時用的param.data
1.np.random.normal(0, 0.01, (num_inputs, 1)) 是很常用的初始化方式, 大家都喜歡用 (2, 3, 5) 這樣的表示維度, 不過看上去形式不同, 如上面的 size=labels.size(), 以及 torch.ones(2, 3).
2.requires_grad_(requires_grad=True) 自己構造的 Tensor 變量, 一是需要提前聲明在全局, 而是需要指定求梯度, 完全和 C 語言是一樣的. 比如可以靜態開闢要用的每層的神經元 Tensor, 然後 def 中調用, 注意通過參數穿進去, 不然也得用 global, 看看哪個方便吧.
3.linreg(X, w, b) 返回 Y, 作爲模塊記下來.
4.squared 是平方的意思, 同時也可理解爲正方形, 廣場; 而 squared root 是平方的跟, 簡寫 sqrt; 所以 squared, square 這些都是平方的意思, 出現 root 根的時候, 纔是平方根, 即開平方, sqrt. 先相減, 再平方, 再相加, 不是標準差級別了, 是原數平方級別, 也就是方差了, 不過因爲該加的已經加完了, 所以不用再開回去.
5.Tensor.view傳入 shape 或者 size(), 取出數據的按照這樣形式來看的, 注意是同一塊數據, 更改後會影響的.
6.sgd, Stochastic gradient descent, 最簡單的梯度下降, 每次梯度反傳 * lr, 其實它的精髓在於每次隨機的 batch, 然後求得 loss 是這個 batch 的和, 其實不是和也一樣, 然後各個 sample 在 batch 中成分平均, 所以不妨 sum loss, 之後 梯度上 /batch_size, 但是其實導數和原數的大小上並沒有直接關係, 因此其實常數 lr 不太好, 不過很小的 lr 多次迭代總是可以. 當然結合 loss 當前的值, 梯度, lr 算出來的新梯度會是更好的, 不過還沒看這方面的文章.
7.遍歷所有的 params, 注意到目前隨手聲明的變量都是不可求梯度的, 除非指明, 以後也記一個哪些天生就可以求梯度, 或者反之, 總而言之別把 feature 和 labels 給改了.
8.Tensor 只要是 requires_grad=True 都有, 但是要在 backward 以後, 之前是 None; 不過沒弄明白 is_leaf 定義是啥用, 以及如果 requires_grad=False 時情況, 是 None 嗎?
9.明確改的 params.data, 看來 python 要改等號左邊的東西, 而且還要複用的時候要小心了, 因爲除了看上去的數據還有很多別的屬性, 是真的一個類. 這是我猜的, 還需要驗證. 所以儘量代碼一順下來, 而有 for 循環的地方名字繼承下來加 id, 如果真的得一直用, 保證等號右邊可以全部給左邊, 或者用 data, 反正就是小心, 具體看實戰吧.
8. 訓練模型以及查看模型
關鍵是 w, b 變量的傳導.
代碼片段 A.5
lr = 0.03
num_epochs = 10
net = linreg
loss = squared_loss
for epoch in range(num_epochs): # 訓練模型一共需要num_epochs個迭代週期
# 在每一個迭代週期中,會使用訓練數據集中所有樣本一次(假設樣本數能夠被批量大小整除)。X
# 和y分別是小批量樣本的特徵和標籤
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y).sum() # l是有關小批量X和y的損失
l.backward() # 小批量的損失對模型參數求梯度
sgd([w, b], lr, batch_size) # 使用小批量隨機梯度下降迭代模型參數
# 不要忘了梯度清零
w.grad.data.zero_()
b.grad.data.zero_()
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
print(true_w, '\n', w)
print(true_b, '\n', b)
解析:
1.net = linreg, loss = squared_loss, python 函數也是變量, 不過我不習慣用…就不用了… 名字而已, 改改.
2.手搓 loss.sum(), 要記住.
3.sgd 中手挑更新梯度的參數, 組成 list, 果然平時 [] 是好看的, 但是與 () 混的傳參就偷懶用元組.
4.backward 簡而言之就是目前值往前推的梯度, 具體的語言意義還不特別清楚, 但是可以通過查看各個 param 的 gradient 確認. 而且梯度求得默認累計, 需要用w.grad.data.zero_() 清零.
5.原來教程是 100 個點, 3 次 epoch 效果就很好, 當我設置爲 200 個點, 發現 3 次 epoch 竟然不如 100 個, 設置 10 才更好. 或許可以通過一些簡單的數據構造和結構, 來理解結構設計, 超參選取, 訓練技巧.
9. 線性迴歸手搓版OJ題目
題目 B.1
其實就是告訴你用線性迴歸模型來擬合數據, 數據已經給你了, 然後要求在 test 集上算出來結果, 並且這個結果的 loss 要比較小, 而且預測的 W, b 啥的跟之前的類似. 其實自己腦補下…並沒有做成真的 loss 的判定程序.
import torch
import numpy as np
import random
# first problem: 100 * 3 D-N(0, 1)
# W, b
W = torch.tensor([2, 3, 4])
b = 10
num_n = 100
features = torch.tensor(np.random.normal(0, 1, (num_n, 3)))
labels = (features * W).sum(-1, keepdim=False)
labels = labels + torch.tensor(np.random.normal(0, 0.01, size=labels.size()))
print('features', features[:2])
print('lables', labels[:2])
test_features = torch.tensor(np.random.normal(0, 1.1, (num_n // 3, 3)))
test_labels = (test_features * W).sum(-1, keepdim=False)
test_labels = test_labels + torch.tensor(np.random.normal(0, 0.009, size=test_labels.size()))
print('test_features', test_features[:2])
print('test_lables', test_labels[:2])
# end of data
# start to no use W, b to predict test_labels. must use LinearRegression
解答 B.2
第一次真的手寫神經網絡, 很開心. 跟 C 語言一樣嘛, 就是類型不能強制好麻煩, 以及發現特徵增多以後, 目標結果比如是一個值, 那麼參數空間太大, 如果數據量不夠, 那麼會容易陷入局部最優, 或者壓根有多個比較合理的解, 只能通過增大數據量. 不過這個存疑.
不不不, 上面的說法是不準確的, 我把 num_n = 100, 跑 40 個epoch, 結果也差不多好了. 所以數據一樣的時候, 看 epoch, 數據不同時, 看迭代次數, 還有 loss 是不是還在降. 以及增添數據的邊際效應究竟是什麼?
# start to no use W, b to predict test_labels. must use LinearRegression
# first think not this dataLoader, but code this here is more easy to review
def dataLoader(features, labels, batch_size):
index = list(range(len(features)))
random.shuffle(index)
for i in range(0, len(features), batch_size):
s = i
t = min(i+batch_size, len(features))
yield features[index[s:t]], labels[index[s:t]]
# start to no use W, b to predict test_labels. must use LinearRegression
W = np.random.normal(0, 0.01, (3, 1))
# print(W.dtype)
W = torch.tensor(W)
# print(W.dtype)
b = torch.zeros(1)
# this to sentence is hard to bei.....
W.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
def Linear(X, W, b):
# X (-1, 3), W (3, 1), b (1) => (-1, 1)
# print(X, type(W), W.dtype)
Y = torch.mm(X, W) + b
# (-1, 1) => (-1)
Y = torch.squeeze(Y, dim=-1)
return Y
def loss(y_hat, y):
# y_hat (-1), y (-1) => (-1) => sum all (1)
# print(y_hat, y)
res = ((y_hat - y)**2 / 2).sum()
return res
def opt_sgd(grad, lr, batch_size):
# grad mat (x, x)
return grad * lr / batch_size
def add_opt(params, lr, batch_size):
for p in params:
p.data -= opt_sgd(p.grad, lr, batch_size)
# start train
tot_epoch = 20
batch_size = 10
lr = 0.01
training_loss_cur = 0
for _ in range(tot_epoch):
training_loss_cur = 0
for bs_features, bs_labels in dataLoader(features, labels, batch_size):
labels_hat = Linear(bs_features, W, b)
l = loss(labels_hat, bs_labels)
l.backward()
# make opt => sgd, then:
add_opt([W, b], lr, batch_size)
# do not forget!
W.grad.data.zero_()
b.grad.data.zero_()
# print('training loss:', l.item()/batch_size)
training_loss_cur += l.item()
test_l = loss(Linear(test_features, W, b),test_labels)
print('training:', training_loss_cur/len(features), 'testing loss:', test_l.item()/len(test_features))
print(W, b)
# use num_n = 200, then W and b will be right.
10. 關於超參數和網絡設計的問題
在羣裏討論之後, 覺得應該開一個帖子單獨來弄. 明天開帖子, 然後鏈接放到這裏.
11.線性迴歸 nn.Module 實現
import torch
from torch import nn
import numpy as np
torch.manual_seed(1)
print(torch.__version__)
torch.set_default_tensor_type('torch.FloatTensor')
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)
import torch.utils.data as Data
batch_size = 10
# 將訓練數據的特徵和標籤組合
dataset = Data.TensorDataset(features, labels)
# 把 dataset 放入 DataLoader
data_iter = Data.DataLoader(
dataset=dataset, # torch TensorDataset format
batch_size=batch_size, # mini batch size
shuffle=True, # 要不要打亂數據 (打亂比較好)
num_workers=2, # 多線程來讀數據
)
for X, y in data_iter:
print(X, '\n', y)
break
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1)
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
print(net) # 使用print可以打印出網絡的結構
# 寫法一
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])
print(net.parameters)
print(net.parameters())
for param in net.parameters():
print(param, param.name)
print(net[0].weight)
from torch.nn import init
init.normal_(net[0].weight, mean=0.0, std=0.01)
init.constant_(net[0].bias, val=0.0) # 也可以直接修改bias的data: net[0].bias.data.fill_(0)
for param in net.parameters():
print(param)
loss = nn.MSELoss()
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
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() # 梯度清零,等價於net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
dense = net[0]
print(true_w, dense.weight.data)
print(true_b, dense.bias.data)
解析:
1.torch.manual_seed(1) 讓神經網絡 torch 部分隨機固定下來.
2.torch.set_default_tensor_type(‘torch.FloatTensor’) 是 float32, 之前遇到的 float64 問題應該可以通過這個解決.
3.dataset = Data.TensorDataset(features, labels), 然後 data_iter = Data.DataLoader(), 再 for X, y in data_iter: 常規操作.
4.繼承 nn.Module, 之後 def init(self, n_feature): 要包含 super(LinearNet, self).init(), 都是常規操作.
5.self.linear = nn.Linear(n_feature, 1), 然後用的時候 y = self.linear(x); 也就是說先是參數 unit 的在 init 時的傳入, 然後再加 () 以後纔是 call, 每一層神經網絡, 或者每一個神經網絡模塊, 它是一個類.
6.def forward(self, x): 就理解爲 torch 中帶有名字特色的 call, 就行了. 用的時候和 call 一樣, output = net(X) 直接調用就好了, 不過之前 net 要通過 構造函數以及 init 的參數來構建.
7.nn.Sequential, 一個有序的容器,神經網絡模塊將按照在傳入構造器的順序依次被添加到計算圖中執行. 也可以追加 net.add_module.
8.nn.Sequential(OrderedDict([])) 不知道爲什麼普通的 list 不行, 不過直接在括號裏寫就行了, 不用額外加 [], 也不知道這個有什麼用.
9.for param in net.parameters(): 常規操作, 但是沒有名字. 這一點和 tf 不同.
10.初始化 tensor, 用 from torch.nn import init, 然後 init.normal_(net[0].weight, mean=0.0, std=0.01).
11.optimizer = optim.SGD(net.parameters(), lr=0.03) 參數傳進去, opt 其實是對參數以及梯度的加工, 和自己寫的一樣, 然後 optimizer.zero_grad(); l.backward(); optimizer.step().
12.線性迴歸 nn.Module 版OJ題目
差不多, 時間不夠, 略去…sorry
13.錯題
1
y_hat的形狀是[n, 1],而y的形狀是[n],兩者相減得到的結果的形狀是[n, n], 這個要注意.
以及 view 的用法
疑問:
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.size())) ** 2 / 2
最後是 (N, 1), 不用再求和變爲 scalar 嗎?
Softmax與分類模型
1.知識點
softmax 用於分類.
softmax運算符(softmax operator)解決了以上兩個問題。它通過下式將輸出值變換成值爲正且和爲1的概率分佈:
y1,y2,y^3=softmax(o1,o2,o3)
交叉熵: 描述兩個概率相似度.
分類概念. 忘了當時寫的目的了, 猜着是: 計算算出來的數和概率產生關係, 然後通過 one-hot 形式又很好的表達出來, 這樣的設計, 不一定最好, 不過很好. 還有沒有別的分類方法, 離散的?
略…sorry
2.代碼理解
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True)
# print("X size is ", X_exp.size())
# print("partition size is ", partition, partition.size())
return X_exp / partition # 這裏應用了廣播機制
略. sorry
3.OJ題目
比較簡單, 比如分類要求都正確.
略…sorry
5.錯題
1
softmax([100, 101, 102])的結果等於以下的哪一項
[-2, -1, 0]
數學上很容易明白, 但是這隱含了 softmax 什麼樣的機理呢? 相當於均值隨便減, 然後距離均值的偏差, 再取能量嗎? 不明白.
多層感知機
1.知識點
ReLU函數只能在隱藏層中使用.
由於梯度消失問題,有時要避免使用sigmoid和tanh函數, 不過是因爲他們值本身絕對值 <= 1呢, 還是梯度問題?
計算上 ReLU 也最快.
多層感知機就是含有至少一個隱藏層的由全連接層組成的神經網絡, 不是從單隱層感知機然後疊加, 而是隻重複隱藏層, 因爲其它的疊加不經過激活函數沒有意義.
略…sorry
2.代碼理解
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)
略. sorry
3.OJ題目
比較簡單, 比如仍然用分類任務, 要求都正確.
不過 MLP 就可以代表很大部分神經網絡的性質了, 就可以做各種測試實驗了. 拓展連接以後放過來.
略…sorry
5.錯題
1
sigmoid和tanh的異同
數學上兩者可以簡單線性變換得到, 那爲什麼在這麼強大的神經網絡中還要分這兩個呢?
答案一: sigmoid 仍是 [0, 1], 用於特別的控制
答案二: tanh 梯度比 sigmoid 梯度陡峭, 想要更大的梯度, 用前者
答案三: 都不用, 使用 relu
其實還是不懂, 需要討論.
文本預處理
小重點記錄列出
1.lines = [re.sub(’[^a-z]+’, ’ ', line.strip().lower()) for line in f]
2.collections.Counter(tokens) # 返回一個字典,記錄每個詞的出現次數
3.多用類思維
class Vocab(object):
def __init__(self, tokens, min_freq=0, use_special_tokens=False):
counter = count_corpus(tokens) # :
self.token_freqs = list(counter.items())
self.idx_to_token = []
if use_special_tokens:
# padding, begin of sentence, end of sentence, unknown
self.pad, self.bos, self.eos, self.unk = (0, 1, 2, 3)
self.idx_to_token += ['', '', '', '']
else:
self.unk = 0
self.idx_to_token += ['']
self.idx_to_token += [token for token, freq in self.token_freqs
if freq >= min_freq and token not in self.idx_to_token]
self.token_to_idx = dict()
for idx, token in enumerate(self.idx_to_token):
self.token_to_idx[token] = idx
def __len__(self):
return len(self.idx_to_token)
def __getitem__(self, tokens):
if not isinstance(tokens, (list, tuple)):
return self.token_to_idx.get(tokens, self.unk)
return [self.__getitem__(token) for token in tokens]
def to_tokens(self, indices):
if not isinstance(indices, (list, tuple)):
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices]
def count_corpus(sentences):
tokens = [tk for st in sentences for tk in st]
return collections.Counter(tokens) # 返回一個字典,記錄每個詞的出現次數
5.有一些現有的工具可以很好地進行分詞,我們在這裏簡單介紹其中的兩個:spaCy和NLTK。
錯題
1.
無論use_special_token參數是否爲真,都會使用的特殊token是____,作用是用來____。
unk標記,表示未登錄詞
解析: 其實 padding, eos, begin 這些都能夠被替換或重複前面後面或用 mask 代替掉, 只是有他們更合理.
關鍵在於 unk 標記 一定要有, 比如我語音合成時有沒見過的 symbol, 以前的做法是直接忽略, 但實際上這次提了個醒, 應該觸發 unk 標記
語言模型
小重點記錄
1.一段自然語言文本可以看作是一個離散時間序列,給定一個長度爲 T 的詞的序列 w1,w2,…,wT ,語言模型的目標就是評估該序列是否合理; 這樣可以預測下一個序列是什麼, 比如 decoder 自迴歸. 不過 encoder 中的 RNN 是什麼意思呢? 以後看看, 能不能用簡化的 n-gram 模型作爲 Tacotron 的 encoder?
2.idx_to_char = list(set(corpus_chars)) # 去重,得到索引到字符的映射
char_to_idx = {char: i for i, char in enumerate(idx_to_char)} # 字符到索引的映射
3.類似於我預處理文本的步驟
def load_data_jay_lyrics():
with open('/home/kesci/input/jaychou_lyrics4703/jaychou_lyrics.txt') as f:
corpus_chars = f.read()
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[0:10000]
idx_to_char = list(set(corpus_chars))
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
vocab_size = len(char_to_idx)
corpus_indices = [char_to_idx[char] for char in corpus_chars]
return corpus_indices, char_to_idx, idx_to_char, vocab_size
4.時序數據的採樣
在訓練中我們需要每次隨機讀取小批量樣本和標籤。與之前章節的實驗數據不同的是,時序數據的一個樣本通常包含連續的字符。假設時間步數爲5,樣本序列爲5個字符,即“想”“要”“有”“直”“升”。該樣本的標籤序列爲這些字符分別在訓練集中的下一個字符,即“要”“有”“直”“升”“機”,即 X =“想要有直升”, Y =“要有直升機”. 文本這樣直接把要預測的是同檔次的東西, 沒有 condition 的話, 就是平級的東西. 那 Tacotron 其實對上一幀 mel 進行 dropout 處理不應該太嚴重, 要不違背了 RNN 最基礎的預測下一幀能力的框架.
5.訓練語言模型, 會定義 num_steps, 或者可以等價於 n-gram 中的 n ? 通過這個來構造 Tacotron 的訓練會得到什麼? mel 意義下的語言模型? 結合隨機採樣和相鄰採樣來理解.
錯題
1.
相鄰採樣這種辦法我還沒用過, 需要和大家討論. 感覺丟一些上下文沒那麼重要吧…
循環神經網絡基礎
小重點記錄
1.基本的 rnn 公式, 包含 hidden 和 output Ht=ϕ(XtWxh+Ht−1Whh+bh).
Ot=HtWhq+bq.
2.scatter_
def one_hot(x, n_class, dtype=torch.float32):
result = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device) # shape: (n, n_class)
result.scatter_(1, x.long().view(-1, 1), 1) # result[i, x[i, 0]] = 1
return result
3.rnn其實就一個一組 for 循環, 只不過每次的 output 都收集起來, 以及規定了state的格式, 以及 (state, ), 元組的使用比較靈活風騷罷了.
def rnn(inputs, state, params):
# inputs和outputs皆爲num_steps個形狀爲(batch_size, vocab_size)的矩陣
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
H = torch.tanh(torch.matmul(X, W_xh) + torch.matmul(H, W_hh) + b_h)
Y = torch.matmul(H, W_hq) + b_q
outputs.append(Y)
return outputs, (H,)
def init_rnn_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device), )
state = init_rnn_state(X.shape[0], num_hiddens, device)
inputs = to_onehot(X.to(device), vocab_size)
params = get_params()
outputs, state_new = rnn(inputs, state, params)
4.裁剪梯度, 疑問是是每個單獨範數還是所有參數範數平均值, 以及 rnn 的時間上的消逝可以這樣解決掉嗎? 只有爆炸可以避免吧?
def grad_clipping(params, theta, device):
norm = torch.tensor([0.0], device=device)
for param in params:
norm += (param.grad.data ** 2).sum()
norm = norm.sqrt().item()
if norm > theta:
for param in params:
param.grad.data *= (theta / norm)
5.RNN的 torch 版 class
class RNNModel(nn.Module):
def __init__(self, rnn_layer, vocab_size):
super(RNNModel, self).__init__()
self.rnn = rnn_layer
self.hidden_size = rnn_layer.hidden_size * (2 if rnn_layer.bidirectional else 1)
self.vocab_size = vocab_size
self.dense = nn.Linear(self.hidden_size, vocab_size)
def forward(self, inputs, state):
# inputs.shape: (batch_size, num_steps)
X = to_onehot(inputs, vocab_size)
X = torch.stack(X) # X.shape: (num_steps, batch_size, vocab_size)
hiddens, state = self.rnn(X, state)
hiddens = hiddens.view(-1, hiddens.shape[-1]) # hiddens.shape: (num_steps * batch_size, hidden_size)
output = self.dense(hiddens)
return output, state
char_to_idx):
state = None
output = [char_to_idx[prefix[0]]] # output記錄prefix加上預測的num_chars個字符
for t in range(num_chars + len(prefix) - 1):
X = torch.tensor([output[-1]], device=device).view(1, 1)
(Y, state) = model(X, state) # 前向計算不需要傳入模型參數
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(Y.argmax(dim=1).item())
return ''.join([idx_to_char[i] for i in output])
錯題
1
採用相鄰採樣僅在每個訓練週期開始的時候初始化隱藏狀態是因爲相鄰的兩個批量在原始數據上是連續的
這樣的訓練辦法還沒用過