4.1 深度學(xué)習(xí)與深層神經(jīng)網(wǎng)絡(luò)
假設(shè)一個(gè)模型的輸出 y 和輸入 x 滿足以下關(guān)系,那么這個(gè)模型為線性模型:

因?yàn)榫仃嚦朔M足結(jié)合律,前向傳播算法可整理為:

說(shuō)明前向傳播算法符合線性模型的定義,而線性模型無(wú)法解決非線性問(wèn)題。
在 TensorFlow 游樂(lè)場(chǎng)中,激活函數(shù)選擇線性(Linear),訓(xùn)練 107 輪后的結(jié)果如下圖所示,并不能很好解決分類的問(wèn)題。

選擇另一個(gè)線性可分的數(shù)據(jù)集,線性模型在訓(xùn)練 101 輪后很好的完成了分類問(wèn)題。說(shuō)明線性模型只能解決線性可分問(wèn)題。

選擇線性不可分?jǐn)?shù)據(jù)集和 ReLU 激活函數(shù),訓(xùn)練 100 輪之后,可以看到較好的完成了分類任務(wù)。說(shuō)明了使用非線性模型解決線性不可分問(wèn)題的效果更好。

加入激活函數(shù)和偏置項(xiàng)之后神經(jīng)元的輸出變成了:

以下是幾個(gè)常用的非線性激活函數(shù)的函數(shù)圖像:



以下選擇了一個(gè)能夠模擬異或運(yùn)算的數(shù)據(jù)集,不使用隱藏層,訓(xùn)練500輪的分類效果??梢钥闯鲞@個(gè)感知機(jī)模型無(wú)法將兩種不同顏色的點(diǎn)分開(kāi),也就是說(shuō)感知機(jī)無(wú)法模擬異或運(yùn)算。

而加入隱藏層之后,該模型很好的完成了分類任務(wù),異或問(wèn)題得到了解決。

從以上例子可以看出深層神經(jīng)網(wǎng)絡(luò)實(shí)際上有組合特征提取的功能。這個(gè)特性對(duì)于解決不易提取特征向量的問(wèn)題(比如圖片識(shí)別、語(yǔ)音識(shí)別等)有很大幫助。
4.2 損失函數(shù)定義
分類問(wèn)題希望解決的是將不同的樣本分到事先定義好的類別中。通過(guò)神經(jīng)網(wǎng)絡(luò)解決多分類問(wèn)題最常用的方法是設(shè)置 n 個(gè)輸出節(jié)點(diǎn),其中 n 為類別的個(gè)數(shù)。對(duì)于每一個(gè)樣例,神經(jīng)網(wǎng)絡(luò)可以得到一個(gè) n 維數(shù)組作為輸出結(jié)果。數(shù)組中的每一個(gè)維度對(duì)應(yīng)一個(gè)類別。在理想情況下,如果一個(gè)樣本屬于類別 k,那么這個(gè)類別所對(duì)應(yīng)的輸出節(jié)點(diǎn)的輸出值應(yīng)該為 1,而其他節(jié)點(diǎn)的輸出都為 0。
交叉熵(cross entropy)是常用的評(píng)判一個(gè)輸出向量和期望的向量之間接近程度的損失函數(shù)。交叉熵刻畫(huà)了兩個(gè)概率分布之間的距離。
給定兩個(gè)概率分布 p 和 q,通過(guò) q 來(lái)表示 p 的交叉熵為:

如果將分類問(wèn)題中“一個(gè)樣例屬于一個(gè)類別”看成是一個(gè)概率事件,那么訓(xùn)練數(shù)據(jù)的正確答案就符合一個(gè)概率分布。Softmax 回歸是一個(gè)非常常用的將神經(jīng)網(wǎng)絡(luò)前向傳播得到的結(jié)果變成概率分布。Softmax 回歸本身可以作為一個(gè)學(xué)習(xí)算法來(lái)優(yōu)化分類結(jié)果,但在 TensorFlow 中,Softmax 回歸的參數(shù)被去掉了,它只是一個(gè)額外的處理層,將神經(jīng)網(wǎng)絡(luò)的輸出變成一個(gè)概率分布。
假設(shè)原始的神經(jīng)網(wǎng)絡(luò)的輸出為 y1, y2, ····, yn,那么經(jīng)過(guò) Softmax 回歸處理之后的輸出為:

