更新時間:2021年03月10日17時52分 來源:傳智教育 瀏覽次數(shù):
在這篇文章中,我們將從頭開始實現(xiàn)一個簡單的3層神經(jīng)網(wǎng)絡。假設你熟悉基本的微積分和機器學習概念,例如:知道什么是分類和正規(guī)化。理想情況下,您還可以了解梯度下降等優(yōu)化技術的工作原理。 但是為什么要從頭開始實施神經(jīng)網(wǎng)絡呢?它可以幫助我們了解神經(jīng)網(wǎng)絡的工作原理,這對于設計有效模型至關重要。
這里我們首先生成后面要用的數(shù)據(jù)集。生成數(shù)據(jù)集可以使用scikit-learn (http://scikit-learn.org/)里面的make_moons (http://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_moons.html)函數(shù)。
In [1]:
# 導包
import matplotlib.pyplot as plt
import numpy as np
import sklearn
import sklearn.datasets
import sklearn.linear_model
import matplotlib
# 設置matplot參數(shù)
%matplotlib inline
matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)
In [2]:
# 生成數(shù)據(jù)集并用plot畫出
np.random.seed(0)
X, y = sklearn.datasets.make_moons(200, noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)
Out[2]:
<matplotlib.collections.PathCollection at 0x1a1ee64f60>
這個數(shù)據(jù)集有兩個類別,分別是用紅色和藍色表示。我們的目標是使用機器學習的分類器根據(jù)x, y坐標預測出正確的類別。注意這里的數(shù)據(jù)并不是線性可分的。我們不能畫一條直線把這個數(shù)據(jù)集分成兩個類別。這就意味著,線性分類器,比如邏輯回歸無法對我們的數(shù)據(jù)行擬合,換言之就是無法用線性分類器對這個數(shù)據(jù)集行分類。除非手動構造非線性特征,比如多項式。事實上這正是神經(jīng)網(wǎng)絡的主要優(yōu)點之一。使用神經(jīng)網(wǎng)絡我們不用去做特征工程 (http://machinelearningmastery.com/discover-feature-engineering-how-to-engineerfeatures-and-how-to-get-good-at-it/)。神經(jīng)網(wǎng)絡的隱藏層會自動的學習這些特征。
這里為了演示,我們使用邏輯回歸行分類。輸入是數(shù)據(jù)集里的x, y坐標,輸出是預測的類別(0或者1)。為了方便我們直接使用scikit-learn 中的邏輯回歸。
In [3]:
# 訓練邏輯回歸分類器
clf = sklearn.linear_model.LogisticRegressionCV(cv=5)
clf.fit(X, y)
Out[3]:
LogisticRegressionCV(Cs=10, class_weight=None, cv=5, dual=False, fit_intercept=True, intercept_scaling=1.0, max_iter=100, multi_class='warn', n_jobs=None, penalty='l2', random_state=None, refit=True, scoring=None, solver='lbfgs', tol=0.0001, verbose=0)
In [4]:
# 這是個幫助函數(shù),這個函數(shù)的作用是用來畫決策邊界的,如果看不懂函數(shù)內(nèi)容不用介意。
def plot_decision_boundary(pred_func):
# 設置邊界最大最小值
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
h = 0.01
# 生成一個點間網(wǎng)格,它們之間的距離為h
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# 預測
Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 繪制輪廓和訓練示例
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
In [5]:
plot_decision_boundary(lambda x: clf.predict(x))
plt.title("Logistic Regression")
Out[5]: Text(0.5, 1.0, 'Logistic Regression')
這個圖顯示了通過邏輯回歸學習到的決策邊界。這里的直線已經(jīng)盡可能的把數(shù)據(jù)集分成兩部分,但是分的效果還是不理想,還是有些分錯類別的。
現(xiàn)在我們構建一個3層神經(jīng)網(wǎng)絡,其中包含一個輸入層,一個隱藏層和一個輸出層。輸入層中的節(jié)點數(shù)由我們的數(shù)據(jù)的維數(shù)確定的,這里是2。輸出層中的節(jié)點數(shù)由我們擁有的類別數(shù)量決定,這里也是2。因為我們只有兩個類 實際上只用一個輸出節(jié)點可以預測0或1,但是有兩個可以讓網(wǎng)絡更容易擴展到更多的類。 網(wǎng)絡的輸入將是x和y坐標,其輸出將是兩個概率,一個用于類別0,一個用于類別1。 神經(jīng)網(wǎng)絡如圖所示:
我們可以選擇隱藏層的維度也就是節(jié)點數(shù)。隱藏層的節(jié)點越多,得到的神經(jīng)網(wǎng)絡功能就越復雜。但更高的維度需要付出代價。首先,學習網(wǎng)絡參數(shù)和預測就需要更多的計算量。同時更多參數(shù)也意味著我們得到的模型更容易過擬合。 如何選擇隱藏層的大???雖然有一些指導方針,但實際上具體問題需要具體分析,稍后我們將改變隱藏層中的節(jié)點數(shù)量來查看它如何影響我們的輸出。
因為我們希望神經(jīng)網(wǎng)絡最終輸出概率值,所以輸出層的激活函數(shù)使用softmax(https://en.wikipedia.org/wiki/Softmax_function)這只是將原始分數(shù)轉換為概率的一種方法。同時如果熟悉邏輯函數(shù),可以認為softmax可以做多分類。
現(xiàn)在我們把具體代碼實現(xiàn)來,這里先定義一些后面求梯度會用到的參數(shù):
In [6]:
num_examples = len(X) # 訓練集大小
nn_input_dim = 2 # 輸入層維度
nn_output_dim = 2 # 輸出層維度
# 梯度下降參數(shù),這兩個參數(shù)是?為設定的超參數(shù)
epsilon = 0.01 # 梯度下降的學習率
reg_lambda = 0.01 # 正則化強度
首先我們實現(xiàn)上面定義的損失函數(shù),這里用它來評估我們的模型的好壞:
In [7]:
# 幫助函數(shù)用來評估數(shù)據(jù)集上的總體損失
def calculate_loss(model):
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
# 前向傳播來計算預測值
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
# 計算損失值
corect_logprobs = -np.log(probs[range(num_examples), y])
data_loss = np.sum(corect_logprobs)
# 為損失添加正則化
data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
return 1./num_examples * data_loss
這里實現(xiàn)了一個幫助函數(shù)來計算網(wǎng)絡的輸出。它按照上面的定義行前向傳播,并返回具有最高概率的類別。
In [8]:
# 幫助函數(shù)用來預測輸出類別(0或者1)
def predict(model, x):
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
# 前向傳播
z1 = x.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
return np.argmax(probs, axis=1)
最后這個函數(shù)是訓練神經(jīng)網(wǎng)絡。這個函數(shù)李我們用前面定義的的反向傳播導數(shù)實現(xiàn)批量梯度下降。
In [9]:
# 這個函數(shù)學習神經(jīng)網(wǎng)絡的參數(shù)并返回模型。
# - nn_hdim: 隱藏層中的節(jié)點數(shù)
# - num_passes: 通過梯度下降的訓練數(shù)據(jù)的次數(shù)
# - print_loss: 如果為True,則每1000次迭代打印一次損失值
def build_model(nn_hdim, num_passes=20000, print_loss=False):
# 將參數(shù)初始化為隨機值。模型會學習這些參數(shù)。
np.random.seed(0)
W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim)
b1 = np.zeros((1, nn_hdim))
W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim)
b2 = np.zeros((1, nn_output_dim))
# 這個是最終返回的值
model = {}
# 梯度遞降
for i in range(0, num_passes):
# 前向傳播
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
# 反向傳播
delta3 = probs
delta3[range(num_examples), y] -= 1
dW2 = (a1.T).dot(delta3)
db2 = np.sum(delta3, axis=0, keepdims=True)
delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
dW1 = np.dot(X.T, delta2)
db1 = np.sum(delta2, axis=0)
# 添加正則化項(b1和b2沒有正則化項)
dW2 += reg_lambda * W2
dW1 += reg_lambda * W1
# 梯度下降參數(shù)更新
W1 += -epsilon * dW1
b1 += -epsilon * db1
W2 += -epsilon * dW2
b2 += -epsilon * db2
# 為模型分配新參數(shù)
model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
# 選擇打印損失,這個操作開銷很大,因為它使用整個數(shù)據(jù)集,所以不要頻繁做這個操作。
if print_loss and i % 1000 == 0:
print("Loss after iteration %i: %f" % (i, calculate_loss(model)))
return model
下面來看看如果我們訓練隱藏層大小為3的網(wǎng)絡會發(fā)生什么。
In [10]:
# 隱藏層大小為3
model = build_model(3, print_loss=True)
# 繪制決策邊界
plot_decision_boundary(lambda x: predict(model, x))
plt.title("Decision Boundary for hidden layer size 3")
Loss after iteration 0: 0.432387
Loss after iteration 1000: 0.068947
Loss after iteration 2000: 0.068901
Loss after iteration 3000: 0.071218
Loss after iteration 4000: 0.071253
Loss after iteration 5000: 0.071278
Loss after iteration 6000: 0.071293
Loss after iteration 7000: 0.071303
Loss after iteration 8000: 0.071308
Loss after iteration 9000: 0.071312
Loss after iteration 10000: 0.071314
Loss after iteration 11000: 0.071315
Loss after iteration 12000: 0.071315
Loss after iteration 13000: 0.071316
Loss after iteration 14000: 0.071316
Loss after iteration 15000: 0.071316
Loss after iteration 16000: 0.071316
Loss after iteration 17000: 0.071316
Loss after iteration 18000: 0.071316
Loss after iteration 19000: 0.071316
Out[10]: Text(0.5, 1.0, 'Decision Boundary for hidden layer size 3')
這看起來很不錯。我們的神經(jīng)網(wǎng)絡能夠找到一個成功分離兩個類別的決策邊界。
在上面的示例中,我們設置了隱藏層大小3,接著看看改變隱藏層大小對結果的影響。
In [11]:
plt.figure(figsize=(16, 32))
hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):
plt.subplot(5, 2, i+1)
plt.title('Hidden Layer size %d' % nn_hdim)
model = build_model(nn_hdim)
plot_decision_boundary(lambda x: predict(model, x))
plt.show()
我們可以看到,隱藏層在低維度時可以很好地擬合數(shù)據(jù)的總體趨勢,更高的維度容易過擬合。當隱藏層維度過大時,模型嘗試著去“記住”數(shù)據(jù)的形狀而不是擬合他們的一般形狀。通常情況我們還需要一個單獨的測試集來評估我們的模型,隱藏層維度較小的模型在這個測試集上的表現(xiàn)應該更好,因為這個模型更加通用。我們也可以通過更強的正則化來抵消過度擬合,但是選擇一個合適的隱藏層大小是一個比較劃算的解決方案。
猜你喜歡