《神經網絡和深度學習 學習筆記》(二)人工神經網絡簡介

文章目錄

1. 從生物神經元到人工神經元

   我們從鳥類那裏學會了飛翔,有很多發明都是被自然所啓發。這麼說來看看大腦的組成,啓發我們構建智能機器,就合乎情理了。這就是人工神經網絡ANN(Artificial Neural Network)的根本來源。
   人工神經網絡是深度學習的核心中的核心。它們通用、強大、可擴展,使它成爲解決複雜機器學習任務的理想選擇。比如,數以億計的圖片分類,擊敗世界冠軍的AlphaGo。

1.1 生物神經元

   它是在動物的大腦皮層中的非凡細胞。生物神經元通過這些突出接受從其他細胞發來的很短的電脈衝,即信號。當一個神經元在一定時間內收到足夠多的信號,就會發出自己的信號。
   超級複雜的計算可以通過這些簡單的神經元來完成。

1.2 具有神經元的邏輯計算

   生物神經元的簡化模型,人工神經元:它有一個或多個二進制 (開\關) 輸入 和 一個輸出。

在這裏插入圖片描述
在這裏插入圖片描述

   邏輯非的應用場景,比如dropout。

1.3 感知器Perceptron

   感知器是最簡單ANN架構。它是基於一個線性閾值單元(LTU,Linear Threshold Unit)的人工神經元。

在這裏插入圖片描述

   分析上圖,x和w沒啥好說的,就是普通的數字或向量,那麼做變換的其實是神經元,它做了哪些操作? ① 加權求和 z=wtxz = w^t \cdot x;② 經過階躍函數進一步變換函數空間 step(z)。③ 最後的輸出:hw(x)=step(wtx)h_w(x) = step(w^t \cdot 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就是根據這個規則的變體來訓練。
   感知器訓練算法 (權重更新)

wijnextstep=wij+η(y^jyj)xi w_{ij}^{next step} = w_{ij} + \eta(\hat y_j-y_j)x_i

   wijw_{ij}是第i個輸入神經元和第j個輸出神經元的鏈接權重;
   xix_i是當前訓練實例的第i個輸入值;
   y^j\hat y_j是當前訓練實例的第j個輸出神經元的輸出,即預測值;
   yiy_i是當前訓練實例的第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個:
   ① 雙曲正切函數 tanh(z)=2σ(2z)1tanh(z)=2 \sigma(2z)-1
   ② ReLU函數 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)

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章