原始神經(jīng)網(wǎng)絡(luò)的輸出被用作置信度來(lái)生成概率分布,就可以通過(guò)交叉熵來(lái)計(jì)算預(yù)測(cè)的概率分布和真實(shí)答案的概率分布之間的距離。
交叉熵函數(shù)不是對(duì)稱的( H(p, q) ≠ H(q, p) )。當(dāng)交叉熵作為神經(jīng)網(wǎng)絡(luò)的損失函數(shù)時(shí),p 代表的是正確答案,q 代表的是預(yù)測(cè)值。交叉熵值越小,兩個(gè)概率分布越接近。
在計(jì)算交叉熵時(shí),通常會(huì)用到以下幾個(gè)函數(shù):
tf.clip_by_value(t, clip_value_min, clip_value_max, name=None): 輸入張量 t,把 t 中的元素的值都限制在 clip_value_min 和 clip_value_max 之間,小于 clip_value_min 的等于 clip_value_min ,大于 clip_value_max 的等于 clip_value_max ,這樣可以避免一些運(yùn)算錯(cuò)誤(比如 log0 無(wú)效)。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
v = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
with tf.Session() as sess:
print(tf.clip_by_value(v, 2.5, 4.5).eval())

tf.log()函數(shù),對(duì)張量中所有元素依次求對(duì)數(shù):
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
v = tf.constant([[1.0, 2.0, 3.0]])
with tf.Session() as sess:
print(tf.log(v).eval())

* 操作,實(shí)現(xiàn)兩個(gè)矩陣對(duì)應(yīng)元素直接相乘,矩陣乘法需要使用 tf.matmul() 函數(shù),以下代碼給出了兩個(gè)操作的區(qū)別:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
v1 = tf.constant([[1.0, 2.0], [3.0, 4.0]])
v2 = tf.constant([[5.0, 6.0], [7.0, 8.0]])
with tf.Session() as sess:
print((v1 * v2).eval())
print(tf.matmul(v1, v2).eval())

tf.reduce_mean() 函數(shù),用于計(jì)算張量 tensor 沿著指定的數(shù)軸(tensor的某一維度)上的平均值,主要用作降維或者計(jì)算 tensor 的平均值。
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
v = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
with tf.Session() as sess:
print(tf.reduce_mean(v).eval())
print(tf.reduce_mean(v, axis=0).eval())
print(tf.reduce_mean(v, axis=1).eval())

因?yàn)榻徊骒匾话銜?huì)與 Softmax 回歸一起使用,所以 TensorFlow 對(duì)這兩個(gè)功能進(jìn)行了統(tǒng)一封裝,并提供了
tf.nn.softmax_cross_entropy_with_logits() 函數(shù)。比如可以直接通過(guò)以下代碼來(lái)實(shí)現(xiàn)使用了 softmax 回歸之后的交叉熵?fù)p失函數(shù):tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)其中 y 代表了原始神經(jīng)網(wǎng)絡(luò)的輸出結(jié)果,而 y_ 給出了標(biāo)準(zhǔn)答案。
在只有一個(gè)正確答案的分類問(wèn)題中,TensorFlow 提供了
tf.nn.sparse_softmax_cross_entropy_with_logits() 函數(shù)來(lái)進(jìn)一步加速計(jì)算過(guò)程。
與分類問(wèn)題不同,回歸問(wèn)題解決的是對(duì)具體數(shù)值的預(yù)測(cè)。解決回歸問(wèn)題的神經(jīng)網(wǎng)絡(luò)一般只有一個(gè)輸出節(jié)點(diǎn),輸出值就是預(yù)測(cè)值。對(duì)于回歸問(wèn)題,最常用的損失函數(shù)是均方誤差(MSE,mean squared error),定義如下:

