程序員入門人工智能:如何從零開始實現線性迴歸?

程序員 人工智能 中央處理器 異步社區 2019-05-19


線性迴歸的從零開始實現

在瞭解了線性迴歸的背景知識之後,現在我們可以動手實現它了。儘管強大的深度學習框架可以減少大量重複性工作,但若過於依賴它提供的便利,會導致我們很難深入理解深度學習是如何工作的。因此,本節將介紹如何只利用NDArray和autograd來實現一個線性迴歸的訓練。

首先,導入本節中實驗所需的包或模塊,其中的matplotlib可用於作圖,且設置成嵌入顯示。

In [1]: %matplotlib inline
from IPython import display
from matplotlib import pyplot as plt
from mxnet import autograd, nd
import random

1 生成數據集

我們構造一個簡單的人工訓練數據集,它可以使我們能夠直觀比較學到的參數和真實的模型參數的區別。設訓練數據集樣本數為 1000,輸入個數(特徵數)為2。給定隨機生成的批量樣本特徵

程序員入門人工智能:如何從零開始實現線性迴歸?

,我們使用線性迴歸模型真實權重

程序員入門人工智能:如何從零開始實現線性迴歸?

和偏差

程序員入門人工智能:如何從零開始實現線性迴歸?

,以及一個隨機噪聲項ϵ來生成標籤


程序員入門人工智能:如何從零開始實現線性迴歸?


其中噪聲項ϵ服從均值為 0、標準差為 0.01 的正態分佈。噪聲代表了數據集中無意義的干擾。下面,讓我們生成數據集。

In [2]: num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = nd.random.normal(scale=1, shape=(num_examples, num_inputs))
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += nd.random.normal(scale=0.01, shape=labels.shape)

注意,features的每一行是一個長度為 2 的向量,而labels的每一行是一個長度為1的向量(標量)。

In [3]: features[0], labels[0]
Out[3]: (
[2.2122064 0.7740038]
<NDArray 2 @cpu(0)>,
[6.000587]
<NDArray 1 @cpu(0)>)

通過生成第二個特徵features[:, 1]和標籤labels的散點圖,可以更直觀地觀察兩者間的線性關係。

In [4]: def use_svg_display():
# 用矢量圖顯示
display.set_matplotlib_formats('svg')
def set_f igsize(f igsize=(3.5, 2.5)):
use_svg_display()
# 設置圖的尺寸
plt.rcParams['f igure.f igsize'] = f igsize
set_f igsize()
plt.scatter(features[:, 1].asnumpy(), labels.asnumpy(), 1); # 加分號只顯示圖


程序員入門人工智能:如何從零開始實現線性迴歸?


我們將上面的plt作圖函數以及use_svg_display函數和set_f igsize函數定義在d2lzh包裡。以後在作圖時,我們將直接調用d2lzh.plt。由於plt在d2lzh包中是一個全局變量,我們在作圖前只需要調用d2lzh.set_figsize()即可打印矢量圖並設置圖的尺寸。

2 讀取數據集

在訓練模型的時候,我們需要遍歷數據集並不斷讀取小批量數據樣本。這裡我們定義一個函數:它每次返回batch_size(批量大小)個隨機樣本的特徵和標籤。

In [5]: # 本函數已保存在d2lzh包中方便以後使用
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuff le(indices) # 樣本的讀取順序是隨機的
for i in range(0, num_examples, batch_size):
j = nd.array(indices[i: min(i + batch_size, num_examples)])
yield features.take(j), labels.take(j) # take函數根據索引返回對應元素

讓我們讀取第一個小批量數據樣本並打印。每個批量的特徵形狀為(10, 2),分別對應批量大小和輸入個數;標籤形狀為批量大小。

In [6]: batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, y)
break
[[ 1.0876857 -1.7063738 ]
[-0.51129895 0.46543437]
[ 0.1533563 -0.735794 ]
[ 0.3717077 0.9300072 ]
[ 1.0115732 -0.83923554]
[ 1.9738784 0.81172043]
[-1.771029 -0.45138445]
[ 0.7465509 -0.5054337 ]
[-0.52480155 0.3005414 ]
[ 0.5583534 -0.6039059 ]]
[12.174357 1.6139998 6.9870367 1.7626053 9.06552 5.3893285
2.1933131 7.4012175 2.1383817 7.379732 ]

