代碼:tensorflow/g3doc/tutorials/mnist/
本篇教程的目的,是向大家展示如何利用TensorFlow使用(經(jīng)典)MNIST數(shù)據(jù)集訓(xùn)練并評(píng)估一個(gè)用于識(shí)別手寫數(shù)字的簡(jiǎn)易前饋神經(jīng)網(wǎng)絡(luò)(feed-forward neural network)。我們的目標(biāo)讀者,是有興趣使用TensorFlow的資深機(jī)器學(xué)習(xí)人士。
因此,撰寫該系列教程并不是為了教大家機(jī)器學(xué)習(xí)領(lǐng)域的基礎(chǔ)知識(shí)。
在學(xué)習(xí)本教程之前,請(qǐng)確保您已按照安裝TensorFlow教程中的要求,完成了安裝。
本教程引用如下文件:
文件 | 目的 |
---|---|
mnist.py | 構(gòu)建一個(gè)完全連接(fully connected)的MINST模型所需的代碼。 |
fully_connected_feed.py | 利用下載的數(shù)據(jù)集訓(xùn)練構(gòu)建好的MNIST模型的主要代碼,以數(shù)據(jù)反饋?zhàn)值洌╢eed dictionary)的形式作為輸入模型。 |
只需要直接運(yùn)行fully_connected_feed.py
文件,就可以開始訓(xùn)練:
python fully_connected_feed.py
MNIST是機(jī)器學(xué)習(xí)領(lǐng)域的一個(gè)經(jīng)典問題,指的是讓機(jī)器查看一系列大小為28x28像素的手寫數(shù)字灰度圖像,并判斷這些圖像代表0-9中的哪一個(gè)數(shù)字。
更多相關(guān)信息,請(qǐng)查閱Yann LeCun網(wǎng)站中關(guān)于MNIST的介紹 或者Chris Olah對(duì)MNIST的可視化探索。
在run_training()
方法的一開始,input_data.read_data_sets()
函數(shù)會(huì)確保你的本地訓(xùn)練文件夾中,已經(jīng)下載了正確的數(shù)據(jù),然后將這些數(shù)據(jù)解壓并返回一個(gè)含有DataSet
實(shí)例的字典。
data_sets = input_data.read_data_sets(FLAGS.train_dir, FLAGS.fake_data)
注意:fake_data
標(biāo)記是用于單元測(cè)試的,讀者可以不必理會(huì)。
數(shù)據(jù)集 | 目的 |
---|---|
data_sets.train | 55000個(gè)圖像和標(biāo)簽(labels),作為主要訓(xùn)練集。 |
data_sets.validation | 5000個(gè)圖像和標(biāo)簽,用于迭代驗(yàn)證訓(xùn)練準(zhǔn)確度。 |
data_sets.test | 10000個(gè)圖像和標(biāo)簽,用于最終測(cè)試訓(xùn)練準(zhǔn)確度(trained accuracy)。 |
了解更多數(shù)據(jù)有關(guān)信息,請(qǐng)查閱此系列教程的數(shù)據(jù)下載 部分.
placeholder_inputs()
函數(shù)將生成兩個(gè)tf.placeholder
操作,定義傳入圖表中的shape參數(shù),shape參數(shù)中包括batch_size
值,后續(xù)還會(huì)將實(shí)際的訓(xùn)練用例傳入圖表。
images_placeholder = tf.placeholder(tf.float32, shape=(batch_size, IMAGE_PIXELS)) labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))
在訓(xùn)練循環(huán)(training loop)的后續(xù)步驟中,傳入的整個(gè)圖像和標(biāo)簽數(shù)據(jù)集會(huì)被切片,以符合每一個(gè)操作所設(shè)置的batch_size
值,占位符操作將會(huì)填補(bǔ)以符合這個(gè)batch_size
值。然后使用feed_dict
參數(shù),將數(shù)據(jù)傳入sess.run()
函數(shù)。
在為數(shù)據(jù)創(chuàng)建占位符之后,就可以運(yùn)行mnist.py
文件,經(jīng)過三階段的模式函數(shù)操作:inference()
, loss()
,和training()
。圖表就構(gòu)建完成了。
1.inference()
—— 盡可能地構(gòu)建好圖表,滿足促使神經(jīng)網(wǎng)絡(luò)向前反饋并做出預(yù)測(cè)的要求。
2.loss()
—— 往inference圖表中添加生成損失(loss)所需要的操作(ops)。
3.training()
—— 往損失圖表中添加計(jì)算并應(yīng)用梯度(gradients)所需的操作。
inference()
函數(shù)會(huì)盡可能地構(gòu)建圖表,做到返回包含了預(yù)測(cè)結(jié)果(output prediction)的Tensor。
它接受圖像占位符為輸入,在此基礎(chǔ)上借助ReLu(Rectified Linear Units)激活函數(shù),構(gòu)建一對(duì)完全連接層(layers),以及一個(gè)有著十個(gè)節(jié)點(diǎn)(node)、指明了輸出logtis模型的線性層。
每一層都創(chuàng)建于一個(gè)唯一的tf.name_scope
之下,創(chuàng)建于該作用域之下的所有元素都將帶有其前綴。
with tf.name_scope('hidden1') as scope:
在定義的作用域中,每一層所使用的權(quán)重和偏差都在tf.Variable
實(shí)例中生成,并且包含了各自期望的shape。
weights = tf.Variable( tf.truncated_normal([IMAGE_PIXELS, hidden1_units], stddev=1.0 / math.sqrt(float(IMAGE_PIXELS))), name='weights') biases = tf.Variable(tf.zeros([hidden1_units]), name='biases')
例如,當(dāng)這些層是在hidden1
作用域下生成時(shí),賦予權(quán)重變量的獨(dú)特名稱將會(huì)是"hidden1/weights
"。
每個(gè)變量在構(gòu)建時(shí),都會(huì)獲得初始化操作(initializer ops)。
在這種最常見的情況下,通過tf.truncated_normal
函數(shù)初始化權(quán)重變量,給賦予的shape則是一個(gè)二維tensor,其中第一個(gè)維度代表該層中權(quán)重變量所連接(connect from)的單元數(shù)量,第二個(gè)維度代表該層中權(quán)重變量所連接到的(connect to)單元數(shù)量。對(duì)于名叫hidden1
的第一層,相應(yīng)的維度則是[IMAGE_PIXELS, hidden1_units]
,因?yàn)闄?quán)重變量將圖像輸入連接到了hidden1
層。tf.truncated_normal
初始函數(shù)將根據(jù)所得到的均值和標(biāo)準(zhǔn)差,生成一個(gè)隨機(jī)分布。
然后,通過tf.zeros
函數(shù)初始化偏差變量(biases),確保所有偏差的起始值都是0,而它們的shape則是其在該層中所接到的(connect to)單元數(shù)量。
圖表的三個(gè)主要操作,分別是兩個(gè)tf.nn.relu
操作,它們中嵌入了隱藏層所需的tf.matmul
;以及l(fā)ogits模型所需的另外一個(gè)tf.matmul
。三者依次生成,各自的tf.Variable
實(shí)例則與輸入占位符或下一層的輸出tensor所連接。
hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases)
hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)
logits = tf.matmul(hidden2, weights) + biases
最后,程序會(huì)返回包含了輸出結(jié)果的logits
Tensor。
loss()
函數(shù)通過添加所需的損失操作,進(jìn)一步構(gòu)建圖表。
首先,labels_placeholer
中的值,將被編碼為一個(gè)含有1-hot values的Tensor。例如,如果類標(biāo)識(shí)符為“3”,那么該值就會(huì)被轉(zhuǎn)換為:[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
batch_size = tf.size(labels) labels = tf.expand_dims(labels, 1) indices = tf.expand_dims(tf.range(0, batch_size, 1), 1) concated = tf.concat(1, [indices, labels]) onehot_labels = tf.sparse_to_dense( concated, tf.pack([batch_size, NUM_CLASSES]), 1.0, 0.0)
之后,又添加一個(gè)tf.nn.softmax_cross_entropy_with_logits
操作,用來比較inference()
函數(shù)與1-hot標(biāo)簽所輸出的logits Tensor。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits, onehot_labels, name='xentropy')
然后,使用tf.reduce_mean
函數(shù),計(jì)算batch維度(第一維度)下交叉熵(cross entropy)的平均值,將將該值作為總損失。
loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')
最后,程序會(huì)返回包含了損失值的Tensor。
注意:交叉熵是信息理論中的概念,可以讓我們描述如果基于已有事實(shí),相信神經(jīng)網(wǎng)絡(luò)所做的推測(cè)最壞會(huì)導(dǎo)致什么結(jié)果。更多詳情,請(qǐng)查閱博文《可視化信息理論》(http://colah.github.io/posts/2015-09-Visual-Information/)
training()
函數(shù)添加了通過梯度下降(gradient descent)將損失最小化所需的操作。
首先,該函數(shù)從loss()
函數(shù)中獲取損失Tensor,將其交給tf.scalar_summary
,后者在與SummaryWriter
(見下文)配合使用時(shí),可以向事件文件(events file)中生成匯總值(summary values)。在本篇教程中,每次寫入?yún)R總值時(shí),它都會(huì)釋放損失Tensor的當(dāng)前值(snapshot value)。
tf.scalar_summary(loss.op.name, loss)
接下來,我們實(shí)例化一個(gè)tf.train.GradientDescentOptimizer
,負(fù)責(zé)按照所要求的學(xué)習(xí)效率(learning rate)應(yīng)用梯度下降法(gradients)。
optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate)
之后,我們生成一個(gè)變量用于保存全局訓(xùn)練步驟(global training step)的數(shù)值,并使用minimize()
函數(shù)更新系統(tǒng)中的三角權(quán)重(triangle weights)、增加全局步驟的操作。根據(jù)慣例,這個(gè)操作被稱為 train_op
,是TensorFlow會(huì)話(session)誘發(fā)一個(gè)完整訓(xùn)練步驟所必須運(yùn)行的操作(見下文)。
global_step = tf.Variable(0, name='global_step', trainable=False) train_op = optimizer.minimize(loss, global_step=global_step)
最后,程序返回包含了訓(xùn)練操作(training op)輸出結(jié)果的Tensor。
一旦圖表構(gòu)建完畢,就通過fully_connected_feed.py
文件中的用戶代碼進(jìn)行循環(huán)地迭代式訓(xùn)練和評(píng)估。
在run_training()
這個(gè)函數(shù)的一開始,是一個(gè)Python語(yǔ)言中的with
命令,這個(gè)命令表明所有已經(jīng)構(gòu)建的操作都要與默認(rèn)的tf.Graph
全局實(shí)例關(guān)聯(lián)起來。
with tf.Graph().as_default():
tf.Graph
實(shí)例是一系列可以作為整體執(zhí)行的操作。TensorFlow的大部分場(chǎng)景只需要依賴默認(rèn)圖表一個(gè)實(shí)例即可。
利用多個(gè)圖表的更加復(fù)雜的使用場(chǎng)景也是可能的,但是超出了本教程的范圍。
完成全部的構(gòu)建準(zhǔn)備、生成全部所需的操作之后,我們就可以創(chuàng)建一個(gè)tf.Session
,用于運(yùn)行圖表。
sess = tf.Session()
另外,也可以利用with
代碼塊生成Session
,限制作用域:
with tf.Session() as sess:
Session
函數(shù)中沒有傳入?yún)?shù),表明該代碼將會(huì)依附于(如果還沒有創(chuàng)建會(huì)話,則會(huì)創(chuàng)建新的會(huì)話)默認(rèn)的本地會(huì)話。
生成會(huì)話之后,所有tf.Variable
實(shí)例都會(huì)立即通過調(diào)用各自初始化操作中的sess.run()
函數(shù)進(jìn)行初始化。
init = tf.initialize_all_variables() sess.run(init)
sess.run()
方法將會(huì)運(yùn)行圖表中與作為參數(shù)傳入的操作相對(duì)應(yīng)的完整子集。在初次調(diào)用時(shí),init
操作只包含了變量初始化程序tf.group
。圖表的其他部分不會(huì)在這里,而是在下面的訓(xùn)練循環(huán)運(yùn)行。
完成會(huì)話中變量的初始化之后,就可以開始訓(xùn)練了。
訓(xùn)練的每一步都是通過用戶代碼控制,而能實(shí)現(xiàn)有效訓(xùn)練的最簡(jiǎn)單循環(huán)就是:
for step in xrange(max_steps): sess.run(train_op)
但是,本教程中的例子要更為復(fù)雜一點(diǎn),原因是我們必須把輸入的數(shù)據(jù)根據(jù)每一步的情況進(jìn)行切分,以匹配之前生成的占位符。
執(zhí)行每一步時(shí),我們的代碼會(huì)生成一個(gè)反饋?zhàn)值洌╢eed dictionary),其中包含對(duì)應(yīng)步驟中訓(xùn)練所要使用的例子,這些例子的哈希鍵就是其所代表的占位符操作。
fill_feed_dict
函數(shù)會(huì)查詢給定的DataSet
,索要下一批次batch_size
的圖像和標(biāo)簽,與占位符相匹配的Tensor則會(huì)包含下一批次的圖像和標(biāo)簽。
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size)
然后,以占位符為哈希鍵,創(chuàng)建一個(gè)Python字典對(duì)象,鍵值則是其代表的反饋Tensor。
feed_dict = { images_placeholder: images_feed, labels_placeholder: labels_feed, }
這個(gè)字典隨后作為feed_dict
參數(shù),傳入sess.run()
函數(shù)中,為這一步的訓(xùn)練提供輸入樣例。
在運(yùn)行sess.run
函數(shù)時(shí),要在代碼中明確其需要獲取的兩個(gè)值:[train_op, loss]
。
for step in xrange(FLAGS.max_steps): feed_dict = fill_feed_dict(data_sets.train, images_placeholder, labels_placeholder) _, loss_value = sess.run([train_op, loss], feed_dict=feed_dict)
因?yàn)橐@取這兩個(gè)值,sess.run()
會(huì)返回一個(gè)有兩個(gè)元素的元組。其中每一個(gè)Tensor
對(duì)象,對(duì)應(yīng)了返回的元組中的numpy數(shù)組,而這些數(shù)組中包含了當(dāng)前這步訓(xùn)練中對(duì)應(yīng)Tensor的值。由于train_op
并不會(huì)產(chǎn)生輸出,其在返回的元祖中的對(duì)應(yīng)元素就是None
,所以會(huì)被拋棄。但是,如果模型在訓(xùn)練中出現(xiàn)偏差,loss
Tensor的值可能會(huì)變成NaN,所以我們要獲取它的值,并記錄下來。
假設(shè)訓(xùn)練一切正常,沒有出現(xiàn)NaN,訓(xùn)練循環(huán)會(huì)每隔100個(gè)訓(xùn)練步驟,就打印一行簡(jiǎn)單的狀態(tài)文本,告知用戶當(dāng)前的訓(xùn)練狀態(tài)。
if step % 100 == 0:print 'Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)
為了釋放TensorBoard所使用的事件文件(events file),所有的即時(shí)數(shù)據(jù)(在這里只有一個(gè))都要在圖表構(gòu)建階段合并至一個(gè)操作(op)中。
summary_op = tf.merge_all_summaries()
在創(chuàng)建好會(huì)話(session)之后,可以實(shí)例化一個(gè)tf.train.SummaryWriter
,用于寫入包含了圖表本身和即時(shí)數(shù)據(jù)具體值的事件文件。
summary_writer = tf.train.SummaryWriter(FLAGS.train_dir, graph_def=sess.graph_def)
最后,每次運(yùn)行summary_op
時(shí),都會(huì)往事件文件中寫入最新的即時(shí)數(shù)據(jù),函數(shù)的輸出會(huì)傳入事件文件讀寫器(writer)的add_summary()
函數(shù)。。
summary_str = sess.run(summary_op, feed_dict=feed_dict) summary_writer.add_summary(summary_str, step)
事件文件寫入完畢之后,可以就訓(xùn)練文件夾打開一個(gè)TensorBoard,查看即時(shí)數(shù)據(jù)的情況。
注意:了解更多如何構(gòu)建并運(yùn)行TensorBoard的信息,請(qǐng)查看相關(guān)教程Tensorboard:訓(xùn)練過程可視化。
為了得到可以用來后續(xù)恢復(fù)模型以進(jìn)一步訓(xùn)練或評(píng)估的檢查點(diǎn)文件(checkpoint file),我們實(shí)例化一個(gè)tf.train.Saver
。
saver = tf.train.Saver()
在訓(xùn)練循環(huán)中,將定期調(diào)用saver.save()
方法,向訓(xùn)練文件夾中寫入包含了當(dāng)前所有可訓(xùn)練變量值得檢查點(diǎn)文件。
saver.save(sess, FLAGS.train_dir, global_step=step)
這樣,我們以后就可以使用saver.restore()
方法,重載模型的參數(shù),繼續(xù)訓(xùn)練。
saver.restore(sess, FLAGS.train_dir)
每隔一千個(gè)訓(xùn)練步驟,我們的代碼會(huì)嘗試使用訓(xùn)練數(shù)據(jù)集與測(cè)試數(shù)據(jù)集,對(duì)模型進(jìn)行評(píng)估。do_eval
函數(shù)會(huì)被調(diào)用三次,分別使用訓(xùn)練數(shù)據(jù)集、驗(yàn)證數(shù)據(jù)集合測(cè)試數(shù)據(jù)集。
print 'Training Data Eval:'do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.train)print 'Validation Data Eval:'do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.validation)print 'Test Data Eval:'do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.test)
注意,更復(fù)雜的使用場(chǎng)景通常是,先隔絕
data_sets.test
測(cè)試數(shù)據(jù)集,只有在大量的超參數(shù)優(yōu)化調(diào)整(hyperparameter tuning)之后才進(jìn)行檢查。但是,由于MNIST問題比較簡(jiǎn)單,我們?cè)谶@里一次性評(píng)估所有的數(shù)據(jù)。
在打開默認(rèn)圖表(Graph)之前,我們應(yīng)該先調(diào)用get_data(train=False)
函數(shù),抓取測(cè)試數(shù)據(jù)集。
test_all_images, test_all_labels = get_data(train=False)
在進(jìn)入訓(xùn)練循環(huán)之前,我們應(yīng)該先調(diào)用mnist.py
文件中的evaluation
函數(shù),傳入的logits和標(biāo)簽參數(shù)要與loss
函數(shù)的一致。這樣做事為了先構(gòu)建Eval操作。
eval_correct = mnist.evaluation(logits, labels_placeholder)
evaluation
函數(shù)會(huì)生成tf.nn.in_top_k
操作,如果在K個(gè)最有可能的預(yù)測(cè)中可以發(fā)現(xiàn)真的標(biāo)簽,那么這個(gè)操作就會(huì)將模型輸出標(biāo)記為正確。在本文中,我們把K的值設(shè)置為1,也就是只有在預(yù)測(cè)是真的標(biāo)簽時(shí),才判定它是正確的。
eval_correct = tf.nn.in_top_k(logits, labels, 1)
之后,我們可以創(chuàng)建一個(gè)循環(huán),往其中添加feed_dict
,并在調(diào)用sess.run()
函數(shù)時(shí)傳入eval_correct
操作,目的就是用給定的數(shù)據(jù)集評(píng)估模型。
for step in xrange(steps_per_epoch): feed_dict = fill_feed_dict(data_set, images_placeholder, labels_placeholder) true_count += sess.run(eval_correct, feed_dict=feed_dict)
true_count
變量會(huì)累加所有in_top_k
操作判定為正確的預(yù)測(cè)之和。接下來,只需要將正確測(cè)試的總數(shù),除以例子總數(shù),就可以得出準(zhǔn)確率了。
precision = float(true_count) / float(num_examples)print 'Num examples: %dNum correct: %dPrecision @ 1: %0.02f' % ( num_examples, true_count, precision)
原文:TensorFlow Mechanics 101 翻譯:bingjin 校對(duì):LichAmnesia
Copyright ? 2019-2024 青島希諾智能科技有限公司版權(quán)所有 備案號(hào):魯ICP備19042003號(hào)-1
技術(shù)支持:微動(dòng)力網(wǎng)絡(luò)