其中 yi 為一個(gè) batch 中第 i 個(gè)數(shù)據(jù)的正確答案,而 yi' 為神經(jīng)網(wǎng)絡(luò)給出的預(yù)測(cè)值。以下代碼展示了如何通過(guò) TensorFlow 實(shí)現(xiàn)均方誤差損失函數(shù):
mse = tf.reduce_mean(tf.square(y_ - y))
TensorFlow 不僅支持經(jīng)典的損失函數(shù),還可以優(yōu)化任意的自定義損失函數(shù)。以預(yù)測(cè)商品銷量問(wèn)題為例,一個(gè)商品的成本是 1 元,利潤(rùn)是 10 元。為了最大化預(yù)期利潤(rùn),需要將損失函數(shù)和利潤(rùn)直接聯(lián)系起來(lái)。注意損失函數(shù)定義的是損失,所以要將利潤(rùn)最大化,定義的損失函數(shù)應(yīng)該刻畫(huà)成本或者代價(jià)。以下公式給出了一個(gè)當(dāng)預(yù)測(cè)多于真實(shí)值和預(yù)測(cè)少于真實(shí)值時(shí)有不同損失系數(shù)的損失函數(shù):

在上面的問(wèn)題中,a 等于 10(正確答案多于預(yù)測(cè)答案的代價(jià)), b 等于1(正確答案少于預(yù)測(cè)答案的代價(jià))。在 TensorFlow 中,可以通過(guò)以下代碼來(lái)實(shí)現(xiàn)該損失函數(shù):
loss = tf.reduce_sum(tf.where(tf.greater(v1, v2), (v1 - v2) * a, (v2 - v1) * b))tf.greater() 輸入的是兩個(gè)張量,比較兩個(gè)輸入張量中每一個(gè)元素的大小,并返回比較結(jié)果。tf.where() 函數(shù)有三個(gè)參數(shù):第一個(gè)作為選擇條件依據(jù),當(dāng)選擇條件為 True 時(shí),選擇第二個(gè)參數(shù)中的值,否則使用第三個(gè)參數(shù)中的值。函數(shù)判斷和選擇都是在元素級(jí)別進(jìn)行。以下代碼展示了
tf.where() 函數(shù)和 tf.greater() 函數(shù)的用法:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
v1 = tf.constant([1.0, 2.0, 3.0, 4.0])
v2 = tf.constant([4.0, 3.0, 2.0, 1.0])
with tf.Session() as sess:
print(tf.greater(v1, v2).eval())
print(tf.where(tf.greater(v1, v2), v1, v2).eval())

下面將通過(guò)一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)程序來(lái)講解損失函數(shù)對(duì)模型訓(xùn)練結(jié)果的影響,在下面這個(gè)程序中,實(shí)現(xiàn)了一個(gè)擁有兩個(gè)輸入節(jié)點(diǎn)、一個(gè)輸出節(jié)點(diǎn),沒(méi)有隱藏層的神經(jīng)網(wǎng)絡(luò):
import tensorflow as tf
from numpy.random import RandomState
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
batch_size = 8
# 定義兩個(gè)輸入節(jié)點(diǎn)
x = tf.placeholder(tf.float32, shape=(None, 2), name='x-input')
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')
# 定義一個(gè)單層神經(jīng)網(wǎng)絡(luò)前向傳播過(guò)程
w1 = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w1)
# 定義成本
loss_less = 10
loss_more = 1
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
# 隨機(jī)生成模擬數(shù)據(jù)集
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)
# 設(shè)置回歸的預(yù)測(cè)值為兩個(gè)輸入的和加上隨機(jī)噪音(均值為0的小量,此處為-0.05~0.05的隨機(jī)數(shù))
Y = [[x1 + x2 + rdm.rand() / 10.0 - 0.05] for (x1, x2) in X]
# 訓(xùn)練神經(jīng)網(wǎng)絡(luò)
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
STEPS = 5000
for i in range(STEPS):
start = (i * batch_size) % dataset_size
end = min(start + batch_size, dataset_size)
sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
print(sess.run(w1))