3 初始化模型參數

我們將權重初始化成均值為 0、標準差為 0.01 的正態隨機數,偏差則初始化成 0。

In [7]: w = nd.random.normal(scale=0.01, shape=(num_inputs, 1))
b = nd.zeros(shape=(1,))

之後的模型訓練中,需要對這些參數求梯度來迭代參數的值,因此我們需要創建它們的梯度。

In [8]: w.attach_grad()
b.attach_grad()

4 定義模型

下面是線性迴歸的矢量計算表達式的實現。我們使用dot函數做矩陣乘法。

In [9]: def linreg(X, w, b): # 本函數已保存在d2lzh包中方便以後使用
return nd.dot(X, w) + b

5 定義損失函數

我們使用3.1節描述的平方損失來定義線性迴歸的損失函數。在實現中,我們需要把真實值y變形成預測值y_hat的形狀。以下函數返回的結果也將和y_hat的形狀相同。

In [10]: def squared_loss(y_hat, y): # 本函數已保存在d2lzh包中方便以後使用
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

6 定義優化算法

以下的sgd函數實現了3.1節中介紹的小批量隨機梯度下降算法。它通過不斷迭代模型參數來優化損失函數。這裡自動求梯度模塊計算得來的梯度是一個批量樣本的梯度和。我們將它除以批量大小來得到平均值。

In [11]: def sgd(params, lr, batch_size): # 本函數已保存在d2lzh包中方便以後使用

for param in params:

7 訓練模型

在訓練中,我們將多次迭代模型參數。在每次迭代中,我們根據當前讀取的小批量數據樣本(特徵X和標籤y),通過調用反向函數backward計算小批量隨機梯度,並調用優化算法sgd迭代模型參數。由於我們之前設批量大小batch_size為 10,每個小批量的損失l的形狀為(10, 1)。回憶一下2.3節。由於變量l並不是一個標量,運行l.backward()將對l中元素求和得到新的變量,再求該變量有關模型參數的梯度。

在一個迭代週期(epoch)中,我們將完整遍歷一遍data_iter函數,並對訓練數據集中所有樣本都使用一次(假設樣本數能夠被批量大小整除)。這裡的迭代週期個數num_epochs和學習率lr都是超參數,分別設3和 0.03。在實踐中,大多超參數都需要通過反覆試錯來不斷調節。雖然迭代週期數設得越大模型可能越有效,但是訓練時間可能過長。我們會在後面第7章中詳細介紹學習率對模型的影響。

In [12]: lr = 0.03
num_epochs = 3
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):
with autograd.record():
l = loss(net(X, w, b), y) # l是有關小批量X和y的損失
l.backward() # 小批量的損失對模型參數求梯度
sgd([w, b], lr, batch_size) # 使用小批量隨機梯度下降迭代模型參數
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().asnumpy()))
epoch 1, loss 0.040436
epoch 2, loss 0.000155
epoch 3, loss 0.000050

訓練完成後,我們可以比較學到的參數和用來生成訓練集的真實參數。它們應該很接近。

In [13]: true_w, w
Out[13]: ([2, -3.4],
[[ 1.9996936]
[-3.3997262]]
<NDArray 2x1 @cpu(0)>)
In [14]: true_b, b
Out[14]: (4.2,
[4.199704]
<NDArray 1 @cpu(0)>)

小結

  • 可以看出,僅使用NDArray和autograd模塊就可以很容易地實現一個模型。接下來,本書會在此基礎上描述更多深度學習模型,並介紹怎樣使用更簡潔的代碼來實現它們。

練習

(1)為什麼squared_loss函數中需要使用reshape函數?

(2)嘗試使用不同的學習率,觀察損失函數值的下降快慢。

(3)如果樣本個數不能被批量大小整除,data_iter函數的行為會有什麼變化?

程序員入門人工智能:如何從零開始實現線性迴歸?

相關推薦

推薦中...