文章目錄
-
第10章 人工神經網絡簡介
1 從生物神經元到人工神經元
1.1 生物神經元
1.2 具有神經元的邏輯計算
1.3 感覺器
1.4 多層感覺器和反向傳播
2 用TensorFlow的進階API來訓練MLP
3 使用純TensorFlow訓練DNN
3.1 建構階段
3.2 執行階段
3.3 使用神經網絡
4 微調神經網絡的超參數
4.1 隐藏層的個數
4.2 每個隐藏層中的神經元數
4.3 激活函數
5 練習
1. 從生物神經元到人工神經元
我們從鳥類那裡學會了飛翔,有很多發明都是
被自然所啟發
。這麼說來看看
大腦
的組成,啟發我們建構智能機器,就合乎情理了。這就是
人工神經網絡ANN(Artificial Neural Network)
的根本來源。
人工神經網絡是深度學習的
核心中的核心
。它們通用、強大、可擴充,使它成為解決複雜機器學習任務的理想選擇。比如,數以億計的圖檔分類,擊敗世界冠軍的AlphaGo。
1.1 生物神經元
它是在動物的大腦皮層中的非凡細胞。生物神經元通過這些突出接受從其他細胞發來的很短的電脈沖,即信号。當一個神經元在一定時間内收到足夠多的信号,就會發出自己的信号。
超級複雜的計算可以通過這些簡單的神經元來完成。
1.2 具有神經元的邏輯計算
生物神經元的簡化模型,
人工神經元:它有一個或多個二進制 (開\關) 輸入 和 一個輸出。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9UleNdXVU1EMJRVT3V1MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0QTN0ITOzQTM1ITMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
邏輯非
的應用場景,比如dropout。
1.3 感覺器Perceptron
感覺器是最簡單ANN架構。它是基于一個線性門檻值單元(LTU,Linear Threshold Unit)的人工神經元。
分析上圖,x和w沒啥好說的,就是普通的數字或向量,那麼做變換的其實是神經元,它做了哪些操作? ① 權重求和 z = w t ⋅ x z = w^t \cdot x z=wt⋅x;② 經過階躍函數進一步變換函數空間 step(z)。③ 最後的輸出: h w ( x ) = s t e p ( w t ⋅ x ) 。 h_w(x) = step(w^t \cdot x)。 hw(x)=step(wt⋅x)。
單個LTU結構可以用于線性二值分類,輸出為一個機率,如果該機率超過了門檻值就是正,否則為負(跟LR和SVM一樣)
感覺器Perceptron
就是多個LTU單元的單層全連接配接NN結構。
注意:X1、X2 是特征 特征 , 1為偏差特征,永遠為1!!!
總的來看,
上面這個感覺器結構做了什麼?
它将一個執行個體(
x1 x2是單個執行個體的2個特征
)分為3個不同的二進制類,是以它是
多輸出分類器
。當然也可以做成單輸出分類器,在後面再加一層單個LTU單元的輸出就好了,此時擁有2層的感覺器叫
多層感覺器
(MLP, Multi-Layer Perceptron)。
感覺器訓練算法很大程度上受hebb’s定律的啟發,同時處于激活狀态的細胞是會連在一起的。這個規律後來被稱為
hebb定律
(又叫hebbinan學習):當2個神經元有相同的輸出時,它們之間的連結權重就會增強。perceptron就是根據這個規則的變體來訓練。
感覺器
訓練算法 (權重更新)
:
w i j n e x t s t e p = w i j + η ( y ^ j − y j ) x i w_{ij}^{next step} = w_{ij} + \eta(\hat y_j-y_j)x_i wijnextstep=wij+η(y^j−yj)xi
w i j w_{ij} wij是第i個輸入神經元和第j個輸出神經元的連結權重;
x i x_i xi是目前訓練執行個體的第i個輸入值;
y ^ j \hat y_j y^j是目前訓練執行個體的第j個輸出神經元的輸出,即預測值;
y i y_i yi是目前訓練執行個體的第j個輸出神經元的目标輸出,即真實值;
η \eta η 是學習率。
注意:
感覺器的每個輸出神經元的
決策邊界是線性的
,是以無法學習複雜的模式。(這點跟LR一樣)
sklearn實作了一個單一LTU忘了的Perceptron類。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.linear_model import Perceptron
iris = load_iris()
X = iris.data[:, (2, 3)] # petal length, petal width
y = (iris.target == 0).astype(np.int)
per_clf = Perceptron(max_iter=100, tol=-np.infty, random_state=42)
per_clf.fit(X, y)
y_pred = per_clf.predict([[2, 0.5]])
a = -per_clf.coef_[0][0] / per_clf.coef_[0][1] #前兩個系數相除
b = -per_clf.intercept_ / per_clf.coef_[0][1] #截距 除以 系數
axes = [0, 5, 0, 2]
x0, x1 = np.meshgrid(
np.linspace(axes[0], axes[1], 500).reshape(-1, 1),# 0 ~ 5之間産生500個等差數列的數
np.linspace(axes[2], axes[3], 200).reshape(-1, 1),# 0 ~ 2之間産生200個等差數列的數
)
#生成測試執行個體
X_new = np.c_[x0.ravel(), x1.ravel()] # 按列合并
y_predict = per_clf.predict(X_new)
zz = y_predict.reshape(x0.shape)
plt.figure(figsize=(10, 4))
plt.plot(X[y==0, 0], X[y==0, 1], "bs", label="Not Iris-Setosa")
plt.plot(X[y==1, 0], X[y==1, 1], "yo", label="Iris-Setosa")
#畫出決策邊界
plt.plot([axes[0], axes[1]], [a * axes[0] + b, a * axes[1] + b], "k-", linewidth=3)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#9898ff', '#fafab0'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)#正負樣本區域 展示不同顔色
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="lower right", fontsize=14)
plt.axis(axes)
# save_fig("perceptron_iris_plot")
plt.show()
注意
:感覺器隻能根據一個固定的門檻值來做預測,而不是像LR輸出一個機率,是以從靈活方面來說應該使用LR而不是Perception。
1.4 多層感覺器和反向傳播
多層感覺器,就是多個感覺器堆疊起來。
反向傳播的實質其實就是
複合函數求導的鍊式法則
。 反向傳播的
訓練過程
:
① 先正向做一次預測,度量誤差;
② 反向的周遊每個層次來度量每個連接配接的誤差;
③ 微調每個連接配接的權重來降低誤差(梯度下降)。
反向傳播可以合作的
激活函數
,除了邏輯函數sigmoid等外,最流行的是2個:
① 雙曲正切函數 t a n h ( z ) = 2 σ ( 2 z ) − 1 tanh(z)=2 \sigma(2z)-1 tanh(z)=2σ(2z)−1
② ReLU函數 R e L U ( z ) = m a x ( 0 , z ) ReLU(z) = max(0,z) ReLU(z)=max(0,z)
z = np.linspace(-5, 5, 200)
plt.figure(figsize=(11,4))
plt.subplot(121)
plt.plot(z, np.sign(z), "r-", linewidth=1, label="Step")
plt.plot(z, sigmoid(z), "g--", linewidth=2, label="Sigmoid")
plt.plot(z, np.tanh(z), "b-", linewidth=2, label="Tanh")
plt.plot(z, relu(z), "m-.", linewidth=2, label="ReLU")
plt.grid(True)
plt.legend(loc="center right", fontsize=14)
plt.title("Activation functions", fontsize=14)
plt.axis([-5, 5, -1.2, 1.2])
plt.subplot(122)
plt.plot(z, derivative(np.sign, z), "r-", linewidth=1, label="Step")
plt.plot(0, 0, "ro", markersize=5)
plt.plot(0, 0, "rx", markersize=10)
plt.plot(z, derivative(sigmoid, z), "g--", linewidth=2, label="Sigmoid")
plt.plot(z, derivative(np.tanh, z), "b-", linewidth=2, label="Tanh")
plt.plot(z, derivative(relu, z), "m-.", linewidth=2, label="ReLU")
plt.grid(True)
#plt.legend(loc="center right", fontsize=14)
plt.title("Derivatives", fontsize=14)
plt.axis([-5, 5, -0.2, 1.2])
save_fig("activation_functions_plot")
plt.show()
2. 用TensorFlow的進階API來訓練MLP
需要用到tf.contrib包 和 sklearn結合,contrib裡面的東西經常疊代,屬于第三方提供的代碼庫,這裡就不描述了。
3. 使用純TensorFlow訓練DNN
3.1 建構階段
#shuffle分批分桶
def shuffle_batch(X, y, batch_size):
rnd_idx = np.random.permutation(len(X))
n_batches = len(X) // batch_size
for batch_idx in np.array_split(rnd_idx, n_batches):
X_batch, y_batch = X[batch_idx], y[batch_idx]
yield X_batch, y_batch #yield生成器,節省記憶體
n_inputs = 28*28 # MNIST
n_hidden1 = 300 #隐層1的神經元數量
n_hidden2 = 100 #隐層2的神經元數量
n_outputs = 10 #輸出層的神經元數量,對于MNIST為多輸出,0 - 9 共10種數字
reset_graph()
#------------------建構階段 --------------------
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X") #占位符,相當于先定義出來因變量X
y = tf.placeholder(tf.int32, shape=(None), name="y")
#建構nn結構
with tf.name_scope("dnn"):
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1",
activation=tf.nn.relu)
hidden2 = tf.layers.dense(hidden1, n_hidden2, name="hidden2",
activation=tf.nn.relu)
logits = tf.layers.dense(hidden2, n_outputs, name="outputs")
y_proba = tf.nn.softmax(logits)
#定義損失函數
with tf.name_scope("loss"):
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
loss = tf.reduce_mean(xentropy, name="loss")
#定義優化器和最小化損失函數的op
learning_rate = 0.01
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
#定義模型評估
with tf.name_scope("eval"):
correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
3.2 執行階段
#------------------執行階段 --------------------
init = tf.global_variables_initializer() # 定義全局變量初始化器
saver = tf.train.Saver() #定義saver用于儲存模型
n_epochs = 20 #疊代輪次
n_batches = 50 #每個批次的執行個體數量
with tf.Session() as sess:
init.run() #初始化變量
for epoch in range(n_epochs):
for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
sess.run(training_op, feed_dict={X: X_batch, y: y_batch}) #開始訓練
acc_batch = accuracy.eval(feed_dict={X: X_batch, y: y_batch}) #每個批次的訓練集精确率
acc_valid = accuracy.eval(feed_dict={X: X_valid, y: y_valid}) #每個批次的驗證集的精确率
print(epoch, "Batch accuracy:", acc_batch, "Validation accuracy:", acc_valid)
save_path = saver.save(sess, "./my_model_final.ckpt") #儲存模型
當然也可以自定義層結構,其他代碼跟上面一樣:
def neuron_layer(X, n_neurons, name, activation=None):
with tf.name_scope(name):
n_inputs = int(X.get_shape()[1])
stddev = 2 / np.sqrt(n_inputs)
init = tf.truncated_normal((n_inputs, n_neurons), stddev=stddev)
W = tf.Variable(init, name="kernel")
b = tf.Variable(tf.zeros([n_neurons]), name="bias")
Z = tf.matmul(X, W) + b
if activation is not None:
return activation(Z)
else:
return Z
#唯一差別是這裡使用了我們自定義的層結構,而不是dense
with tf.name_scope("dnn"):
hidden1 = neuron_layer(X, n_hidden1, name="hidden1",
activation=tf.nn.relu)
hidden2 = neuron_layer(hidden1, n_hidden2, name="hidden2",
activation=tf.nn.relu)
logits = neuron_layer(hidden2, n_outputs, name="outputs")
3.3 使用神經網絡
前面已經将訓練好的NN儲存成了ckpt檔案,我們可以直接取出來用于預測:
with tf.Session() as sess:
saver.restore(sess, "./my_model_final.ckpt") # or better, use save_path
X_new_scaled = X_test[:20] #這裡需要特征縮放 0 ~ 1
Z = logits.eval(feed_dict={X: X_new_scaled}) # logits為nn最後的輸出節點
y_pred = np.argmax(Z, axis=1) #取出最大值的索引下标,即為預測圖檔
Z
y_pred
4. 微調神經網絡的超參數
有太多超參數需要調整:層數、每層神經元數、每層的激活函數類型、初始化邏輯的權重等等。是以,了解每個超參數的合理取值會很有幫助。
4.1 隐藏層的個數
① 大多數問題可以用一個或兩個隐藏層來處理,此時可以增加神經元的數量。比如,對于MINST資料集,一個隐藏層擁有數百個神經元就可以達到97%的精度,2層可以獲得超過98%的精度。
② 非常複雜的問題,比如大圖檔的分類,語音識别,通常需要數十層的隐藏層,此時每層的神經元數量要非常少。當然他們也需要超大的資料集。
隐藏層多神經元少的目的是為了訓練起來更加快速
。不過,很少會有人從頭建構這樣的網絡:更常見的是
重用
别人訓練好的用來處理類似任務的網絡。
4.2 每個隐藏層中的神經元數( 重要
)
重要
① 對于輸入層和輸出層,由具體任務要求決定,比如MNIST輸出10種數字,輸出層神經元數就是10;
② 對于隐藏層,
經驗
是以漏鬥型來定義其尺寸,每層的神經元數依次減少,原因:許多低級功能可以合并成數量更少的進階功能。
③ 對于以上經驗也不是那麼絕對,可以逐漸增加神經元的數量,直到過拟合。通常來說,通過增加每層的神經元數量比增加層數會産生更多的消耗。
④ 一個
更簡單的方式:
使用更多的層次和神經元,然後提前設定
1 早停
來
避免過拟合
,或者使用
2 dropout
正則化技術。這被稱為 彈力褲 方法。
4.3 激活函數
大多數情況下,可以在隐藏層中使用ReLU激活函數(或其變種)。它比其他激活函數快,因為梯度下降對于大資料值沒有上限,會導緻它無法終止。
對于輸出層,Softmax對于分類任務( 若分類是互斥的) 來說是一個不錯的選擇。
對于回歸任務,完全可以不使用激活函數?
。
5. 練習
在MNIST資料集上訓練一個深度MLP,看看預測準确度能不能超過98%。嘗試一些
額外的功能
(
儲存檢查點,中斷後從檢查點恢複,添加彙總,用tensorboard繪制學習曲線
)
from datetime import datetime
#定義日志路徑
def log_dir(prefix=""):
now = datetime.utcnow().strftime("%Y%m%d%H%M%S")
root_logdir = "tf_logs"
if prefix:
prefix += "-"
name = prefix + "run-" + now
return "{}/{}/".format(root_logdir, name)
n_inputs = 28*28 # MNIST
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10
reset_graph()
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")
with tf.name_scope("dnn"):
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1",
activation=tf.nn.relu)
hidden2 = tf.layers.dense(hidden1, n_hidden2, name="hidden2",
activation=tf.nn.relu)
logits = tf.layers.dense(hidden2, n_outputs, name="outputs")
with tf.name_scope("loss"):
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
loss = tf.reduce_mean(xentropy, name="loss")
loss_summary = tf.summary.scalar('log_loss', loss)
learning_rate = 0.01
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
with tf.name_scope("eval"):
correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
accuracy_summary = tf.summary.scalar('accuracy', accuracy)
init = tf.global_variables_initializer()
saver = tf.train.Saver()
#定義二進制日志檔案writer
file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())
m, n = X_train.shape
# -------------- 執行計算圖--------------------
n_epochs = 10001
batch_size = 50
n_batches = int(np.ceil(m / batch_size))
checkpoint_path = "./tmp/my_deep_mnist_model.ckpt" #第一次訓練時路徑不對
checkpoint_epoch_path = checkpoint_path + ".epoch"
final_model_path = "./my_deep_mnist_model"
best_loss = np.infty
epochs_without_progress = 0
max_epochs_without_progress = 50
with tf.Session() as sess:
if os.path.isfile(checkpoint_epoch_path):
# if the checkpoint file exists, restore the model and load the epoch number
with open(checkpoint_epoch_path, "rb") as f:
start_epoch = int(f.read())
print("Training was interrupted. Continuing at epoch", start_epoch)
saver.restore(sess, checkpoint_path)
else:
start_epoch = 0
sess.run(init)
for epoch in range(start_epoch, n_epochs):
for X_batch, y_batch in shuffle_batch(X_train, y_train, batch_size):
sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
accuracy_val, loss_val, accuracy_summary_str, loss_summary_str = sess.run([accuracy, loss, accuracy_summary, loss_summary], feed_dict={X: X_valid, y: y_valid})
file_writer.add_summary(accuracy_summary_str, epoch)
file_writer.add_summary(loss_summary_str, epoch)
if epoch % 5 == 0:
print("Epoch:", epoch,
"\tValidation accuracy: {:.3f}%".format(accuracy_val * 100),
"\tLoss: {:.5f}".format(loss_val))
#儲存目前模型
saver.save(sess, checkpoint_path)
#儲存目前疊代輪次到.epoch字尾的檔案中
with open(checkpoint_epoch_path, "wb") as f:
f.write(b"%d" % (epoch + 1))
if loss_val < best_loss:
saver.save(sess, final_model_path)
best_loss = loss_val
else:
epochs_without_progress += 5
if epochs_without_progress > max_epochs_without_progress:
print("Early stopping")
break
#模型訓練完成後,删除檢查點檔案
os.remove(checkpoint_epoch_path)