4.3 神經(jīng)網(wǎng)絡(luò)優(yōu)化算法
梯度下降算法(gradient descent)主要用于優(yōu)化單個(gè)參數(shù)的取值,而反向傳播算法(backpropagation)給出了一個(gè)在所有參數(shù)上使用梯度下降算法的高效方式,從而使神經(jīng)網(wǎng)絡(luò)模型在訓(xùn)練數(shù)據(jù)上的損失盡可能小。反向傳播算法是訓(xùn)練神經(jīng)網(wǎng)絡(luò)的核心算法,它可以根據(jù)定義好的損失函數(shù)優(yōu)化神經(jīng)網(wǎng)絡(luò)中參數(shù)的取值,從而使神經(jīng)網(wǎng)絡(luò)模型在訓(xùn)練數(shù)據(jù)集上的損失函數(shù)達(dá)到一個(gè)較小值。
假設(shè)用 θ 表示神經(jīng)網(wǎng)絡(luò)中的參數(shù),J(θ) 表示在給定的參數(shù)取值下,訓(xùn)練數(shù)據(jù)集上損失函數(shù)的大小,那么整個(gè)優(yōu)化過(guò)程可以抽象為尋找一個(gè)參數(shù) θ,使得 J(θ) 最小。因?yàn)槟壳皼](méi)有一個(gè)通用的方法可以對(duì)任意損失函數(shù)直接求解最佳的參數(shù)取值,所以在實(shí)踐中,梯度下降算法是最常用的神經(jīng)網(wǎng)絡(luò)優(yōu)化方法。梯度下降算法會(huì)迭代式更新參數(shù) θ,不斷沿著梯度的反方向讓參數(shù)朝著總損失更小的方向更新。
參數(shù)的梯度可以通過(guò)求偏導(dǎo)的方式計(jì)算,對(duì)于參數(shù) θ,其梯度為 :?J(θ)/?θ。
定義學(xué)習(xí)率 η (learning rate)來(lái)定義每次參數(shù)更新的幅度。通過(guò)參數(shù)的梯度和學(xué)習(xí)率,參數(shù)更新的公式為:

神經(jīng)網(wǎng)絡(luò)的優(yōu)化過(guò)程可以分為兩個(gè)階段,第一個(gè)階段先通過(guò)前向傳播算法計(jì)算得到預(yù)測(cè)值,并將預(yù)測(cè)值和真實(shí)值作對(duì)比得出兩者之間的差距。然后在第二個(gè)階段通過(guò)反向傳播算法計(jì)算損失函數(shù)對(duì)每一個(gè)參數(shù)的梯度,再根據(jù)梯度和學(xué)習(xí)率使用梯度下降算法更新每一個(gè)參數(shù)。
梯度下降算法并不能保證被優(yōu)化的函數(shù)達(dá)到全局最優(yōu)解。只有當(dāng)損失函數(shù)為凸函數(shù)時(shí),梯度下降算法才能保證達(dá)到全局最優(yōu)解。
除了不一定能達(dá)到全局最優(yōu),梯度下降算法的另一個(gè)問(wèn)題就是計(jì)算時(shí)間太長(zhǎng)。因?yàn)橐谌坑?xùn)練數(shù)據(jù)上最小化損失,所以損失函數(shù) J(θ) 是在所有訓(xùn)練數(shù)據(jù)上的損失和。這樣在每一輪迭代中都需要計(jì)算在全部訓(xùn)練數(shù)據(jù)上的損失函數(shù)。
為了加速訓(xùn)練過(guò)程,可以使用隨機(jī)梯度下降算法(stochastic gradient descent)。這個(gè)算法在每一輪迭代中,隨機(jī)優(yōu)化某一條訓(xùn)練數(shù)據(jù)的損失函數(shù),大大加快每一輪參數(shù)更新的速度。缺點(diǎn)是:在某一條數(shù)據(jù)上損失函數(shù)更小并不代表在全部數(shù)據(jù)上損失函數(shù)更小,于是使用隨機(jī)梯度下降優(yōu)化得到的神經(jīng)網(wǎng)絡(luò)甚至可能無(wú)法達(dá)到局部最優(yōu)。
綜合梯度下降算法和隨機(jī)梯度下降算法的優(yōu)缺點(diǎn),在實(shí)際應(yīng)用中采用每次計(jì)算一小部分訓(xùn)練數(shù)據(jù)的損失函數(shù)的方法。這一小部分訓(xùn)練數(shù)據(jù)被稱之為一個(gè) batch。通過(guò)矩陣運(yùn)算,每次在一個(gè) batch 上優(yōu)化神經(jīng)網(wǎng)絡(luò)的參數(shù)并不會(huì)比單個(gè)數(shù)據(jù)慢太多。另外,每次使用一個(gè) batch 可以大大減小收斂所需要的迭代次數(shù),同時(shí)可以使收斂的結(jié)果更加接近梯度下降的效果。以下代碼給出了在 TensorFlow 中如何實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)的訓(xùn)練過(guò)程:
batch_size = n
# 每次讀取小部分訓(xùn)練數(shù)據(jù)
x = tf.placeholder(tf.float32, shape=(batch_size, 2), name='x-input')
y_ = tf.placeholder(tf.float32, shape=(batch_size, 1), name='y-input')
# 定義神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)和優(yōu)化算法
loss = ...
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
# 訓(xùn)練神經(jīng)網(wǎng)絡(luò)
with tf.Session() as sess:
# 參數(shù)初始化
...
# 迭代的更新參數(shù)
for i in range(STEPS):
# 準(zhǔn)備batch_size個(gè)訓(xùn)練數(shù)據(jù)
current_X, current_Y = ...
sess.run(train_step, feed_dict={x: current_X, y_: current_Y})
4.4 神經(jīng)網(wǎng)絡(luò)進(jìn)一步優(yōu)化
為了解決學(xué)習(xí)率的問(wèn)題,TensorFlow 提供了一種靈活的學(xué)習(xí)率設(shè)置方法——指數(shù)衰減法(exponential attenuation/decay)。tf.train.exponential_decay() 函數(shù)實(shí)現(xiàn)了指數(shù)衰減學(xué)習(xí)率。通過(guò)這個(gè)函數(shù),可以先使用較大的學(xué)習(xí)率來(lái)快速得到一個(gè)比較優(yōu)的解,然后隨著迭代的繼續(xù)逐步減小學(xué)習(xí)率,使得模型在訓(xùn)練后期更加穩(wěn)定。exponential_decay 函數(shù)會(huì)指數(shù)級(jí)地減小學(xué)習(xí)率,它實(shí)現(xiàn)了以下代碼的功能:
decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
decay_rate 為衰減系數(shù),decay_steps 為衰減速度,通常代表了完整的使用一遍訓(xùn)練數(shù)據(jù)所需要的迭代輪數(shù),也就是總訓(xùn)練樣本數(shù)除以每個(gè) batch 中的訓(xùn)練樣本數(shù)。這種設(shè)置的使用場(chǎng)景是每完整過(guò)完一遍訓(xùn)練數(shù)據(jù),學(xué)習(xí)率就減小一次。這可以使得訓(xùn)練數(shù)據(jù)集中的所有數(shù)據(jù)對(duì)模型訓(xùn)練有相等的作用。下面給出的示例代碼展示了如何在 TensorFlow 中使用 tf.train.exponential_decay() 函數(shù):
global_step = tf.Variable(0)
# 通過(guò)exponential_decay函數(shù)生成學(xué)習(xí)率
learning_rate = tf.train.exponential_decay(0.1, global_step, 100, 0.96, staircase=True)
# 使用指數(shù)衰減的學(xué)習(xí)率
learning_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
正則化(regularizaton)是非常常用的避免過(guò)擬合(overfitting)的方法。正則化的思想是在損失函數(shù)中加入刻畫(huà)模型復(fù)雜程度的指標(biāo)。假設(shè)用于刻畫(huà)模型在訓(xùn)練數(shù)據(jù)上表現(xiàn)的損失函數(shù)為 J(θ),那么在優(yōu)化時(shí)不是直接優(yōu)化 J(θ),而是優(yōu)化 J(θ)+λR(w)。其中 R(w) 刻畫(huà)的是模型的復(fù)雜程度,而 λ 表示模型復(fù)雜損失在總損失中的比例。這里 θ 表示的是一個(gè)神經(jīng)網(wǎng)絡(luò)中的所有參數(shù),包括邊上的權(quán)重 w 和偏置項(xiàng) b。一般來(lái)說(shuō)模型復(fù)雜度只由權(quán)重 w 決定。
常用的刻畫(huà)模型復(fù)雜度的函數(shù) R(w) 有兩種,一種是 L1 正則化,計(jì)算公式是:

另一種是 L2 正則化,計(jì)算公式是:

無(wú)論是哪種正則化方式,基本的思想都是希望通過(guò)限制權(quán)重的大小,使得模型不能任意擬合訓(xùn)練數(shù)據(jù)中的隨機(jī)噪音。
L1 正則化會(huì)讓參數(shù)變得更稀疏,可以達(dá)到類似特征選取的方式。L1 正則化的計(jì)算公式不可導(dǎo),而 L2 正則化公式可導(dǎo)。因?yàn)樵趦?yōu)化時(shí)需要計(jì)算損失函數(shù)的偏導(dǎo)數(shù),所有對(duì)含有 L2 正則化損失函數(shù)的優(yōu)化要更加簡(jiǎn)潔。
在實(shí)踐中,也可以將 L1 正則化和 L2 正則化同時(shí)使用:

以下代碼給出了一個(gè)簡(jiǎn)單的帶 L2 正則化的損失函數(shù)定義:
w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w)
loss = tf.reduce_mean(tf.square(y_ -y)) + tf.contrib.layers.l2_regularizer(lambda)(w)
loss 為定義的損失函數(shù),由刻畫(huà)模型在訓(xùn)練數(shù)據(jù)上表現(xiàn)的均方誤差損失函數(shù)和防止模型過(guò)度模擬訓(xùn)練數(shù)據(jù)中的隨機(jī)噪音的 L2 正則化組成。lambda 參數(shù)表示了正則化項(xiàng)的權(quán)重,也就是公式 J(θ)+λR(w) 中的 λ。w 為需要計(jì)算正則化損失的參數(shù)。TensorFlow 提供了 tf.contrib.layers.l2_regularizer() 函數(shù),它可以返回一個(gè)函數(shù),這個(gè)函數(shù)可以計(jì)算一個(gè)給定參數(shù)的 L2 正則化項(xiàng)的值。類似的,tf.contrib.layers.l1_regularizer 可以計(jì)算 L1 正則化項(xiàng)的值。以下代碼給出了使用這兩個(gè)函數(shù)的樣例:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
weights = tf.constant([[1.0, -2.0], [-3.0, 4.0]])
with tf.Session() as sess:
print(sess.run(tf.contrib.layers.l1_regularizer(.5)(weights)))
print(sess.run(tf.contrib.layers.l2_regularizer(.5)(weights)))

當(dāng)網(wǎng)絡(luò)結(jié)構(gòu)復(fù)雜之后定義網(wǎng)絡(luò)結(jié)構(gòu)的部分和計(jì)算損失函數(shù)的部分可能不在同一個(gè)函數(shù)中,這樣通過(guò)變量這種方式計(jì)算損失函數(shù)就不方便。為了解決這個(gè)問(wèn)題,可以使用 TensorFlow 中提供的集合(collection)。它可以在一個(gè)計(jì)算圖中保存一組實(shí)體。以下代碼給出了通過(guò)集合計(jì)算一個(gè) 5 層神經(jīng)網(wǎng)絡(luò)帶 L2 正則化的損失函數(shù)的計(jì)算方法:
import tensorflow as tf
from numpy.random import RandomState
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 獲取一層神經(jīng)網(wǎng)絡(luò)邊上的權(quán)重并將該權(quán)重的L2正則化損失加入集合中
def get_weight(shape, Lambda):
# 生成一個(gè)變量
var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
# 加入集合
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(Lambda)(var))
# 返回生成的變量
return var
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))
batch_size = 8
# 定義了每一層網(wǎng)絡(luò)中節(jié)點(diǎn)的個(gè)數(shù)
layer_dimension = [2, 10, 10, 10, 1]
# 定義神經(jīng)網(wǎng)絡(luò)的層數(shù)
n_layers = len(layer_dimension)
# 定義變量,這個(gè)變量維護(hù)前向傳播時(shí)最深層的節(jié)點(diǎn),開(kāi)始的時(shí)候是輸入層
cur_layer = x
# 當(dāng)前層的節(jié)點(diǎn)個(gè)數(shù)
in_dimension = layer_dimension[0]
for i in range(1, n_layers):
# layer_dimension[i]為下一層的節(jié)點(diǎn)個(gè)數(shù)
out_dimension = layer_dimension[i]
# 生成當(dāng)前層中權(quán)重的變量并將L2正則化損失加入集合
weight = get_weight([in_dimension, out_dimension], 0.001)
bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))
# 使用ReLU激活函數(shù)
cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias)
# 將下一層的節(jié)點(diǎn)個(gè)數(shù)更新為當(dāng)前層節(jié)點(diǎn)個(gè)數(shù)
in_dimension = layer_dimension[i]
# 計(jì)算損失函數(shù)
mse_loss = tf.reduce_mean(tf.square(y_ - cur_layer))
# 將均方誤差損失函數(shù)加入損失集合
tf.add_to_collection('losses', mse_loss)
# 返回列表
loss = tf.add_n(tf.get_collection('losses'))
滑動(dòng)平均模型(moving average)可以使模型在測(cè)試數(shù)據(jù)上更健壯(robust)。在采用隨機(jī)梯度下降算法訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí),使用滑動(dòng)平均模型在很多應(yīng)用中都可以在一定程度上提高最終模型在測(cè)試數(shù)據(jù)上的表現(xiàn)。
TensorFlow 提供了 tf.train.ExponentialMovingAverage 來(lái)實(shí)現(xiàn)滑動(dòng)平均模型。在初始化 ExponentialMovingAverage 時(shí),需要提供一個(gè)衰減率(decay)。這個(gè)衰減率將用于控制模型更新的速度。ExponentialMovingAverage 對(duì)每一個(gè)變量會(huì)維護(hù)一個(gè)影子變量(shadow variable),這個(gè)影子變量的初始值就是對(duì)應(yīng)變量的初始值,而每次運(yùn)行變量更新時(shí),影子變量的值會(huì)更新為:
shadow_variable = decay × shadow_variable + (1 - decay) × variable
decay 決定了模型更新的速度,decay 越大模型越趨于穩(wěn)定。在實(shí)際應(yīng)用中,decay 一般會(huì)設(shè)成非常接近 1 的數(shù)(比如 0.999 或 0.9999)。
如果在 ExponentialMovingAverage 初始化的時(shí)候提供了 num_updates 參數(shù),那么每次使用的衰減率將是:

下面代碼解釋了
ExponentialMovingAverage 是如何被使用的:
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
# 定義一個(gè)變量用于計(jì)算滑動(dòng)平均,初始值為0
v1 = tf.Variable(0, dtype=tf.float32)
# step變量模擬神經(jīng)網(wǎng)絡(luò)中迭代的輪數(shù),用于動(dòng)態(tài)控制衰減率
step = tf.Variable(0, trainable=False)
# 定義滑動(dòng)平均的類,初始化時(shí)給定衰減率(0.99)和控制衰減率的變量step
ema = tf.train.ExponentialMovingAverage(0.99, step)
# 定義更新滑動(dòng)變量的操作
maintain_averages_op = ema.apply([v1])
with tf.Session() as sess:
# 初始化變量
init_op = tf.global_variables_initializer()
sess.run(init_op)
# 通過(guò)ema.average(v1)獲取滑動(dòng)平均之后變量的取值
print(sess.run([v1, ema.average(v1)]))
# 更新v1的值到5
sess.run(tf.assign(v1, 5))
# 更新v1的滑動(dòng)平均值,衰減率為min{0.99, (1+step)/(10+step)=0.1}=0.1
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# 更新step的值為0
sess.run(tf.assign(step, 10000))
# 更新v1的值為10
sess.run(tf.assign(v1, 10))
# 更新v1的滑動(dòng)平均值,衰減率為min{0.99, (1+step)/(10+step)≈0.999}=0.99
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
# 再次更新滑動(dòng)平均值
sess.run(maintain_averages_op)
print(sess.run([v1, ema.average(v1)]))
