【TensorFlow學習筆記】2:基本使用流程和使用檢查點,按照時間自動管理檢查點
學習《深度學習之TensorFlow》時的一些實踐。
TF的基本使用
對於分類問題的特徵X和標籤Y,分別定義tf.placeholder
,這是計算圖輸入資料的入口。
對於模型中的引數(注意不是超引數),如往往是權向量w和偏置b,定義tf.Variable
,並傳入初始的值,模型訓練就是在改變這些引數的值。
定義前向結構,即計算圖中,特徵X以及前面的引數經過怎樣的運算結合得到標籤Y的預測值Z。
定義損失函式,它是基於Y和Z的一個具體的計算過程。
定義優化器,這是一個反向過程。因為是針對損失來做優化的,所以上一步定義的損失要傳入這個優化器裡。
定義tf.Variable
啟動Session。
-
通過Session的
run()
方法啟動初始化過程。 -
對於每個epoch,通過Session的
run()
方法啟動優化器來訓練模型引數。 -
上一步迴圈結束後,訓練過程也就結束了,可以通過Session的
run()
方法來檢視損失、預測值Z等。
關閉Session。
要注意,除了啟動初始化過程,其餘的
run
過程都需要通過引數feed_dict
將具體的X傳入,對於訓練過程和計算損失的過程,還需要將Y傳入。
在訓練完成後,
tf.Variable
如w和b是可以直接訪問的,因為這決定了訓練好的模型,而損失loss、預測值Z等都是依賴於輸入的X和Y的,需要feed_dict
將其傳入。
一些小測試
定義常量
import tensorflow as tf
a = tf.constant(3)
b = tf.constant(4)
with tf.Session() as sess:
print("相加: %i" % sess.run(a + b))
print("相乘: %i" % sess.run(a * b))
相加: 7
相乘: 12
計算圖中的運算結點
import tensorflow as tf
a = tf.placeholder(tf.int16)
b = tf.placeholder(tf.int16)
add = tf.add(a, b)
mul = tf.multiply(a, b)
with tf.Session() as sess:
print("相加: %i" % sess.run(add, feed_dict={a: 3, b: 4}))
print("相乘: %i" % sess.run(mul, feed_dict={a: 3, b: 4}))
print("一起取出多個結點:", sess.run([add, mul], feed_dict={a: 4, b: 5}))
取出檢查點檔案中的模型引數
這個是在下面的例子生成的檢查點檔案的基礎上的。
from tensorflow.python.tools.inspect_checkpoint import print_tensors_in_checkpoint_file
savedir = "../z3/"
# 預設情況下,Saver使用tf.Variable.name屬性來儲存變數
# 這裡用print_tensors_in_checkpoint_file()輸出其中所有變數和它的值
print_tensors_in_checkpoint_file(savedir + "linermodel.ckpt-18", tensor_name=None, all_tensors=True)
tensor_name: bias
[-0.05547838]
tensor_name: weight
[2.0291195]
擬合線性迴歸模型並使用檢查點
除了上面的流程外,這裡選擇了在epoch為偶數時將模型儲存在檢查點檔案中。之所以這樣做(而不是在訓練完成再儲存),是因為訓練模型時可能因為各種原因中斷訓練,用這種方式至少可以確保記錄了中間的結果,即使訓練中途崩了,下次也不必重新訓練,只要拿出模型繼續訓練就好了。
注意38行的saver = tf.train.Saver(max_to_keep=1)
,這可以保證只儲存一個檢查點檔案,這樣每次就會覆蓋掉之前儲存的,而只保留最新的。
在程式的最後從檢查點中讀取了模型,並做了預測。這裡發現和模型訓練完後的預測值不一樣,因為這個程式裡最後一次儲存檢查點並不是模型訓練完的那一次(少了一次,epoch實際是0~19而在18時做了最後一次儲存)。
import tensorflow as tf
import numpy as np
from matplotlib import pyplot as plt
import math
# 生成樣本,y=2x噪聲上下0.1
X_train = np.linspace(-1, 1, 100)
Y_train = 2 * X_train + np.random.randn(*X_train.shape) * 0.2 - 0.1
# plt.plot(X_train, y_train, 'ro', label="原始資料")
# plt.legend()
# plt.show()
"""建立模型"""
# 佔位符
X = tf.placeholder("float")
Y = tf.placeholder("float")
# 模型引數
W = tf.Variable(tf.random_normal([1]), name="weight") # 一維的權重w_1,初始化為-1到1的隨機數
b = tf.Variable(tf.zeros([1]), name="bias") # 一維的偏置b,初始化為0
# (1)前向結構:通過正向生成一個結果
Z = tf.multiply(X, W) + b
# (2)定義損失的計算:這裡就是所有的y和z的差的平方的平均值
# reduce_mean用於計算指定axis的平均值,未指定時則對tensor中每個數加起來求平均
cost = tf.reduce_mean(tf.square(Y - Z)) # <class 'tensorflow.python.framework.ops.Tensor'>
# (3)反向優化:通過反向過程調整模型引數.這裡使用學習率為0.01的梯度下降最小化損失cost
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(cost)
"""迭代訓練模型"""
# 初始化過程:初始化所有的變數
init = tf.global_variables_initializer()
# 模型的超引數,這裡epoch是模型會完整學習多少次樣本
train_epochs = 20
# 這個僅僅是控制每多少個epoch顯示下模型的詳細資訊
display_step = 2
# tf.train.Saver用於儲存模型到檔案/從檔案中取用模型
# 使用了檢查點,這裡指定max_to_keep=1即在迭代過程中只儲存一個檔案
# 那麼在迴圈訓練中,新生成的模型會覆蓋之前的模型
saver = tf.train.Saver(max_to_keep=1)
# 啟動Session,這種方式不用手動關閉Session
with tf.Session() as sess:
# 執行初始化過程
sess.run(init)
# 用於記錄批次和損失
plotdata = {"ephch": [], "loss": []}
# 向模型輸入資料,對於每個epoch
for epoch in range(train_epochs):
# 都要遍歷模型中所有的樣本(x,y)
for (x, y) in zip(X_train, Y_train):
# 執行優化器,填充模型中X,Y佔位符的內容為這個具體的樣本(x,y)
sess.run(optimizer, feed_dict={X: x, Y: y})
# 每display_step個epoch顯示一下訓練中的詳細資訊
if epoch % display_step == 0:
# 計算下損失.每次要計算中間變數的當前值時候都要sess.run得到
loss = sess.run(cost, feed_dict={X: X_train, Y: Y_train})
print("Epoch:", epoch, "cost=", loss, "W=", sess.run(W), "b=", sess.run(b))
# 如果損失存在,將該批次和對應損失記錄下來
if loss != "NA":
plotdata["ephch"].append(epoch)
plotdata["loss"].append(loss)
"""儲存檢查點"""
# 這裡選擇在每次輸出資訊後儲存一下檢查點,同時使用global_step記錄epoch次數
saver.save(sess, "./linermodel.ckpt", global_step=epoch)
print("完成,cost=", sess.run(cost, feed_dict={X: X_train, Y: Y_train}), "W=", sess.run(W), "b=", sess.run(b))
# 提前計算出結果以在Session外也能使用,下面這種方式都可以
result = sess.run(W) * X_train + sess.run(b)
result2 = sess.run(Z, feed_dict={X: X_train})
# 這裡驗證一下它們中對應項的值是相等的
for k1, k2 in zip(result, result2):
assert math.isclose(k1, k2, rel_tol=1e-5)
"""使用模型"""
# 如果要使用模型,直接將樣本的值傳入並計算輸出Z即可
print("對x=5的預測值:", sess.run(Z, feed_dict={X: 5}))
"""儲存模型到檔案"""
# saver.save(sess, "./linermodel.ckpt")
"""訓練模型視覺化"""
plt.scatter(X_train, Y_train, c='r', marker='o', label="原始資料")
plt.plot(X_train, result, label="擬合直線")
plt.legend()
plt.show()
def moving_average(a, w=10):
"""
對損失序列a,生成w-平均損失序列
即每個位置的損失由其和其前的共w個損失的平均來代替
"""
if len(a) < w: # 當w太小不足以計算任何元素的平均時
return a[:] # 直接返回a的複製
return [val if idx < w else sum(a[idx - w:idx]) / w for idx, val in enumerate(a)]
"""繪製平均loss變化曲線"""
plotdata["avgloss"] = moving_average(plotdata["loss"])
plt.plot(plotdata["ephch"], plotdata["avgloss"], 'b--')
plt.xlabel("ephch")
plt.ylabel("avg loss")
plt.title("平均損失變化")
plt.show()
"""從檔案載入模型"""
with tf.Session() as sess2:
sess2.run(init) # 還是需要執行一下初始化過程
# saver.restore(sess2, "./linermodel.ckpt")
# 在當前目錄下尋找最近的檢查點並載入
ckpt = tf.train.latest_checkpoint("./")
if ckpt is not None:
saver.restore(sess2, ckpt)
print("對x=5的預測值:", sess2.run(Z, feed_dict={X: 5}))
Epoch: 0 cost= 0.1917328 W= [1.4377091] b= [0.12655073]
Epoch: 2 cost= 0.055657808 W= [1.871314] b= [0.00432279]
Epoch: 4 cost= 0.04542881 W= [1.9882357] b= [-0.03980212]
Epoch: 6 cost= 0.044927694 W= [2.0185487] b= [-0.05142205]
Epoch: 8 cost= 0.044943742 W= [2.0263882] b= [-0.05443016]
Epoch: 10 cost= 0.044957623 W= [2.028414] b= [-0.05520765]
Epoch: 12 cost= 0.044961873 W= [2.0289385] b= [-0.05540875]
Epoch: 14 cost= 0.044963013 W= [2.029074] b= [-0.05546085]
Epoch: 16 cost= 0.044963315 W= [2.0291097] b= [-0.05547457]
Epoch: 18 cost= 0.044963405 W= [2.0291195] b= [-0.05547838]
完成,cost= 0.04496341 W= [2.0291212] b= [-0.05547896]
對x=5的預測值: [10.090127]
對x=5的預測值: [10.090119]
按照時間自動管理檢查點
書上用一個全域性張量每次自增1為例。
import tensorflow as tf
'''
按照時間儲存檢查點
'''
# 清除預設圖堆疊,重置全域性的預設圖
tf.reset_default_graph()
# 建立(如果需要的話)並返回global step tensor,預設引數graph=None即使用預設圖
# 當用這種方法按照時間儲存檢查點時,必須要定義這個global step張量
global_step = tf.train.get_or_create_global_step()
# 這裡是為global_step張量每次增加1
step = tf.assign_add(global_step, 1)
# 通過MonitoredTrainingSession實現按時間自動儲存,設定檢查點儲存目錄,設定儲存間隔為2秒
with tf.train.MonitoredTrainingSession(checkpoint_dir='log/checkpoints', save_checkpoint_secs=2) as sess:
# 輸出一下當前global_step這個張量的值
print(sess.run([global_step]))
# 這裡是一個死迴圈,只要sess不結束就一直迴圈
while not sess.should_stop():
# 執行step,即為global_step這個張量增加1
i = sess.run(step)
# 每次執行都輸出一下
print(i)
多次執行可以發現數字(global_step這個張量的值)並不會從頭開始,而是因為每2秒儲存一次,從上次儲存的檢查點開始。
如某次的執行結果如下: