大部分內容整理自《Tensorflow實戰Google深度學習框架》
在前面計算圖、張量、會話、Tensorflow遊樂場的基礎上轉到神經網絡,MLP的神經元,層數,參數之前已經瞭解。從Tensorflow遊樂場模型上課看出,使用神經網絡解決分類問題主要分爲以下4個步驟:
1.提取問題中實現實體的特徵向量作爲神經網絡的輸入,不同的實體可以提取不同的特徵向量。
2.定義神經網絡的結構,並定義如何從神經網絡的輸入得到輸出,這個過程就是神經網絡的前向傳播算法。輸出就是所有輸入的加權和。
3.通過訓練數據來調整神經網絡中參數的值,不同輸入的權重就是我們要調節的參數
4.使用訓練好的數據預測未知的數據
如下是用tensorflow通過變量實現神經網絡的參數並實現前向傳播的過程:
import tensorflow as tf
w1=tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1),name="w1")
w2=tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1),name="w2")
x=tf.constant([[0.7, 0.9]])
a=tf.matmul(x, w1)
y=tf.matmul(a, w2)
sess = tf.Session()
sess.run(w1.initializer)
sess.run(w1.initializer)
print(sess.run(y))
sess.close()
這裏變量初始化也可以如下方式(上面是直接調用每個變量的初始化過程,但是當變量數目增多,變量之間存在依賴關係時,單個的調用方法就麻煩了):
import tensorflow as tf
tf.reset_default_graph()
w1=tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1),name="w1")
w2=tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1),name="w2")
x=tf.constant([[0.7, 0.9]])
a=tf.matmul(x, w1)
y=tf.matmul(a, w2)
init_op=tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init_op)
print(sess.run(y))
writer = tf.summary.FileWriter("D://tensorflow-log//test_tensorboard2", tf.get_default_graph())
writer.close()
"""
輸出所有的變量
"""
for i in tf.trainable_variables():
print(i)
"""
對所屬的計算圖進行輸出,可以發現使用變量、常數、操作、以及默認圖進行的輸出結果是一樣的
"""
print(tf.get_default_graph())
print(a.graph)##可以直接使用某一個操作
print(w1.graph)
print(x.graph)
"""
對張量進行輸出
"""
print(tf.get_default_graph().get_tensor_by_name("w1:0")) ##合理
類似張量,維度(shape)和類型(type)是變量的最重要兩個屬性,構建後類型不可以改變,維度可以改變,如下會報錯:
w1=tf.Variable(tf.random_normal([2,3],stddev=1),name="w1")
w2=tf.Variable(tf.random_normal([2,3],dtype=tf.float64,stddev=1),name="w2")
w1.assign(w2)
如下可以成功執行,前提是validate_shape=False選項要加上
w1=tf.Variable(tf.random_normal([2,3],stddev=1),name="w1")
w2=tf.Variable(tf.random_normal([2,2],stddev=1),name="w2")
tf.assign(w1,w2,validate_shape=False)
設置神經網絡參數的過程就是神經網絡訓練的過程,在神經網絡的優化算法中最常用的方法是反向傳播算法,在每次迭代開始先選取一小部分訓練數據,這一小部分數據叫做一個batch, 可以計算出當前神經網絡模型的預測答案與正確答案之間的差距。基於這個預測值和真實值之間的差距,去更新神經網絡的參數。
如下是placeholfer機制的使用案例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Icecream.Shao
import tensorflow as tf
tf.reset_default_graph()
w1=tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1),name="w1")
w2=tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1),name="w2")
x=tf.placeholder(tf.float32,shape=(1,2),name="input")
a=tf.matmul(x, w1)
y=tf.matmul(a, w2)
sess=tf.Session()
init_op=tf.initialize_all_variables()
sess.run(init_op)
print(sess.run(y,feed_dict={x:[[0.7,0.9]]}))
然後第3章裏示例了一個完整的樣例
batch size爲8,128個樣本,用了placeholder,定義了前向傳播算法(只有一層隱藏層),並沒有加入激活函數和偏置項,訓練了5000次,定義了損失函數。可以認爲是一個線性的神經網絡
接着講用激活函數實現去線性化(加上激活函數和偏置項),激活函數有ReLU, Sigmoid,tanh函數
接着重點講解了損失函數,提到了交叉熵是常用的評判方法之一。softmax迴歸將神經網絡的輸出變成一個概率分佈。
有了求出的概率分佈後,容易計算出交叉熵,公式爲:
假設某個樣本的正確答案是(1,0,0)
經softmax迴歸之後的預測答案是(0.5,0.4,0.1),那麼交叉熵爲:
H((1,0,0),(0.5,0.4,0.1))=-(1*log0.5+0*log0.4+0*log0.1)~0.3
tf.clip_by_value函數可以限制張量中的數值限制在一個範圍內,可以避免一些運算錯誤,比如log0.
tf.mean函數可以求平均值,求得一個batch平均交叉熵。
tensorflow提供tf.nn.softmax_cross_entropy_with_logits(y,y_)做了封裝,來計算該batch迴歸之後的交叉熵。
又介紹瞭解決迴歸問題最常用的損失函數均方誤差
除了以上介紹的兩個函數外,還可以自定義其它損失函數
接下來重點介紹了神經網絡的優化算法,
(1)用梯度下降算法調整神經網絡中的參數取值
假設初始值爲5,學習率0.3,梯度下降算法優化函數(損失函數的大小)J(x)=x^2
輪數 | 當前輪參數值 | 梯度*學習率 | 更新後參數值 |
1 | 5 | 2*5*0.3=3 | 5-3=2 |
2 | 2 | 2*2*0.3=1.2 | 2-1.2=0.8 |
3 | 0.8 | 2*0.8*0.3=0.48 | 0.8-0.48=0.32 |
4 | 0.32 | 2*0.32*0.3=0.192 | 0.32-0.192=0.128 |
5 | 0.128 | 2*0.128*0.3=0.0768 | 0.128-0.0768=0.0512 |
梯度下降算法並不能保證被優化函數達到全局最優,只有當損失函數爲凸函數時,梯度下降算法才能保證達到全局最優解
一般會綜合梯度下降算法和隨機梯度下降算法,
(2)學習率(初始學習率,衰減係數和衰減速度)
(3)正則化(在損失函數中加入刻畫模型複雜程度的指標),L1正則化和L2正則化
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)
這邊的損失函數由兩部分組成,第一個部分是均方差損失函數,第二個部分是正則化
可以使用tensorflow中提供的集合collection來計算損失函數
mse_loss=tf.reduce_mean(tf.square(y_ - cur_layer))
tf.add_to_collection('losses',mse_loss)
loss = tf.add_n(tf.get_collection('losses'))
最後介紹了滑動平均模型tf.train.ExponentialMovingAverage()
shadow_variable函數爲影子變量,variable爲代更新的變量,decay爲衰減率。
接下來介紹了Mnist數據集,然後給出結合以上所有優化手段訓練只有一個隱藏層的神經網絡MLP
結論:在神經網絡結構的設計上,需要使用激活函數和多層隱藏層,在神經網絡優化時,可以使用指數衰減學習率,加入正則化損失函數,滑動平均模型。由於在MNIST,模型收斂速度很快。
使用所有優化,不用滑動平均,不用正則化,不同指數衰減學習率的正確率到98%多
不用隱藏層,不用激活函數時,只能到92%
接下來講了變量管理。當tf.variable_scope函數使用參數reuse=True生成上下文管理器時,這個上下文管理器內所有的tf.get_variable函數會直接獲取已經創建的變量。如果變量不存在,就會報錯。
當tf.variable_scope函數使用參數reuse=False生成上下文管理器時,tf.get_variable操作將創建新的變量。如果同名的變量已經存在,則tf.get_variable函數將報錯。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Icecream.Shao
import tensorflow as tf
with tf.variable_scope("foo"):
v=tf.get_variable("v",[1],initializer=tf.constant_initializer(1.0))
#如下語句
with tf.variable_scope("foo"):
v=tf.get_variable("v",[1])
with tf.variable_scope("foo",reuse=True):
v1 = tf.get_variable("v",[1])
print(v==v1)
#如下語句會報錯
with tf.variable_scope("bar",reuse=True):
v=tf.get_variable("v",[1])
tf.variable_scope函數除了可以控制tf.get_variable執行的功能之外,這個函數也提供了一個管理變量命名空間的方式。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Icecream.Shao
import tensorflow as tf
v1=tf.get_variable("v",[1])
print(v1.name)
with tf.variable_scope("foo"):
v2=tf.get_variable("v",[1])
print(v2.name)
with tf.variable_scope("foo"):
with tf.variable_scope("bar"):
v3=tf.get_variable("v",[1])
print(v3.name)
v4 = tf.get_variable("v1",[1])
print(v4.name)
with tf.variable_scope("",reuse=True):
v5=tf.get_variable("foo/bar/v")
print(v5==v3)
v6=tf.get_variable("foo/v1")
print(v6==v4)
with tf.Session() as sess:
#sess.run(tf.global_variables_initializer())
tf.initialize_all_variables().run()
print("v1: ", v1.eval())
print("v2: ", v2.eval())
print("v3: ", v3.eval())
print("v4: ", v4.eval())
print("v5: ", v5.eval())
print("v6: ", v6.eval())
結果爲:v1: [-0.61839104]
v2: [0.03291154]
v3: [0.9467553]
v4: [-0.49030578]
v5: [0.9467553]
v6: [-0.49030578]
接下來講了模型持久化,tf.train.Saver()會保存運行tensorflow程序所需要的的全部信息
結合變量管理機制和tensorflow模型持久化機制,介紹了一個tensorflow訓練神經網絡的最佳實踐,將訓練和測試分爲兩個獨立的程序,有三個程序mnist_inference.py, mnist_train.py,mnist_eval.py
mnisy_inference.py程序如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Icecream.Shao
# -*- coding: utf-8 -*-
import tensorflow as tf
# 定義神經網絡結構相關的參數
INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500
# 通過tf.get_variable函數來獲取變量。在訓練神經網絡時會創建這些變量;在測試時會通
# 過保存的模型加載這些變量的取值。而且更加方便的是,因爲可以在變量加載時將滑動平均變
# 量重命名,所以可以直接通過相同的名字在訓練時使用變量自身,而在測試時使用變量的滑動
# 平均值。在這個函數中也會將變量的正則化損失加入到損失集合。
def get_weight_variable(shape, regularizer):
weights = tf.get_variable(
"weights", shape,
initializer=tf.truncated_normal_initializer(stddev=0.1)
)
# 當給出了正則化生成函數時,將當前變量的正則化損失加入名字爲losses的集合。在這裏
# 使用了add_to_collection函數將一個張量加入一個集合,而這個集合的名稱爲losses。
# 這是自定義的集合,不在TensorFlow自動管理的集合列表中。
if regularizer != None:
tf.add_to_collection('losses', regularizer(weights))
return weights
# 定義神經網絡的前向傳播過程
def inference(input_tensor, regularizer):
# 聲明第一層神經網絡的變量並完成前向傳播過程。
with tf.variable_scope('layer1'):
# 這裏通過tf.get_variable或者tf.Variable沒有本質區別,因爲在訓練或者測試
# 中沒有在同一個程序中多次調用這個函數。如果在同一個程序中多次調用,在第一次
# 調用之後需要將reuse參數設置爲True。
weights = get_weight_variable(
[INPUT_NODE, LAYER1_NODE], regularizer
)
biases = tf.get_variable(
"biases", [LAYER1_NODE],
initializer=tf.constant_initializer(0.0)
)
layer1 = tf.nn.relu(tf.matmul(input_tensor, weights)+biases)
# 類似的聲明第二層神經網絡的變量並完成前向傳播過程。
with tf.variable_scope('layer2'):
weights = get_weight_variable(
[LAYER1_NODE, OUTPUT_NODE], regularizer
)
biases = tf.get_variable(
"biases", [OUTPUT_NODE],
initializer=tf.constant_initializer(0.0)
)
layer2 = tf.matmul(layer1, weights) + biases
# 返回最後前向傳播的結果
return layer2
mnist_train.py程序如下:
# -*- coding: utf-8 -*-
import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 加載mnist_inference.py中定義的常量和前向傳播的函數。
import mnist_inference
# 配置神經網絡的參數。
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 10000
MOVING_AVERAGE_DECAY = 0.99
# 模型保存的路徑和文件名
MODEL_SAVE_PATH = "./model/"
MODEL_NAME = "model.ckpt"
def train(mnist):
# 定義輸入輸出placeholder。
x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
# 直接使用mnist_inference.py中定義的前向傳播過程
y = mnist_inference.inference(x, regularizer)
global_step = tf.Variable(0, trainable=False)
# 定義損失函數、學習率、滑動平均操作以及訓練過程
variable_averages = tf.train.ExponentialMovingAverage(
MOVING_AVERAGE_DECAY, global_step
)
variable_averages_op = variable_averages.apply(
tf.trainable_variables()
)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
logits=y, labels=tf.argmax(y_, 1)
)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
mnist.train.num_examples / BATCH_SIZE,
LEARNING_RATE_DECAY
)
train_step = tf.train.GradientDescentOptimizer(learning_rate)\
.minimize(loss, global_step=global_step)
with tf.control_dependencies([train_step, variable_averages_op]):
train_op = tf.no_op(name='train')
# 初始化TensorFlow持久化類
saver = tf.train.Saver()
writer = tf.summary.FileWriter("D:\\tensorflow-log\\test_mlp_true_net", tf.get_default_graph())
writer.close()
with tf.Session() as sess:
tf.global_variables_initializer().run()
# 在訓練過程中不再測試模型在驗證數據上的表現,驗證和測試的過程將會有一個獨
# 立的程序來完成。
for i in range(TRAINING_STEPS):
xs, ys = mnist.train.next_batch(BATCH_SIZE)
_, loss_value, step = sess.run([train_op, loss, global_step],
feed_dict={x: xs, y_: ys})
# 每1000輪保存一次模型
if i % 1000 == 0:
# 輸出當前的訓練情況。這裏只輸出了模型在當前訓練batch上的損失
# 函數大小。通過損失函數的大小可以大概瞭解訓練的情況。在驗證數
# 據集上正確率的信息會有一個單獨的程序來生成
print("After %d training step(s), loss on training "
"batch is %g." % (step, loss_value))
# 保存當前的模型。注意這裏給出了global_step參數,這樣可以讓每個
# 被保存的模型的文件名末尾加上訓練的輪數,比如“model.ckpt-1000”,
# 表示訓練1000輪之後得到的模型。
saver.save(
sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME),
global_step=global_step
)
# 將當前的計算圖輸出到TensorBoard日誌文件。
def main(argv=None):
mnist = input_data.read_data_sets("./data", one_hot=True)
train(mnist)
if __name__ == "__main__":
tf.app.run()
mnist_eval.py程序如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Icecream.Shao
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 加載mnist_inference.py 和mnist_train.py中定義的常量和函數。
import mnist_inference
import mnist_train
# 每10秒加載一次最新的模型,並且在測試數據上測試最新模型的正確率
EVAL_INTERVAL_SECS = 10
def evaluate(mnist):
with tf.Graph().as_default() as g:
# 定義輸入輸出的格式。
x = tf.placeholder(
tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input'
)
y_ = tf.placeholder(
tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input'
)
validate_feed = {x: mnist.validation.images,
y_: mnist.validation.labels}
# 直接通過調用封裝好的函數來計算前向傳播的結果。因爲測試時不關注ze正則化損失的值
# 所以這裏用於計算正則化損失的函數被設置爲None。
y = mnist_inference.inference(x, None)
# 使用前向傳播的結果計算正確率。如果需要對未知的樣例進行分類,那麼使用
# tf.argmax(y,1)就可以得到輸入樣例的預測類別了。
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 通過變量重命名的方式來加載模型,這樣在前向傳播的過程中就不需要調用求滑動平均
# 的函數來獲取平均值了。這樣就可以完全共用mnist_inference.py中定義的
# 前向傳播過程。
variable_averages = tf.train.ExponentialMovingAverage(
mnist_train.MOVING_AVERAGE_DECAY
)
variables_to_restore = variable_averages.variables_to_restore()
saver = tf.train.Saver(variables_to_restore)
# 每隔EVAL_INTERVAL_SECS秒調用一次計算正確率的過程以檢驗訓練過程中正確率的
# 變化。
while True:
with tf.Session() as sess:
# tf.train.get_checkpoint_state函數會通過checkpoint文件自動
# 找到目錄中最新模型的文件名。
ckpt = tf.train.get_checkpoint_state(
mnist_train.MODEL_SAVE_PATH
)
if ckpt and ckpt.model_checkpoint_path:
# 加載模型。
saver.restore(sess, ckpt.model_checkpoint_path)
# 通過文件名得到模型保存時迭代的輪數。
global_step = ckpt.model_checkpoint_path\
.split('/')[-1].split('-')[-1]
accuracy_score = sess.run(accuracy,
feed_dict=validate_feed)
print("After %s training step(s), validation "
"accuracy = %g" % (global_step, accuracy_score))
else:
print("No checkpoint file found")
return
time.sleep(EVAL_INTERVAL_SECS)
def main(argv=None):
mnist = input_data.read_data_sets("./data", one_hot=True)
evaluate(mnist)
if __name__ == "__main__":
tf.app.run()
卷積神經網絡能夠很好的利用圖像的結構信息,在全連接神經網絡中,每相鄰兩層之間的節點都有邊相連,對於卷積神經網絡,相鄰兩層之間只有部分節點相連。tensorflow訓練一個卷積神經網絡和全連接神經網絡沒有任何區別。對於mnist數據集,假設用全連接神經網絡,每一張圖片的大小爲28*28*1,所以輸入有28*28*1個節點,假設第一層隱藏層的節點數爲500個,那麼一個全連接神經網絡有28*28*500+500個參數。假設是cifar-10圖像,大小爲32*32*3,則輸入層有32*32*3個節點,假設隱藏層有500個節點,則有32*32*3*500+500~150萬個參數。
CNN主要有三大特色,分別是局部感知、權重共享和多卷積核。
局部感知就是所說的感受野,實際上就是卷積核和圖像卷積的時候,每次卷積核所覆蓋的像素只是一小部分,是局部特徵,所以說是局部感知。感受野的計算見我之前的博客
權重共享,不同的圖像或者同一張圖像共用一個卷積核,減少重複的卷積核。同一張圖像當中可能會出現相同的特徵,共享卷積核能夠進一步減少權值參數。
多卷積核,一種卷積核代表的是一種特徵,爲獲得更多不同的特徵集合,卷積層會有多個卷積核,生成不同的特徵,這也是爲什麼卷積後的圖片的高,每一個圖片代表不同的特徵,一般輸出單位節點矩陣的深度代表卷積核的個數
共享每一層卷積層中過濾器中的參數可巨幅減少神經網絡上的參數。以cifar-10問題爲例,輸入層矩陣的維度爲32*32*3,假設第一層卷積層使用尺寸爲5*5,深度爲16的過濾器,那麼這個卷積層參數的個數爲5*5*3*16+16=1216個。相比較前面150萬個參數減少很多。
全0填充,一定步長,一定大小的過濾器卷積後的圖像大小計算見我之前博客。
可以自己構建一個參數矩陣去遍歷圖像實現
當中四維矩陣前兩維代表過濾器的尺寸,第三個代表深度,第四個表示過濾器的深度
filter_weight = tf.get_variable('weights',[5,5,3,16],initializer=tf.truncated_normal_initializer(stddev=0.1))
biases=tf.get_variable('biases',[16],initializer=tf.constant_initializer(0.1))
但可以用tf.nn.conv2d來實現卷積層前向傳播的算法,第一個輸入爲當前層的節點矩陣,這個矩陣是四維矩陣,後面三維對應一個節點矩陣,第一維對應一個輸入batch, 第二個參數提供了卷積層的權重,第三個參數爲不同維度上的步長,第一維和最後一維的數字要求一定爲1,最後一個是填充的方法,SAME表示全0填充,VALID表示不添加
conv = tf.nn.conv2d(input,filter_weight,strides=[1,1,1,1],padding='SAME')
bias = tf.nn.bias_add(conv, biases)
actived_conv = tf.nn.relu(bias)
一個卷積神經網絡主要由以下5中結構組成:
輸入層、卷積層、池化層、全連接層、softmax層
pool=tf.nn.max_pool(actived_conv,ksize=[1,3,3,1],strides=[1,2,2,1],padding="SAME")
和卷積層類似,要先傳入四維矩陣,第二個參數爲過濾器的尺寸,第三個參數爲步長,第四個參數和卷積層設置保持一致
接下來分析了經典的卷積網絡模型LeNet-5模型,可以對比我的博客:caffe使用命令行方式訓練預測mnist、cifar10及自己的數據集中caffe實現的LeNet-5結構
並給出了實現LetNet-5模型的tensorflow程序,只需要修改前面的mnist_inference.py即可,在這個程序中使用了dropout方法,dropout可以進一步提升模型可靠性並防止過擬合,dropout過程只在訓練時使用。
後面又介紹了Inception-v3模型
後面又介紹了卷積神經網絡遷移學習的程序示例
圖像數據處理,多線程輸入,循環神經網絡,計算加速。
tensorflow源碼編譯可見我之前的博客win7下VS2015編譯tensorflow源碼教程(在線和離線)及調用配置
不同於caffe,使用tensorflow框架基本還是在python環境下去搭建網絡結構(可結合keras快速搭建),訓練和測試
我的其它博客有介紹如何源碼編譯caffe,並在matlab、python、c++環境下去搭建網絡、訓練網絡、預測圖片
其中keras的.h5模型可以轉化爲tensorflow的pb模型,供c++及python版tensorflow使用。對我而言如果需要用c++環境去訓練和預測圖片,我會優先選擇caffe來做。如果是python環境去訓練和預測圖片,我會優先考慮tensorflow結合keras。caffe也可以輸出網絡結構圖及打印損失率及精度曲線,我的其它博客有介紹。目前來看常用的還是用在python環境下使用tensorflow或者keras去定義網絡結構, 該訓練好的模型可以生成pb文件,直接給c++版的tensorflow去預測。很少去用tensorflow 的c++版去訓練網絡結構。