這是本人在知乎上翻譯的文章, 陸續更新中,如今搬運到掘金上來。
在這一系列文章中,你將學到深度學習的一些基本概念以及TensorFlow的使用,並完成手寫體數字識別、圖像分類、遷移學習、Deep Dream、風格遷移和強化學習等項目。 github上的Python NoteBook也能夠很方便的調試代碼。python
總而言之, 一份很讚的入門教程。歡迎分享/關注/訂閱。git
不得不說,掘金支持Markdown真是方便多了。向Aaron Swartz致敬。github
by Magnus Erik Hvass Pedersen / GitHub / Videos on YouTube
中文翻譯 thrillerist /Github編程
若有轉載,請附上本文連接。session
這份教程示範了在TensorFlow中使用一個簡單線性模型的工做流程。在載入稱爲MNIST的手寫數字圖片數據集後,咱們在TensorFlow中定義並優化了一個數學模型。(咱們)會畫出結果並展開討論。 機器學習
你應該熟悉基本的線性代數,Python和Jupyter Notebook編輯器。若是你對機器學習和分類有基本的理解也頗有幫助。編輯器
%matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
from sklearn.metrics import confusion_matrix複製代碼
使用Python3.5.2(Anaconda)開發,TensorFlow版本是:ide
tf.__version__複製代碼
'0.12.0-rc1'函數
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編碼的方式載入。這意味着標籤從一個單獨的數字轉換成一個長度等於全部可能類別數量的向量。向量中除了第$i$個元素是1,其餘元素都是0,這表明着它的類別是$i$'。好比,前面五張圖像標籤的One-Hot編碼爲:
data.test.labels[0:5, :]複製代碼
array([[ 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
[ 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.]])
在不一樣的比較和度量性能時,咱們也須要用單獨的數字表示類別,所以咱們經過取最大元素的索引,將One-Hot編碼的向量轉換成一個單獨的數字。需注意的是'class'在Python中是一個關鍵字,因此咱們用'cls'代替它。
data.test.cls = np.array([label.argmax() for label in data.test.labels])複製代碼
如今咱們能夠看到測試集中前面五張圖像的類別。將這些與上面的One-Hot編碼的向量進行比較。例如,第一張圖像的類別是7,對應的在One-Hot編碼向量中,除了第7個元素其餘都爲零。
data.test.cls[0:5]複製代碼
array([7, 2, 1, 0, 4])
在下面的源碼中,有不少地方用到了數據維度。在計算機編程中,一般來講最好使用變量和常量,而不是在每次使用數值時寫硬代碼。這意味着數字只須要在一個地方改動就行。這些最好能從讀取的數據中獲取,但這裏咱們直接寫上數值。
# 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 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])
ax.set_xlabel(xlabel)
# Remove ticks from the plot.
ax.set_xticks([])
ax.set_yticks([])複製代碼
# 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的所有目的就是使用一個稱之爲計算圖(computational graph)的東西,它會比直接在Python中進行相同計算量要高效得多。TensorFlow比Numpy更高效,由於TensorFlow瞭解整個須要運行的計算圖,然而Numpy只知道某個時間點上惟一的數學運算。
TensorFlow也可以自動地計算須要優化的變量的梯度,使得模型有更好的表現。這是因爲Graph是簡單數學表達式的結合,所以整個圖的梯度能夠用鏈式法則推導出來。
TensorFlow還能利用多核CPU和GPU,Google也爲TensorFlow製造了稱爲TPUs(Tensor Processing Units)的特殊芯片,它比GPU更快。
一個TensorFlow圖由下面幾個部分組成,後面會詳細描述:
另外,TensorFlow圖也包含了一些調試狀態,好比用TensorBoard打印log數據,本教程不涉及這些。
Placeholder是做爲圖的輸入,每次咱們運行圖的時候均可能會改變它們。將這個過程稱爲feeding placeholder變量,後面將會描述它。
首先咱們爲輸入圖像定義placeholder變量。這讓咱們能夠改變輸入到TensorFlow圖中的圖像。這也是一個張量(tensor),表明一個多維向量或矩陣。數據類型設置爲float32
,形狀設爲[None, img_size_flat]
,None
表明tensor可能保存着任意數量的圖像,每張圖象是一個長度爲img_size_flat
的向量。
x = tf.placeholder(tf.float32, [None, img_size_flat])複製代碼
接下來咱們爲輸入變量x
中的圖像所對應的真實標籤訂義placeholder變量。變量的形狀是[None, num_classes]
,這表明着它保存了任意數量的標籤,每一個標籤是長度爲num_classes
的向量,本例中長度爲10。
y_true = tf.placeholder(tf.float32, [None, num_classes])複製代碼
最後咱們爲變量x
中圖像的真實類別定義placeholder變量。它們是整形,而且這個變量的維度設爲[None]
,表明placeholder變量是任意長的一維向量。
y_true_cls = tf.placeholder(tf.int64, [None])複製代碼
除了上面定義的那些給模型輸入數據的變量以外,TensorFlow還須要改變一些模型變量,使得訓練數據的表現更好。
第一個須要優化的變量稱爲權重weight
,TensorFlow變量須要被初始化爲零,它的形狀是[img_size_flat, num_classes]
,所以它是一個img_size_flat
行、num_classes
列的二維張量(或矩陣)。
weights = tf.Variable(tf.zeros([img_size_flat, num_classes]))複製代碼
第二個須要優化的是誤差變量biases
,它被定義成一個長度爲num_classes
的1維張量(或向量)。
biases = tf.Variable(tf.zeros([num_classes]))複製代碼
這個最基本的數學模型將placeholder變量x
中的圖像與權重weight
相乘,而後加上誤差biases
。
結果是大小爲[num_images, num_classes]
的一個矩陣,因爲x
的形狀是[num_images, img_size_flat]
而且 weights
的形狀是[img_size_flat, num_classes]
,所以兩個矩陣乘積的形狀是[num_images, num_classes]
,而後將biases
向量添加到矩陣每一行中。
logits = tf.matmul(x, weights) + biases複製代碼
如今logits
是一個 num_images
行num_classes
列的矩陣,第$i$行第$j$列的那個元素表明着第$i$張輸入圖像有多大可能性是第$j$個類別。
然而,這是很粗略的估計而且很難解釋,由於數值可能很小或很大,所以咱們想要對它們作歸一化,使得logits
矩陣的每一行相加爲1,每一個元素限制在0到1之間。這是用一個稱爲softmax的函數來計算的,結果保存在y_pred
中。
y_pred = tf.nn.softmax(logits)複製代碼
能夠從y_pred
矩陣中取每行最大元素的索引值,來獲得預測的類別。
y_pred_cls = tf.argmax(y_pred, dimension=1)複製代碼
爲了使模型更好地對輸入圖像進行分類,咱們必須改變weights
和biases
變量。首先咱們須要比較模型的預測輸出y_pred
和指望輸出y_true
,來了解目前模型的性能如何。
交叉熵(cross-entropy)是一個在分類中使用的性能度量。交叉熵是一個常爲正值的連續函數,若是模型的預測值精準地符合指望的輸出,它就等於零。所以,優化的目的就是最小化交叉熵,經過改變模型中weights
和biases
的值,使交叉熵越接近零越好。
TensorFlow有一個內置的計算交叉熵的函數。須要注意的是它使用logits
的值,由於在它內部也計算了softmax。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits,
labels=y_true)複製代碼
如今,咱們已經爲每一個圖像分類計算了交叉熵,因此有一個當前模型在每張圖上的性能度量。可是爲了用交叉熵來指導模型變量的優化,咱們須要一個額外的標量值,所以咱們簡單地利用全部圖像分類交叉熵的均值。
cost = tf.reduce_mean(cross_entropy)複製代碼
如今,咱們有一個須要被最小化的損失度量,接着咱們能夠建立優化器。在這種狀況中,用的是梯度降低的基本形式,步長設爲0.5。
優化過程並非在這裏執行。實際上,還沒計算任何東西,咱們只是往TensorFlow圖中添加了優化器,以便以後的操做。
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.5).minimize(cost)複製代碼
咱們須要另一些性能度量,來向用戶展現這個過程。
這是一個布爾值向量,表明預測類型是否等於每張圖片的真實類型。
correct_prediction = tf.equal(y_pred_cls, y_true_cls)複製代碼
上面先將布爾值向量類型轉換成浮點型向量,這樣子False就變成0,True變成1,而後計算這些值的平均數,以此來計算分類的準確度。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))複製代碼
一旦建立了TensorFlow圖,咱們須要建立一個TensorFlow session,用來運行圖。
session = tf.Session()複製代碼
咱們須要在開始優化weights
和biases
變量以前對它們進行初始化。
session.run(tf.global_variables_initializer())複製代碼
在訓練集中有50,000張圖。用這些圖像計算模型的梯度會花不少時間。所以咱們利用隨機梯度降低的方法,它在優化器的每次迭代裏只用到了一小部分的圖像。
batch_size = 100複製代碼
函數執行了屢次的優化迭代來逐步地提高模型的weights
和biases
。在每次迭代中,從訓練集中選擇一批新的數據,而後TensorFlow用這些訓練樣原本執行優化器。
def optimize(num_iterations):
for i in range(num_iterations):
# 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(batch_size)
# Put the batch into a dict with the proper names
# for placeholder variables in the TensorFlow graph.
# Note that the placeholder for y_true_cls is not set
# because it is not used during training.
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)複製代碼
測試集數據字典被當作TensorFlow圖的輸入。注意,在TensorFlow圖中,placeholder變量必須使用正確的名字。
feed_dict_test = {x: data.test.images,
y_true: data.test.labels,
y_true_cls: data.test.cls}複製代碼
用來打印測試集分類準確度的函數。
def print_accuracy():
# Use TensorFlow to compute the accuracy.
acc = session.run(accuracy, feed_dict=feed_dict_test)
# Print the accuracy.
print("Accuracy on test-set: {0:.1%}".format(acc))複製代碼
函數用scikit-learn打印並繪製混淆矩陣。
def print_confusion_matrix():
# Get the true classifications for the test-set.
cls_true = data.test.cls
# Get the predicted classifications for the test-set.
cls_pred = session.run(y_pred_cls, feed_dict=feed_dict_test)
# 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.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
# Make various adjustments to the plot.
plt.tight_layout()
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')複製代碼
繪製測試集中誤分類圖像的函數。
def plot_example_errors():
# Use TensorFlow to get a list of boolean values
# whether each test-image has been correctly classified,
# and a list for the predicted class of each image.
correct, cls_pred = session.run([correct_prediction, y_pred_cls],
feed_dict=feed_dict_test)
# 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])複製代碼
這個函數用來繪製模型的權重weights
。畫了10張圖像,訓練模型所識別出的每一個數字對應着一張圖。
def plot_weights():
# Get the values for the weights from the TensorFlow variable.
w = session.run(weights)
# 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)
# Create figure with 3x4 sub-plots,
# where the last 2 sub-plots are unused.
fig, axes = plt.subplots(3, 4)
fig.subplots_adjust(hspace=0.3, wspace=0.3)
for i, ax in enumerate(axes.flat):
# Only use the weights for the first 10 sub-plots.
if i<10:
# Get the weights for the i'th digit and reshape it.
# Note that w.shape == (img_size_flat, 10)
image = w[:, i].reshape(img_shape)
# Set the label for the sub-plot.
ax.set_xlabel("Weights: {0}".format(i))
# Plot the image.
ax.imshow(image, vmin=w_min, vmax=w_max, cmap='seismic')
# Remove ticks from each sub-plot.
ax.set_xticks([])
ax.set_yticks([])複製代碼
測試集上的準確度是9.8%。這是因爲模型只作了初始化,並沒作任何優化,因此它一般將圖像預測成數字零,正以下面繪製的圖像那樣,恰好測試集中9.8%的圖像是數字零。
print_accuracy()複製代碼
Accuracy on test-set: 9.8%
plot_example_errors()複製代碼
在完成一次迭代優化以後,模型在測試集上的準確率從9.8%提升到了40.7%。這意味着它大約10次裏面會誤分類6次,正以下面所顯示的。
optimize(num_iterations=1)複製代碼
print_accuracy()複製代碼
Accuracy on test-set: 40.7%
plot_example_errors()複製代碼
下面繪製的是權重。正值爲紅色,負值爲藍色。這些權重能夠直觀地理解爲圖像濾波器。
例如,權重用來肯定一張數字零的圖像對圓形圖像有正反應(紅色),對圓形圖像的中間部分有負反應(藍色)。
相似的,權重也用來肯定一張數字一的圖像對圖像中心垂直線段有正反應(紅色),對線段周圍有負反應(藍色)。
注意到權重大多看起來跟它要識別的數字很像。這是由於只作了一次迭代,即權重只在100張圖像上訓練。等通過上千張圖像的訓練以後,權重會變得更難分辨,由於它們須要識別出數字的許多種書寫方法。
plot_weights()複製代碼
# We have already performed 1 iteration.
optimize(num_iterations=9)複製代碼
print_accuracy()複製代碼
Accuracy on test-set: 78.2%
plot_example_errors()複製代碼
plot_weights()複製代碼
在迭代了1000次以後,模型在十次裏面大約只誤識別了一次。以下圖所示,有些誤識別情有可原,由於即便在人類眼裏,也很難肯定圖像(的數字),然而有一些圖像是很明顯的,好的模型應該能分辨出來。但這個簡單的模型沒法達到更好的性能,所以須要更爲複雜的模型。
# We have already performed 10 iterations.
optimize(num_iterations=990)複製代碼
print_accuracy()複製代碼
Accuracy on test-set: 91.7%
plot_example_errors()複製代碼
模型通過了1000次迭代訓練,每次迭代用到訓練集裏面的100張圖像。因爲圖像的多樣化,如今權重變得很難辨認,咱們可能會懷疑這些權重是否真的理解數字是怎麼由線條組成的,或者模型只是記住了許多不一樣的像素。
plot_weights()複製代碼
print_confusion_matrix()複製代碼
[[ 957 0 3 2 0 5 11 1 1 0]
[ 0 1108 2 2 1 2 4 2 14 0]
[ 4 9 914 19 15 5 13 14 35 4]
[ 1 0 16 928 0 28 2 14 13 8]
[ 1 1 3 2 939 0 10 2 6 18]
[ 10 3 3 33 10 784 17 6 19 7]
[ 8 3 3 2 11 14 915 1 1 0]
[ 3 9 21 9 7 1 0 959 2 17]
[ 8 8 8 38 11 40 14 18 825 4]
[ 11 7 1 13 75 13 1 39 4 845]]
# This has been commented out in case you want to modify and experiment
# with the Notebook without having to restart it.
# session.close()複製代碼
下面是一些可能會讓你提高TensorFlow技能的一些建議練習。爲了學習如何更合適地使用TensorFlow,實踐經驗是很重要的。
在你對這個Notebook進行修改以前,可能須要先備份一下。
AdagradOptimizer
或 AdamOptimizer
。plot_example_errors()
函數,使它打印誤分類的 logits
和y_pred
值。sparse_softmax_cross_entropy_with_logits
代替 softmax_cross_entropy_with_logits
。這可能須要改變代碼的多個地方。探討使用這兩中方法的優缺點。