本篇主要介紹如何儲存和恢復神經網路變數以及Early-Stopping優化策略。
其中有大段之前教程的文字及程式碼,如果看過的朋友可以快速翻到下文Saver相關的部分。
by Magnus Erik Hvass Pedersen / GitHub / Videos on YouTube
中文翻譯 thrillerist / Github
如有轉載,請附上本文連結。
介紹
這篇教程展示瞭如何儲存以及恢復神經網路中的變數。在優化的過程中,當驗證集上分類準確率提高時,儲存神經網路的變數。如果經過1000次迭代還不能提升效能時,就終止優化。然後我們重新載入在驗證集上表現最好的變數。
這種策略稱為Early-Stopping。它用來避免神經網路的過擬合。(過擬合)會在神經網路訓練時間太長時出現,此時神經網路開始學習訓練集中的噪聲,將導致它誤分類新的影像。
這篇教程主要是用神經網路來識別MNIST資料集中的手寫數字,過擬合在這裡並不是什麼大問題。但本教程展示了Early Stopping的思想。
本文基於上一篇教程,你需要了解基本的TensorFlow和附加包Pretty Tensor。其中大量程式碼和文字與之前教程相似,如果你已經看過就可以快速地瀏覽本文。
流程圖
下面的圖表直接顯示了之後實現的卷積神經網路中資料的傳遞。網路有兩個卷積層和兩個全連線層,最後一層是用來給輸入影像分類的。關於網路和卷積的更多細節描述見教程 #02 。
from IPython.display import Image
Image('images/02_network_flowchart.png')複製程式碼
匯入
%matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from sklearn.metrics import confusion_matrix
import time
from datetime import timedelta
import math
import os
# Use PrettyTensor to simplify Neural Network construction.
import prettytensor as pt複製程式碼
使用Python3.5.2(Anaconda)開發,TensorFlow版本是:
tf.__version__複製程式碼
'0.12.0-rc0'
PrettyTensor 版本:
pt.__version__複製程式碼
'0.7.1'
載入資料
MNIST資料集大約12MB,如果沒在給定路徑中找到就會自動下載。
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets('data/MNIST/', one_hot=True)複製程式碼
Extracting data/MNIST/train-images-idx3-ubyte.gz
Extracting data/MNIST/train-labels-idx1-ubyte.gz
Extracting data/MNIST/t10k-images-idx3-ubyte.gz
Extracting data/MNIST/t10k-labels-idx1-ubyte.gz
現在已經載入了MNIST資料集,它由70,000張影像和對應的標籤(比如影像的類別)組成。資料集分成三份互相獨立的子集。我們在教程中只用訓練集和測試集。
print("Size of:")
print("- Training-set:\t\t{}".format(len(data.train.labels)))
print("- Test-set:\t\t{}".format(len(data.test.labels)))
print("- Validation-set:\t{}".format(len(data.validation.labels)))複製程式碼
Size of:
-Training-set: 55000
-Test-set: 10000
-Validation-set: 5000
型別標籤使用One-Hot編碼,這意外每個標籤是長為10的向量,除了一個元素之外,其他的都為零。這個元素的索引就是類別的數字,即相應圖片中畫的數字。我們也需要測試資料集類別數字的整型值,用下面的方法來計算。
data.test.cls = np.argmax(data.test.labels, axis=1)
data.validation.cls = np.argmax(data.validation.labels, axis=1)複製程式碼
資料維度
在下面的原始碼中,有很多地方用到了資料維度。它們只在一個地方定義,因此我們可以在程式碼中使用這些數字而不是直接寫數字。
# We know that MNIST images are 28 pixels in each dimension.
img_size = 28
# Images are stored in one-dimensional arrays of this length.
img_size_flat = img_size * img_size
# Tuple with height and width of images used to reshape arrays.
img_shape = (img_size, img_size)
# Number of colour channels for the images: 1 channel for gray-scale.
num_channels = 1
# Number of classes, one class for each of 10 digits.
num_classes = 10複製程式碼
用來繪製圖片的幫助函式
這個函式用來在3x3的柵格中畫9張影像,然後在每張影像下面寫出真實類別和預測類別。
def plot_images(images, cls_true, cls_pred=None):
assert len(images) == len(cls_true) == 9
# Create figure with 3x3 sub-plots.
fig, axes = plt.subplots(3, 3)
fig.subplots_adjust(hspace=0.3, wspace=0.3)
for i, ax in enumerate(axes.flat):
# Plot image.
ax.imshow(images[i].reshape(img_shape), cmap='binary')
# Show true and predicted classes.
if cls_pred is None:
xlabel = "True: {0}".format(cls_true[i])
else:
xlabel = "True: {0}, Pred: {1}".format(cls_true[i], cls_pred[i])
# Show the classes as the label on the x-axis.
ax.set_xlabel(xlabel)
# Remove ticks from the plot.
ax.set_xticks([])
ax.set_yticks([])
# Ensure the plot is shown correctly with multiple plots
# in a single Notebook cell.
plt.show()複製程式碼
繪製幾張影像來看看資料是否正確
# Get the first images from the test-set.
images = data.test.images[0:9]
# Get the true classes for those images.
cls_true = data.test.cls[0:9]
# Plot the images and labels using our helper-function above.
plot_images(images=images, cls_true=cls_true)複製程式碼
TensorFlow圖
TensorFlow的全部目的就是使用一個稱之為計算圖(computational graph)的東西,它會比直接在Python中進行相同計算量要高效得多。TensorFlow比Numpy更高效,因為TensorFlow瞭解整個需要執行的計算圖,然而Numpy只知道某個時間點上唯一的數學運算。
TensorFlow也能夠自動地計算需要優化的變數的梯度,使得模型有更好的表現。這是由於圖是簡單數學表示式的結合,因此整個圖的梯度可以用鏈式法則推匯出來。
TensorFlow還能利用多核CPU和GPU,Google也為TensorFlow製造了稱為TPUs(Tensor Processing Units)的特殊晶片,它比GPU更快。
一個TensorFlow圖由下面幾個部分組成,後面會詳細描述:
- 佔位符變數(Placeholder)用來改變圖的輸入。
- 模型變數(Model)將會被優化,使得模型表現得更好。
- 模型本質上就是一些數學函式,它根據Placeholder和模型的輸入變數來計算一些輸出。
- 一個cost度量用來指導變數的優化。
- 一個優化策略會更新模型的變數。
另外,TensorFlow圖也包含了一些除錯狀態,比如用TensorBoard列印log資料,本教程不涉及這些。
佔位符 (Placeholder)變數
Placeholder是作為圖的輸入,我們每次執行圖的時候都可能改變它們。將這個過程稱為feeding placeholder變數,後面將會描述這個。
首先我們為輸入影像定義placeholder變數。這讓我們可以改變輸入到TensorFlow圖中的影像。這也是一個張量(tensor),代表一個多維向量或矩陣。資料型別設定為float32,形狀設為[None, img_size_flat]
,None
代表tensor可能儲存著任意數量的影像,每張圖象是一個長度為img_size_flat
的向量。
x = tf.placeholder(tf.float32, shape=[None, img_size_flat], name='x')複製程式碼
卷積層希望x
被編碼為4維張量,因此我們需要將它的形狀轉換至[num_images, img_height, img_width, num_channels]
。注意img_height == img_width == img_size
,如果第一維的大小設為-1, num_images
的大小也會被自動推匯出來。轉換運算如下:
x_image = tf.reshape(x, [-1, img_size, img_size, num_channels])複製程式碼
接下來我們為輸入變數x
中的影像所對應的真實標籤定義placeholder變數。變數的形狀是[None, num_classes]
,這代表著它儲存了任意數量的標籤,每個標籤是長度為num_classes
的向量,本例中長度為10。
y_true = tf.placeholder(tf.float32, shape=[None, 10], name='y_true')複製程式碼
我們也可以為class-number提供一個placeholder,但這裡用argmax來計算它。這裡只是TensorFlow中的一些操作,沒有執行什麼運算。
y_true_cls = tf.argmax(y_true, dimension=1)複製程式碼
神經網路
這一節用PrettyTensor實現卷積神經網路,這要比直接在TensorFlow中實現來得簡單,詳見教程 #03。
基本思想就是用一個Pretty Tensor object封裝輸入張量x_image
,它有一個新增新卷積層的幫助函式,以此來建立整個神經網路。Pretty Tensor負責變數分配等等。
x_pretty = pt.wrap(x_image)複製程式碼
現在我們已經將輸入影像裝到一個PrettyTensor的object中,再用幾行程式碼就可以新增摺積層和全連線層。
注意,在with
程式碼塊中,pt.defaults_scope(activation_fn=tf.nn.relu)
把 activation_fn=tf.nn.relu
當作每個的層引數,因此這些層都用到了 Rectified Linear Units (ReLU) 。defaults_scope
使我們能更方便地修改所有層的引數。
with pt.defaults_scope(activation_fn=tf.nn.relu):
y_pred, loss = x_pretty.\
conv2d(kernel=5, depth=16, name='layer_conv1').\
max_pool(kernel=2, stride=2).\
conv2d(kernel=5, depth=36, name='layer_conv2').\
max_pool(kernel=2, stride=2).\
flatten().\
fully_connected(size=128, name='layer_fc1').\
softmax_classifier(num_classes=num_classes, labels=y_true)複製程式碼
獲取權重
下面,我們要繪製神經網路的權重。當使用Pretty Tensor來建立網路時,層的所有變數都是由Pretty Tensoe間接建立的。因此我們要從TensorFlow中獲取變數。
我們用layer_conv1
和 layer_conv2
代表兩個卷積層。這也叫變數作用域(不要與上面描述的defaults_scope
混淆了)。PrettyTensor會自動給它為每個層建立的變數命名,因此我們可以通過層的作用域名稱和變數名來取得某一層的權重。
函式實現有點笨拙,因為我們不得不用TensorFlow函式get_variable()
,它是設計給其他用途的,建立新的變數或重用現有變數。建立下面的幫助函式很簡單。
def get_weights_variable(layer_name):
# Retrieve an existing variable named 'weights' in the scope
# with the given layer_name.
# This is awkward because the TensorFlow function was
# really intended for another purpose.
with tf.variable_scope(layer_name, reuse=True):
variable = tf.get_variable('weights')
return variable複製程式碼
藉助這個幫助函式我們可以獲取變數。這些是TensorFlow的objects。你需要類似的操作來獲取變數的內容: contents = session.run(weights_conv1)
,下面會提到這個。
weights_conv1 = get_weights_variable(layer_name='layer_conv1')
weights_conv2 = get_weights_variable(layer_name='layer_conv2')複製程式碼
優化方法
PrettyTensor給我們提供了預測型別標籤(y_pred
)以及一個需要最小化的損失度量,用來提升神經網路分類圖片的能力。
PrettyTensor的文件並沒有說明它的損失度量是用cross-entropy還是其他的。但現在我們用AdamOptimizer
來最小化損失。
優化過程並不是在這裡執行。實際上,還沒計算任何東西,我們只是往TensorFlow圖中新增了優化器,以便後續操作。
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(loss)複製程式碼
效能度量
我們需要另外一些效能度量,來向使用者展示這個過程。
首先我們從神經網路輸出的y_pred
中計算出預測的類別,它是一個包含10個元素的向量。類別數字是最大元素的索引。
y_pred_cls = tf.argmax(y_pred, dimension=1)複製程式碼
然後建立一個布林向量,用來告訴我們每張圖片的真實類別是否與預測類別相同。
correct_prediction = tf.equal(y_pred_cls, y_true_cls)複製程式碼
上面的計算先將布林值向量型別轉換成浮點型向量,這樣子False就變成0,True變成1,然後計算這些值的平均數,以此來計算分類的準確度。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))複製程式碼
Saver
為了儲存神經網路的變數,我們建立一個稱為Saver-object的物件,它用來儲存及恢復TensorFlow圖的所有變數。在這裡並未儲存什麼東西,(儲存操作)在後面的optimize()
函式中完成。
saver = tf.train.Saver()複製程式碼
由於(儲存操作)常間隔著寫在(程式碼)中,因此儲存的檔案通常稱為checkpoints。
這是用來儲存或恢復資料的資料夾。
save_dir = 'checkpoints/'複製程式碼
如果資料夾不存在則建立。
if not os.path.exists(save_dir):
os.makedirs(save_dir)複製程式碼
這是儲存checkpoint檔案的路徑。
save_path = os.path.join(save_dir, 'best_validation')複製程式碼
執行TensorFlow
建立TensorFlow會話(session)
一旦建立了TensorFlow圖,我們需要建立一個TensorFlow會話,用來執行圖。
session = tf.Session()複製程式碼
初始化變數
變數weights
和biases
在優化之前需要先進行初始化。我們寫一個簡單的封裝函式,後面會再次呼叫。
def init_variables():
session.run(tf.global_variables_initializer())複製程式碼
執行函式來初始化變數。
init_variables()複製程式碼
用來優化迭代的幫助函式
在訓練集中有50,000張圖。用這些影像計算模型的梯度會花很多時間。因此我們利用隨機梯度下降的方法,它在優化器的每次迭代裡只用到了一小部分的影像。
如果記憶體耗盡導致電腦當機或變得很慢,你應該試著減少這些數量,但同時可能還需要更優化的迭代。
train_batch_size = 64複製程式碼
每迭代100次下面的優化函式,會計算一次驗證集上的分類準確率。如果過了1000次迭代驗證準確率還是沒有提升,就停止優化。我們需要一些變數來跟蹤這個過程。
# Best validation accuracy seen so far.
best_validation_accuracy = 0.0
# Iteration-number for last improvement to validation accuracy.
last_improvement = 0
# Stop optimization if no improvement found in this many iterations.
require_improvement = 1000複製程式碼
函式用來執行一定數量的優化迭代,以此來逐漸改善網路層的變數。在每次迭代中,會從訓練集中選擇新的一批資料,然後TensorFlow在這些訓練樣本上執行優化。每100次迭代會列印出(資訊),同時計算驗證準確率,如果效果有提升的話會將它儲存至檔案。
# Counter for total number of iterations performed so far.
total_iterations = 0
def optimize(num_iterations):
# Ensure we update the global variables rather than local copies.
global total_iterations
global best_validation_accuracy
global last_improvement
# Start-time used for printing time-usage below.
start_time = time.time()
for i in range(num_iterations):
# Increase the total number of iterations performed.
# It is easier to update it in each iteration because
# we need this number several times in the following.
total_iterations += 1
# Get a batch of training examples.
# x_batch now holds a batch of images and
# y_true_batch are the true labels for those images.
x_batch, y_true_batch = data.train.next_batch(train_batch_size)
# Put the batch into a dict with the proper names
# for placeholder variables in the TensorFlow graph.
feed_dict_train = {x: x_batch,
y_true: y_true_batch}
# Run the optimizer using this batch of training data.
# TensorFlow assigns the variables in feed_dict_train
# to the placeholder variables and then runs the optimizer.
session.run(optimizer, feed_dict=feed_dict_train)
# Print status every 100 iterations and after last iteration.
if (total_iterations % 100 == 0) or (i == (num_iterations - 1)):
# Calculate the accuracy on the training-batch.
acc_train = session.run(accuracy, feed_dict=feed_dict_train)
# Calculate the accuracy on the validation-set.
# The function returns 2 values but we only need the first.
acc_validation, _ = validation_accuracy()
# If validation accuracy is an improvement over best-known.
if acc_validation > best_validation_accuracy:
# Update the best-known validation accuracy.
best_validation_accuracy = acc_validation
# Set the iteration for the last improvement to current.
last_improvement = total_iterations
# Save all variables of the TensorFlow graph to file.
saver.save(sess=session, save_path=save_path)
# A string to be printed below, shows improvement found.
improved_str = '*'
else:
# An empty string to be printed below.
# Shows that no improvement was found.
improved_str = ''
# Status-message for printing.
msg = "Iter: {0:>6}, Train-Batch Accuracy: {1:>6.1%}, Validation Acc: {2:>6.1%} {3}"
# Print it.
print(msg.format(i + 1, acc_train, acc_validation, improved_str))
# If no improvement found in the required number of iterations.
if total_iterations - last_improvement > require_improvement:
print("No improvement found in a while, stopping optimization.")
# Break out from the for-loop.
break
# Ending time.
end_time = time.time()
# Difference between start and end-times.
time_dif = end_time - start_time
# Print the time-usage.
print("Time usage: " + str(timedelta(seconds=int(round(time_dif)))))複製程式碼
用來繪製錯誤樣本的幫助函式
函式用來繪製測試集中被誤分類的樣本。
def plot_example_errors(cls_pred, correct):
# This function is called from print_test_accuracy() below.
# cls_pred is an array of the predicted class-number for
# all images in the test-set.
# correct is a boolean array whether the predicted class
# is equal to the true class for each image in the test-set.
# Negate the boolean array.
incorrect = (correct == False)
# Get the images from the test-set that have been
# incorrectly classified.
images = data.test.images[incorrect]
# Get the predicted classes for those images.
cls_pred = cls_pred[incorrect]
# Get the true classes for those images.
cls_true = data.test.cls[incorrect]
# Plot the first 9 images.
plot_images(images=images[0:9],
cls_true=cls_true[0:9],
cls_pred=cls_pred[0:9])複製程式碼
繪製混淆(confusion)矩陣的幫助函式
def plot_confusion_matrix(cls_pred):
# This is called from print_test_accuracy() below.
# cls_pred is an array of the predicted class-number for
# all images in the test-set.
# Get the true classifications for the test-set.
cls_true = data.test.cls
# Get the confusion matrix using sklearn.
cm = confusion_matrix(y_true=cls_true,
y_pred=cls_pred)
# Print the confusion matrix as text.
print(cm)
# Plot the confusion matrix as an image.
plt.matshow(cm)
# Make various adjustments to the plot.
plt.colorbar()
tick_marks = np.arange(num_classes)
plt.xticks(tick_marks, range(num_classes))
plt.yticks(tick_marks, range(num_classes))
plt.xlabel('Predicted')
plt.ylabel('True')
# Ensure the plot is shown correctly with multiple plots
# in a single Notebook cell.
plt.show()複製程式碼
計算分類的幫助函式
這個函式用來計算影像的預測類別,同時返回一個代表每張影像分類是否正確的布林陣列。
由於計算可能會耗費太多記憶體,就分批處理。如果你的電腦當機了,試著降低batch-size。
# Split the data-set in batches of this size to limit RAM usage.
batch_size = 256
def predict_cls(images, labels, cls_true):
# Number of images.
num_images = len(images)
# Allocate an array for the predicted classes which
# will be calculated in batches and filled into this array.
cls_pred = np.zeros(shape=num_images, dtype=np.int)
# Now calculate the predicted classes for the batches.
# We will just iterate through all the batches.
# There might be a more clever and Pythonic way of doing this.
# The starting index for the next batch is denoted i.
i = 0
while i < num_images:
# The ending index for the next batch is denoted j.
j = min(i + batch_size, num_images)
# Create a feed-dict with the images and labels
# between index i and j.
feed_dict = {x: images[i:j, :],
y_true: labels[i:j, :]}
# Calculate the predicted class using TensorFlow.
cls_pred[i:j] = session.run(y_pred_cls, feed_dict=feed_dict)
# Set the start-index for the next batch to the
# end-index of the current batch.
i = j
# Create a boolean array whether each image is correctly classified.
correct = (cls_true == cls_pred)
return correct, cls_pred複製程式碼
計算測試集上的預測類別。
def predict_cls_test():
return predict_cls(images = data.test.images,
labels = data.test.labels,
cls_true = data.test.cls)複製程式碼
計算驗證集上的預測類別。
def predict_cls_validation():
return predict_cls(images = data.validation.images,
labels = data.validation.labels,
cls_true = data.validation.cls)複製程式碼
分類準確率的幫助函式
這個函式計算了給定布林陣列的分類準確率,布林陣列表示每張影像是否被正確分類。比如, cls_accuracy([True, True, False, False, False]) = 2/5 = 0.4
。
def cls_accuracy(correct):
# Calculate the number of correctly classified images.
# When summing a boolean array, False means 0 and True means 1.
correct_sum = correct.sum()
# Classification accuracy is the number of correctly classified
# images divided by the total number of images in the test-set.
acc = float(correct_sum) / len(correct)
return acc, correct_sum複製程式碼
計算驗證集上的分類準確率。
def validation_accuracy():
# Get the array of booleans whether the classifications are correct
# for the validation-set.
# The function returns two values but we only need the first.
correct, _ = predict_cls_validation()
# Calculate the classification accuracy and return it.
return cls_accuracy(correct)複製程式碼
展示效能的幫助函式
函式用來列印測試集上的分類準確率。
為測試集上的所有圖片計算分類會花費一段時間,因此我們直接從這個函式裡呼叫上面的函式,這樣就不用每個函式都重新計算分類。
def print_test_accuracy(show_example_errors=False,
show_confusion_matrix=False):
# For all the images in the test-set,
# calculate the predicted classes and whether they are correct.
correct, cls_pred = predict_cls_test()
# Classification accuracy and the number of correct classifications.
acc, num_correct = cls_accuracy(correct)
# Number of images being classified.
num_images = len(correct)
# Print the accuracy.
msg = "Accuracy on Test-Set: {0:.1%} ({1} / {2})"
print(msg.format(acc, num_correct, num_images))
# Plot some examples of mis-classifications, if desired.
if show_example_errors:
print("Example errors:")
plot_example_errors(cls_pred=cls_pred, correct=correct)
# Plot the confusion matrix, if desired.
if show_confusion_matrix:
print("Confusion Matrix:")
plot_confusion_matrix(cls_pred=cls_pred)複製程式碼
繪製卷積權重的幫助函式
def plot_conv_weights(weights, input_channel=0):
# Assume weights are TensorFlow ops for 4-dim variables
# e.g. weights_conv1 or weights_conv2.
# Retrieve the values of the weight-variables from TensorFlow.
# A feed-dict is not necessary because nothing is calculated.
w = session.run(weights)
# Print mean and standard deviation.
print("Mean: {0:.5f}, Stdev: {1:.5f}".format(w.mean(), w.std()))
# Get the lowest and highest values for the weights.
# This is used to correct the colour intensity across
# the images so they can be compared with each other.
w_min = np.min(w)
w_max = np.max(w)
# Number of filters used in the conv. layer.
num_filters = w.shape[3]
# Number of grids to plot.
# Rounded-up, square-root of the number of filters.
num_grids = math.ceil(math.sqrt(num_filters))
# Create figure with a grid of sub-plots.
fig, axes = plt.subplots(num_grids, num_grids)
# Plot all the filter-weights.
for i, ax in enumerate(axes.flat):
# Only plot the valid filter-weights.
if i<num_filters:
# Get the weights for the i'th filter of the input channel.
# The format of this 4-dim tensor is determined by the
# TensorFlow API. See Tutorial #02 for more details.
img = w[:, :, input_channel, i]
# Plot image.
ax.imshow(img, vmin=w_min, vmax=w_max,
interpolation='nearest', cmap='seismic')
# Remove ticks from the plot.
ax.set_xticks([])
ax.set_yticks([])
# Ensure the plot is shown correctly with multiple plots
# in a single Notebook cell.
plt.show()複製程式碼
優化之前的效能
測試集上的準確度很低,這是由於模型只做了初始化,並沒做任何優化,所以它只是對影像做隨機分類。
print_test_accuracy()複製程式碼
Accuracy on Test-Set: 8.5% (849 / 10000)
卷積權重是隨機的,但也很難把它與下面優化過的權重區分開來。這裡也展示了平均值和標準差,因此我們可以看看是否有差別。
plot_conv_weights(weights=weights_conv1)複製程式碼
Mean: 0.00880, Stdev: 0.28635
10,000次優化迭代後的效能
現在我們進行了10,000次優化迭代,並且,當經過1000次迭代驗證集上的效能卻沒有提升時就停止優化。
星號 * 代表驗證集上的分類準確度有提升。
optimize(num_iterations=10000)複製程式碼
Iter: 100, Train-Batch Accuracy: 84.4%, Validation Acc: 85.2%
Iter: 200, Train-Batch Accuracy: 92.2%, Validation Acc: 91.5%
Iter: 300, Train-Batch Accuracy: 95.3%, Validation Acc: 93.7%
Iter: 400, Train-Batch Accuracy: 92.2%, Validation Acc: 94.3%
Iter: 500, Train-Batch Accuracy: 98.4%, Validation Acc: 94.7%
Iter: 600, Train-Batch Accuracy: 93.8%, Validation Acc: 94.7%
Iter: 700, Train-Batch Accuracy: 98.4%, Validation Acc: 95.6%
Iter: 800, Train-Batch Accuracy: 100.0%, Validation Acc: 96.3%
Iter: 900, Train-Batch Accuracy: 98.4%, Validation Acc: 96.4%
Iter: 1000, Train-Batch Accuracy: 100.0%, Validation Acc: 96.9%
Iter: 1100, Train-Batch Accuracy: 96.9%, Validation Acc: 97.0%
Iter: 1200, Train-Batch Accuracy: 93.8%, Validation Acc: 97.0%
Iter: 1300, Train-Batch Accuracy: 92.2%, Validation Acc: 97.2%
Iter: 1400, Train-Batch Accuracy: 100.0%, Validation Acc: 97.3%
Iter: 1500, Train-Batch Accuracy: 96.9%, Validation Acc: 97.4%
Iter: 1600, Train-Batch Accuracy: 100.0%, Validation Acc: 97.7%
Iter: 1700, Train-Batch Accuracy: 100.0%, Validation Acc: 97.8%
Iter: 1800, Train-Batch Accuracy: 98.4%, Validation Acc: 97.7%
Iter: 1900, Train-Batch Accuracy: 98.4%, Validation Acc: 98.1%
Iter: 2000, Train-Batch Accuracy: 95.3%, Validation Acc: 98.0%
Iter: 2100, Train-Batch Accuracy: 98.4%, Validation Acc: 97.9%
Iter: 2200, Train-Batch Accuracy: 100.0%, Validation Acc: 98.0%
Iter: 2300, Train-Batch Accuracy: 96.9%, Validation Acc: 98.1%
Iter: 2400, Train-Batch Accuracy: 93.8%, Validation Acc: 98.1%
Iter: 2500, Train-Batch Accuracy: 98.4%, Validation Acc: 98.2%
Iter: 2600, Train-Batch Accuracy: 98.4%, Validation Acc: 98.0%
Iter: 2700, Train-Batch Accuracy: 98.4%, Validation Acc: 98.0%
Iter: 2800, Train-Batch Accuracy: 96.9%, Validation Acc: 98.1%
Iter: 2900, Train-Batch Accuracy: 96.9%, Validation Acc: 98.2%
Iter: 3000, Train-Batch Accuracy: 98.4%, Validation Acc: 98.2%
Iter: 3100, Train-Batch Accuracy: 100.0%, Validation Acc: 98.1%
Iter: 3200, Train-Batch Accuracy: 100.0%, Validation Acc: 98.3%
Iter: 3300, Train-Batch Accuracy: 98.4%, Validation Acc: 98.4%
Iter: 3400, Train-Batch Accuracy: 95.3%, Validation Acc: 98.0%
Iter: 3500, Train-Batch Accuracy: 98.4%, Validation Acc: 98.3%
Iter: 3600, Train-Batch Accuracy: 100.0%, Validation Acc: 98.5%
Iter: 3700, Train-Batch Accuracy: 98.4%, Validation Acc: 98.3%
Iter: 3800, Train-Batch Accuracy: 96.9%, Validation Acc: 98.1%
Iter: 3900, Train-Batch Accuracy: 96.9%, Validation Acc: 98.5%
Iter: 4000, Train-Batch Accuracy: 100.0%, Validation Acc: 98.4%
Iter: 4100, Train-Batch Accuracy: 100.0%, Validation Acc: 98.5%
Iter: 4200, Train-Batch Accuracy: 100.0%, Validation Acc: 98.3%
Iter: 4300, Train-Batch Accuracy: 100.0%, Validation Acc: 98.6%
Iter: 4400, Train-Batch Accuracy: 96.9%, Validation Acc: 98.4%
Iter: 4500, Train-Batch Accuracy: 98.4%, Validation Acc: 98.5%
Iter: 4600, Train-Batch Accuracy: 98.4%, Validation Acc: 98.5%
Iter: 4700, Train-Batch Accuracy: 98.4%, Validation Acc: 98.4%
Iter: 4800, Train-Batch Accuracy: 100.0%, Validation Acc: 98.8% *
Iter: 4900, Train-Batch Accuracy: 100.0%, Validation Acc: 98.8%
Iter: 5000, Train-Batch Accuracy: 98.4%, Validation Acc: 98.6%
Iter: 5100, Train-Batch Accuracy: 98.4%, Validation Acc: 98.6%
Iter: 5200, Train-Batch Accuracy: 100.0%, Validation Acc: 98.6%
Iter: 5300, Train-Batch Accuracy: 96.9%, Validation Acc: 98.5%
Iter: 5400, Train-Batch Accuracy: 98.4%, Validation Acc: 98.7%
Iter: 5500, Train-Batch Accuracy: 98.4%, Validation Acc: 98.6%
Iter: 5600, Train-Batch Accuracy: 100.0%, Validation Acc: 98.4%
Iter: 5700, Train-Batch Accuracy: 100.0%, Validation Acc: 98.6%
Iter: 5800, Train-Batch Accuracy: 100.0%, Validation Acc: 98.7%
No improvement found in a while, stopping optimization.
Time usage: 0:00:28
print_test_accuracy(show_example_errors=True,
show_confusion_matrix=True)複製程式碼
Accuracy on Test-Set: 98.4% (9842 / 10000)
Example errors:
Confusion Matrix:
[[ 974 0 0 0 0 1 2 0 2 1]
[ 0 1127 2 2 0 0 1 0 3 0]
[ 4 4 1012 4 1 0 0 3 4 0]
[ 0 0 1 1005 0 2 0 0 2 0]
[ 1 0 1 0 961 0 2 0 3 14]
[ 2 0 1 6 0 880 1 0 1 1]
[ 4 2 0 1 3 4 942 0 2 0]
[ 1 1 8 6 1 0 0 994 1 16]
[ 6 0 1 4 1 1 1 2 952 6]
[ 3 3 0 3 2 2 0 0 1 995]]
現在卷積權重是經過優化的。將這些與上面的隨機權重進行對比。它們看起來基本相同。實際上,一開始我以為程式有bug,因為優化前後的權重看起來差不多。
但儲存影像,並排著比較它們(你可以右鍵儲存)。你會發現兩者有細微的不同。
平均值和標準差也有一點變化,因此優化過的權重肯定是不一樣的。
plot_conv_weights(weights=weights_conv1)複製程式碼
Mean: 0.02895, Stdev: 0.29949
再次初始化變數
再一次用隨機值來初始化所有神經網路變數。
init_variables()複製程式碼
這意味著神經網路又是完全隨機地對圖片進行分類,由於只是隨機的猜測所以分類準確率很低。
print_test_accuracy()複製程式碼
Accuracy on Test-Set: 13.4% (1341 / 10000)
卷積權重看起來應該與上面的不同。
plot_conv_weights(weights=weights_conv1)複製程式碼
Mean: -0.01086, Stdev: 0.28023
恢復最好的變數
重新載入在優化過程中儲存到檔案的所有變數。
saver.restore(sess=session, save_path=save_path)複製程式碼
使用之前儲存的那些變數,分類準確率又提高了。
注意,準確率與之前相比可能會有細微的上升或下降,這是由於檔案裡的變數是用來最大化驗證集上的分類準確率,但在儲存檔案之後,又進行了1000次的優化迭代,因此這是兩組有輕微不同的變數的結果。有時這會導致測試集上更好或更差的表現。
print_test_accuracy(show_example_errors=True,
show_confusion_matrix=True)複製程式碼
Accuracy on Test-Set: 98.3% (9826 / 10000)
Example errors:
Confusion Matrix:
[[ 973 0 0 0 0 0 2 0 3 2]
[ 0 1124 2 2 0 0 3 0 4 0]
[ 2 1 1027 0 0 0 0 1 1 0]
[ 0 0 1 1005 0 2 0 0 2 0]
[ 0 0 3 0 968 0 1 0 3 7]
[ 2 0 1 9 0 871 3 0 3 3]
[ 4 2 1 0 3 3 939 0 6 0]
[ 1 3 19 11 2 0 0 972 2 18]
[ 6 0 3 5 1 0 1 2 951 5]
[ 3 3 0 1 4 1 0 0 1 996]]
卷積權重也與之前顯示的圖幾乎相同,同樣,由於多做了1000次優化迭代,二者並非完全一樣。
plot_conv_weights(weights=weights_conv1)複製程式碼
Mean: 0.02792, Stdev: 0.29822
關閉TensorFlow會話
現在我們已經用TensorFlow完成了任務,關閉session,釋放資源。
# This has been commented out in case you want to modify and experiment
# with the Notebook without having to restart it.
# session.close()複製程式碼
總結
這篇教程描述了在TensorFlow中如何儲存並恢復神經網路的變數。它有許多用處。比如,當你用神經網路來識別影像的時候,只需要訓練網路一次,然後可以在其他電腦上完成開發工作。
checkpoint的另一個用處是,如果你有一個非常大的神經網路和資料集,就可能會在中間儲存一些checkpoints來避免電腦當機,這樣,你就可以在最近的checkpoint開始優化而不是重頭開始。
本教程也展示瞭如何用驗證集來進行所謂的Early Stopping,如果沒有降低驗證錯誤優化就會終止。這在神經網路出現過擬合以及開始學習訓練集中的噪聲時很有用;不過這在本教程的神經網路和MNIST資料集中並不是什麼大問題。
還有一個有趣的現象,最優化時卷積權重(或者叫濾波)的變化很小,即使網路的效能從隨機猜測提高到近乎完美的分類。奇怪的是隨機的權重好像已經足夠好了。你認為為什麼會有這種現象?
練習
下面使一些可能會讓你提升TensorFlow技能的一些建議練習。為了學習如何更合適地使用TensorFlow,實踐經驗是很重要的。
在你對這個Notebook進行修改之前,可能需要先備份一下。
- 在經過1000次迭代而效能沒有提升時,優化就終止了。這樣夠嗎?你能想出一個更好地進行Early Stopping的方法麼?試著實現它。
- 如果checkpoint檔案已經存在了,載入它而不是做優化。
- 每100次優化迭代儲存一次checkpoint。通過
saver.latest_checkpoint()
取回最新的(儲存點)。為什麼儲存多個checkpoints而不是隻儲存最近的一個? - 試著改變神經網路,比如新增其他層。當你從不同的網路中重新載入變數會出現什麼問題?
- 用
plot_conv_weights()
函式在優化前後畫出第二個卷積層的權重。它們幾乎相同的麼? - 你認為優化過的卷積權重為什麼與隨機初始化的(權重)幾乎相同?
- 不看原始碼,自己重寫程式。
- 向朋友解釋程式如何工作。