參考網(wǎng)站:http://zh.gluon.ai/
從0開始的線性回歸
雖然強大的深度學(xué)習(xí)框架可以減少很多重復(fù)性工作,但如果你過于依賴它提供的便利抽象针贬,那么你可能不會很容易的理解到底深度學(xué)習(xí)是如何工作的击费。所以我們的第一個教程是如何只利用ndarray和autograd來實現(xiàn)一個線性回歸的訓(xùn)練。
線性回歸
給定一個數(shù)據(jù)點集合X和對應(yīng)的目標(biāo)值y
桦他,線性模型的目標(biāo)是找一根線蔫巩,其由向量w
和位移b
組成,來最好的近似每個樣本X[i]
和y[i]
快压。用數(shù)學(xué)符號來表示就是我們將學(xué)w
和b
來預(yù)測圆仔,
并最小化所有數(shù)據(jù)點上的平方誤差
你可能會對我們把古老的線性回歸作為深度學(xué)習(xí)的一個樣例表示很奇怪。實際上線性模型是最簡單但也可能是最有用的神經(jīng)網(wǎng)絡(luò)蔫劣。一個神經(jīng)網(wǎng)絡(luò)就是一個由節(jié)點(神經(jīng)元)和有向邊組成的集合坪郭。我們一般把一些節(jié)點組成層,每一層使用下一層的節(jié)點作為輸入拦宣,并輸出給上面層使用截粗。為了計算一個節(jié)點值,我們將輸入節(jié)點值做加權(quán)和鸵隧,然后再加上一個激活函數(shù)。對于線性回歸而言意推,它是一個兩層神經(jīng)網(wǎng)絡(luò)豆瘫,其中第一層是(下圖橙色點)輸入,每個節(jié)點對應(yīng)輸入數(shù)據(jù)點的一個維度菊值,第二層是單輸出節(jié)點(下圖綠色點)外驱,它使用身份函數(shù)(f(x)=x)
作為激活函數(shù)育灸。
創(chuàng)建數(shù)據(jù)集
這里我們使用一個人工數(shù)據(jù)集來把事情弄簡單些,因為這樣我們將知道真實的模型是什么樣的昵宇。具體來首我們使用如下方法來生成數(shù)據(jù)
y[i] = 2 * X[i][0] - 3.4 * X[i][1] + 4.2 + noise
這里噪音服從均值0和方差為0.1的正態(tài)分布磅崭。
from mxnet import ndarray as nd
from mxnet import autograd
num_inputs = 2
num_examples = 1000
true_w = [2, 3.4]
true_b = 4.2
X = nd.random_normal(shape=(num_examples, num_inputs))
y = true_w[0] * X[:, 0] - true_w[1] * X[:, 1] + true_b
y += .01 * nd.random_normal(shape=y.shape)
注意到X
的每一行是一個長度為2的向量,而y
的每一行是一個長度為1的向量(標(biāo)量)瓦哎。
print(X[0], y[0])
[ 2.21220636 1.16307867]
[ 4.6620779]
數(shù)據(jù)讀取
當(dāng)我們開始訓(xùn)練神經(jīng)網(wǎng)絡(luò)的時候砸喻,我們需要不斷的讀取數(shù)據(jù)塊。這里我們定義一個函數(shù)它每次返回batch_size
個隨機的樣本和對應(yīng)的目標(biāo)蒋譬。我們通過python的yield
來構(gòu)造一個迭代器割岛。
import random
batch_size = 10
def data_iter():
# 產(chǎn)生一個隨機索引
idx = list(range(num_examples))
random.shuffle(idx)
for i in range(0, num_examples, batch_size):
j = nd.array(idx[i:min(i+batch_size,num_examples)])
yield nd.take(X, j), nd.take(y, j)
下面代碼讀取第一個隨機數(shù)據(jù)塊
for data, label in data_iter():
print(data, label)
break
[[ 0.24021588 -0.53960389]
[ 0.01106104 0.36940244]
[-0.21115878 -0.64478874]
[-0.73600543 1.56812 ]
[-0.73192883 -0.50927299]
[-0.48362762 0.27216455]
[-0.60159451 0.29670078]
[-0.88538933 0.09512273]
[ 0.19420861 -0.91510016]
[ 0.00955429 -0.35396427]]
[ 6.49492311 2.97613215 5.98414278 -2.6195066 4.46368217 2.31007123
1.97259736 2.08594513 7.70643806 5.41053724]
初始化模型參數(shù)
下面我們隨機初始化模型參數(shù)
w = nd.random_normal(shape=(num_inputs, 1))
b = nd.zeros((1,))
params = [w, b]
之后訓(xùn)練時我們需要對這些參數(shù)求導(dǎo)來更新它們的值,所以我們需要創(chuàng)建它們的梯度犯助。
for param in params:
param.attach_grad()
定義模型
線性模型就是將輸入和模型做乘法再加上偏移:
def net(X):
return nd.dot(X, w) + b
損失函數(shù)
我們使用常見的平方誤差來衡量預(yù)測的目標(biāo)和真實目標(biāo)之間的差距癣漆。
def square_loss(yhat, y):
# 注意這里我們把y變形成yhat的形狀來避免自動廣播
return (yhat - y.reshape(yhat.shape)) ** 2
優(yōu)化
雖然線性回歸有顯試解,但絕大部分模型并沒有剂买。所以我們這里通過隨機梯度下降來求解惠爽。每一步,我們將模型參數(shù)沿著梯度的反方向走特定距離瞬哼,這個距離一般叫學(xué)習(xí)率疆股。(我們會之后一直使用這個函數(shù),我們將其保存在utils.py倒槐。)
def SGD(params, lr):
for param in params:
param[:] = param - lr * param.grad
訓(xùn)練
現(xiàn)在我們可以開始訓(xùn)練了旬痹。訓(xùn)練通常需要迭代數(shù)據(jù)數(shù)次,一次迭代里讨越,我們每次隨機讀取固定數(shù)個數(shù)據(jù)點两残,計算梯度并更新模型參數(shù)。
epochs = 5
learning_rate = .001
for e in range(epochs):
total_loss = 0
for data, label in data_iter():
with autograd.record():
output = net(data)
loss = square_loss(output, label)
loss.backward()
SGD(params, learning_rate)
total_loss += nd.sum(loss).asscalar()
print("Epoch %d, average loss: %f" % (e, total_loss/num_examples))
Epoch 0, average loss: 7.941256
Epoch 1, average loss: 0.100285
Epoch 2, average loss: 0.001379
Epoch 3, average loss: 0.000120
Epoch 4, average loss: 0.000103
訓(xùn)練完成后我們可以比較學(xué)到的參數(shù)和真實參數(shù)
true_w, w
([2, 3.4],
[[ 1.99963176]
[-3.40014362]])
true_b, b
(4.2,
[ 4.19964504])
結(jié)論
我們現(xiàn)在看到僅僅使用NDArray和autograd我們可以很容易的實現(xiàn)一個模型把跨。有興趣的話人弓,可以嘗試用不同的學(xué)習(xí)率查看誤差下降速度(收斂率)。
使用Gluon的線性回歸
前面我們僅僅使用了ndarray和autograd來實現(xiàn)線性回歸着逐,現(xiàn)在我們?nèi)匀粚崿F(xiàn)同樣的模型崔赌,但是使用高層抽象包gluon
。
創(chuàng)建數(shù)據(jù)集
我們生成同樣的數(shù)據(jù)集
from mxnet import ndarray as nd
from mxnet import autograd
from mxnet import gluon
num_inputs = 2
num_examples = 1000
true_w = [2, 3.4]
true_b = 4.2
X = nd.random_normal(shape=(num_examples, num_inputs))
y = true_w[0] * X[:, 0] - true_w[1] * X[:, 1] + true_b
y += .01 * nd.random_normal(shape=y.shape)
數(shù)據(jù)讀取
但這里使用data
模塊來讀取數(shù)據(jù)耸别。
batch_size = 10
dataset = gluon.data.ArrayDataset(X, y)
data_iter = gluon.data.DataLoader(dataset, batch_size, shuffle=True)
讀取跟前面一致:
for data, label in data_iter:
print(data, label)
break
[[ 1.66524243 -0.790555 ]
[ 2.73936391 0.73395604]
[-0.82552391 0.60547197]
[ 0.18361944 -1.8479687 ]
[-1.11130977 -0.30177692]
[-0.23753072 -0.68533319]
[ 0.02715491 -0.26509324]
[-1.07131875 0.9324615 ]
[ 0.6325348 -0.19508815]
[ 0.82890278 -0.25843123]]
[ 10.22668362 7.193501 0.48110276 10.85089588 3.0170579
6.05681705 5.15688562 -1.11165142 6.12516403 6.74039841]
定義模型
當(dāng)我們手寫模型的時候健芭,我們需要先聲明模型參數(shù),然后再使用它們來構(gòu)建模型秀姐。但gluon
提供大量提前定制好的層慈迈,使得我們只需要主要關(guān)注使用哪些層來構(gòu)建模型。例如線性模型就是使用的對應(yīng)Dense
層省有。
雖然我們之后會介紹如何構(gòu)造任意結(jié)構(gòu)的神經(jīng)網(wǎng)絡(luò)痒留,構(gòu)建模型最簡單的辦法是利用Sequential
來所有層串起來谴麦。首先我們定義一個空的模型:
net = gluon.nn.Sequential()
然后我們加入一個Dense層,它唯一必須要定義的參數(shù)就是輸出節(jié)點的個數(shù)伸头,在線性模型里面是1.
net.add(gluon.nn.Dense(1))
(注意這里我們并沒有定義說這個層的輸入節(jié)點是多少匾效,這個在之后真正給數(shù)據(jù)的時候系統(tǒng)會自動賦值。我們之后會詳細(xì)介紹這個特性是如何工作的恤磷。)
初始化模型參數(shù)
在使用net
前我們必須要初始化模型權(quán)重面哼,這里我們使用默認(rèn)隨機初始化方法(之后我們會介紹更多的初始化方法)。
net.initialize()
損失函數(shù)
gluon
提供了平方誤差函數(shù):
square_loss = gluon.loss.L2Loss()
優(yōu)化
同樣我們無需手動實現(xiàn)隨機梯度下降碗殷,我們可以用創(chuàng)建一個Trainer
的實例精绎,并且將模型參數(shù)傳遞給它就行。
trainer = gluon.Trainer(
net.collect_params(), 'sgd', {'learning_rate': 0.1})
訓(xùn)練
這里的訓(xùn)練跟前面沒有太多區(qū)別锌妻,唯一的就是我們不再是調(diào)用SGD
代乃,而是trainer.step
來更新模型。
epochs = 5
batch_size = 10
learning_rate = .01
for e in range(epochs):
total_loss = 0
for data, label in data_iter:
with autograd.record():
output = net(data)
loss = square_loss(output, label)
loss.backward()
trainer.step(batch_size)
total_loss += nd.sum(loss).asscalar()
print("Epoch %d, average loss: %f" % (e, total_loss/num_examples))
Epoch 0, average loss: 0.905177
Epoch 1, average loss: 0.000052
Epoch 2, average loss: 0.000052
Epoch 3, average loss: 0.000052
Epoch 4, average loss: 0.000052
比較學(xué)到的和真實模型仿粹。我們先從net拿到需要的層搁吓,然后訪問其權(quán)重和位移。
dense = net[0]
true_w, dense.weight.data()
([2, 3.4],
[[ 2.00046849 -3.40106511]])
true_b, dense.bias.data()
(4.2,
[ 4.20045042])
結(jié)論
可以看到gluon
可以幫助我們更快更干凈的實現(xiàn)模型吭历。在訓(xùn)練的時候堕仔,為什么我們用了比前面要大10倍的學(xué)習(xí)率呢?運行 help(trainer.step)
可以知道晌区,學(xué)習(xí)率的數(shù)值一般設(shè)置為1/batch_size摩骨。我們?nèi)绾文苣玫絯eight的梯度呢?運行 help(dense.weight)
可知朗若,dense.weight.grad()能查看梯度恼五。善用help命令,能讓我們更好的理解我們的程序哭懈。
下一Part我們將討論如何解決邏輯回歸的問題灾馒。