三分鐘快速上手TensorFlow 2.0 (中)——經常使用模塊和模型的部署

本文學習筆記參照來源:https://tf.wiki/zh/basic/basic.htmlhtml

前文:三分鐘快速上手TensorFlow 2.0 (上)——前置基礎、模型創建與可視化

 

tf.train.Checkpoint :變量的保存與恢復

只保存模型的參數,不保存模型的計算過程python

須要導出模型(無需源代碼也能運行模型),請參考 SavedModelapi

可使用其 save() 和 restore() 方法將 TensorFlow 中全部包含 Checkpointable State 的對象進行保存和恢復。數組

tf.keras.optimizer 、 tf.Variable 、 tf.keras.Layer 或者 tf.keras.Model 實例均可以被保存。瀏覽器

checkpoint = tf.train.Checkpoint(model=model)
聲明一個checkpoint
接受的初始化參數比較特殊,是一個 **kwargs 。具體而言,是一系列的鍵值對,鍵名能夠隨意取,值爲須要保存的對象
舉例, 保存一個繼承 tf.keras.Model 的模型實例 model 和一個繼承 tf.train.Optimizer 的優化器 optimizer
myAwesomeModel 是咱們爲待保存的模型 model 所取的任意鍵名。注意,在恢復變量的時候,咱們還將使用這一鍵
 
checkpoint.save(save_path_with_prefix)
當模型訓練完成須要保存的時候

save()    輸入的是保存文件的目錄 + 前綴。好比'./save/model.ckpt'緩存

model_to_be_restored = MyModel()                                        # 待恢復參數的同一模型
checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored)   # 鍵名保持爲「myAwesomeModel」
checkpoint.restore(save_path_with_prefix_and_index)
View Code爲模型從新載入以前保存的參數時,須要再次實例化一個 checkpoint,同時保持鍵名的一致。再調用 checkpoint 的 restore 方法
restore()
save_path_with_prefix_and_index 是以前保存的文件的目錄 + 前綴 + 編號。例如,調用 checkpoint.restore('./save/model.ckpt-1') 就能夠載入前綴爲 model.ckpt ,序號爲 1 的文件來恢復模型。
載入最近的一個。可使用 tf.train.latest_checkpoint(save_path) 這個輔助函數返回目錄下最近一次 checkpoint 的文件名。
 
總框架
# train.py 模型訓練階段

model = MyModel()
# 實例化Checkpoint,指定保存對象爲model(若是須要保存Optimizer的參數也可加入)
checkpoint = tf.train.Checkpoint(myModel=model)
# ...(模型訓練代碼)
# 模型訓練完畢後將參數保存到文件(也能夠在模型訓練過程當中每隔一段時間就保存一次)
checkpoint.save('./save/model.ckpt')
保存變量
# test.py 模型使用階段

model = MyModel()
checkpoint = tf.train.Checkpoint(myModel=model)             # 實例化Checkpoint,指定恢復對象爲model
checkpoint.restore(tf.train.latest_checkpoint('./save'))    # 從文件恢復模型參數
# 模型使用代碼
恢復變量

 

import tensorflow as tf
import numpy as np
import argparse
from zh.model.mnist.mlp import MLP
from zh.model.utils import MNISTLoader

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('--mode', default='train', help='train or test')
parser.add_argument('--num_epochs', default=1)
parser.add_argument('--batch_size', default=50)
parser.add_argument('--learning_rate', default=0.001)
args = parser.parse_args()
data_loader = MNISTLoader()


def train():
    model = MLP()
    optimizer = tf.keras.optimizers.Adam(learning_rate=args.learning_rate)
    num_batches = int(data_loader.num_train_data // args.batch_size * args.num_epochs)
    checkpoint = tf.train.Checkpoint(myAwesomeModel=model)      # 實例化Checkpoint,設置保存對象爲model
    for batch_index in range(1, num_batches+1):                 
        X, y = data_loader.get_batch(args.batch_size)
        with tf.GradientTape() as tape:
            y_pred = model(X)
            loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
            loss = tf.reduce_mean(loss)
            print("batch %d: loss %f" % (batch_index, loss.numpy()))
        grads = tape.gradient(loss, model.variables)
        optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
        if batch_index % 100 == 0:                              # 每隔100個Batch保存一次
            path = checkpoint.save('./save/model.ckpt')         # 保存模型參數到文件
            print("model saved to %s" % path)


def test():
    model_to_be_restored = MLP()
    # 實例化Checkpoint,設置恢復對象爲新創建的模型model_to_be_restored
    checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored)      
    checkpoint.restore(tf.train.latest_checkpoint('./save'))    # 從文件恢復模型參數
    y_pred = np.argmax(model_to_be_restored.predict(data_loader.test_data), axis=-1)
    print("test accuracy: %f" % (sum(y_pred == data_loader.test_label) / data_loader.num_test_data))


if __name__ == '__main__':
    if args.mode == 'train':
        train()
    if args.mode == 'test':
        test()
之前文的 多層感知機模型 爲例展現模型變量的保存和載入
在代碼目錄下創建 save 文件夾並運行代碼進行訓練後,save 文件夾內將會存放每隔 100 個 batch 保存一次的模型變量數據。在命令行參數中加入 --mode=test 並再次運行代碼,將直接使用最後一次保存的變量值恢復模型並在測試集上測試模型性能
 
 
使用 TensorFlow 的 tf.train.CheckpointManager能夠完成
  • 保留最後的幾個 Checkpoint;app

  • Checkpoint 默認從 1 開始編號,每次累加 1,但咱們可能但願使用別的編號方式(例如使用當前 Batch 的編號做爲文件編號)。框架

checkpoint = tf.train.Checkpoint(model=model)
manager = tf.train.CheckpointManager(checkpoint, directory='./save', checkpoint_name='model.ckpt', max_to_keep=k)
在定義 Checkpoint 後接着定義一個 CheckpointManager

directory 參數爲文件保存的路徑, checkpoint_name 爲文件名前綴(不提供則默認爲 ckpt ), max_to_keep 爲保留的 Checkpoint 數目。ide

在須要保存模型的時候,咱們直接使用 manager.save() 便可。若是咱們但願自行指定保存的 Checkpoint 的編號,則能夠在保存時加入 checkpoint_number 參數。例如 manager.save(checkpoint_number=100) 。函數

import tensorflow as tf
import numpy as np
import argparse
from zh.model.mnist.mlp import MLP
from zh.model.utils import MNISTLoader

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('--mode', default='train', help='train or test')
parser.add_argument('--num_epochs', default=1)
parser.add_argument('--batch_size', default=50)
parser.add_argument('--learning_rate', default=0.001)
args = parser.parse_args()
data_loader = MNISTLoader()


def train():
    model = MLP()
    optimizer = tf.keras.optimizers.Adam(learning_rate=args.learning_rate)
    num_batches = int(data_loader.num_train_data // args.batch_size * args.num_epochs)
    checkpoint = tf.train.Checkpoint(myAwesomeModel=model)      
    # 使用tf.train.CheckpointManager管理Checkpoint
    manager = tf.train.CheckpointManager(checkpoint, directory='./save', max_to_keep=3)
    for batch_index in range(1, num_batches):
        X, y = data_loader.get_batch(args.batch_size)
        with tf.GradientTape() as tape:
            y_pred = model(X)
            loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
            loss = tf.reduce_mean(loss)
            print("batch %d: loss %f" % (batch_index, loss.numpy()))
        grads = tape.gradient(loss, model.variables)
        optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
        if batch_index % 100 == 0:
            # 使用CheckpointManager保存模型參數到文件並自定義編號
            path = manager.save(checkpoint_number=batch_index)         
            print("model saved to %s" % path)


def test():
    model_to_be_restored = MLP()
    checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored)      
    checkpoint.restore(tf.train.latest_checkpoint('./save'))
    y_pred = np.argmax(model_to_be_restored.predict(data_loader.test_data), axis=-1)
    print("test accuracy: %f" % (sum(y_pred == data_loader.test_label) / data_loader.num_test_data))


if __name__ == '__main__':
    if args.mode == 'train':
        train()
    if args.mode == 'test':
        test()
實例,展現使用 CheckpointManager 限制僅保留最後三個 Checkpoint 文件,並使用 batch 的編號做爲 Checkpoint 的文件編號

 

 

 

 

TensorBoard:訓練過程可視化

summary_writer = tf.summary.create_file_writer('./tensorboard')     # 參數爲記錄文件所保存的目錄
在代碼目錄下創建一個文件夾(如 ./tensorboard )存放 TensorBoard 的記錄文件,並在代碼中實例化一個記錄器
summary_writer = tf.summary.create_file_writer('./tensorboard')
# 開始模型訓練
for batch_index in range(num_batches):
    # ...(訓練代碼,當前batch的損失值放入變量loss中)
    with summary_writer.as_default():                               # 但願使用的記錄器
        tf.summary.scalar("loss", loss, step=batch_index)
        tf.summary.scalar("MyScalar", my_scalar, step=batch_index)  # 還能夠添加其餘自定義的變量
記錄參數
經過 with 語句指定但願使用的記錄器,並對須要記錄的參數(通常是 scalar)運行 tf.summary.scalar(name, tensor, step=batch_index),便可將訓練過程當中參數在 step 時候的值記錄下來。這裏的 step 參數可根據本身的須要自行制定,通常可設置爲當前訓練過程當中的 batch 序號
tensorboard --logdir=./tensorboard
要對訓練過程可視化時,在代碼目錄打開終端(如須要的話進入 TensorFlow 的 conda 環境)
使用瀏覽器訪問命令行程序所輸出的網址(通常是 http:// 計算機名稱:6006)
tf.summary.trace_on(graph=True, profiler=True)  # 開啓Trace,能夠記錄圖結構和profile信息
# 進行訓練
with summary_writer.as_default():
    tf.summary.trace_export(name="model_trace", step=0, profiler_outdir=log_dir)    # 保存Trace信息到文件
在訓練時使用 tf.summary.trace_on 開啓 Trace,此時 TensorFlow 會將訓練時的大量信息(如計算圖的結構,每一個操做所耗費的時間等)記錄下來。
  • 若是須要從新訓練,須要刪除掉記錄文件夾內的信息並重啓 TensorBoard(或者創建一個新的記錄文件夾並開啓 TensorBoard, --logdir 參數設置爲新創建的文件夾);

  • 記錄文件夾目錄保持全英文。

import tensorflow as tf
from zh.model.mnist.mlp import MLP
from zh.model.utils import MNISTLoader

num_batches = 1000
batch_size = 50
learning_rate = 0.001
log_dir = 'tensorboard'
model = MLP()
data_loader = MNISTLoader()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
summary_writer = tf.summary.create_file_writer(log_dir)     # 實例化記錄器
tf.summary.trace_on(profiler=True)  # 開啓Trace(可選)
for batch_index in range(num_batches):
    X, y = data_loader.get_batch(batch_size)
    with tf.GradientTape() as tape:
        y_pred = model(X)
        loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
        loss = tf.reduce_mean(loss)
        print("batch %d: loss %f" % (batch_index, loss.numpy()))
        with summary_writer.as_default():                           # 指定記錄器
            tf.summary.scalar("loss", loss, step=batch_index)       # 將當前損失函數的值寫入記錄器
    grads = tape.gradient(loss, model.variables)
    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))
with summary_writer.as_default():
    tf.summary.trace_export(name="model_trace", step=0, profiler_outdir=log_dir)    # 保存Trace信息到文件(可選)
實例:查看多層感知機模型的訓練狀況

 

 

 

 

 

 

tf.data :數據集的構建與預處理

快速、高效地構建數據輸入的流水線,尤爲適用於數據量巨大的場景。

數據集對象的創建

tf.data 的核心是 tf.data.Dataset 類,提供了對數據集的高層封裝。tf.data.Dataset由一系列的可迭代訪問的元素(element)組成

import tensorflow as tf
import numpy as np

X = tf.constant([2013, 2014, 2015, 2016, 2017])
Y = tf.constant([12000, 14000, 15000, 16500, 17500])

# 也可使用NumPy數組,效果相同
# X = np.array([2013, 2014, 2015, 2016, 2017])
# Y = np.array([12000, 14000, 15000, 16500, 17500])

dataset = tf.data.Dataset.from_tensor_slices((X, Y))

for x, y in dataset:
    print(x.numpy(), y.numpy()) 
最基礎的創建 tf.data.Dataset 的方法是使用 tf.data.Dataset.from_tensor_slices() ,適用於數據量較小(可以整個裝進內存)的狀況。
按張量的第 0 維展開來構建數據集,數據集的元素數量爲張量第 0 位的大小
當提供多個張量做爲輸入時,張量的第 0 維大小必須相同,且必須將多個張量做爲元組(Tuple,即便用 Python 中的小括號)拼接並做爲輸入。
例: 載入前章的 MNIST 數據集

數據集對象的預處理

最經常使用的如:

  • Dataset.map(f) :對數據集中的每一個元素應用函數 f ,獲得一個新的數據集(這部分每每結合 tf.io 進行讀寫和解碼文件, tf.image 進行圖像處理);

  • Dataset.shuffle(buffer_size) :將數據集打亂(設定一個固定大小的緩衝區(Buffer),取出前 buffer_size 個元素放入,並從緩衝區中隨機採樣,採樣後的數據用後續數據替換);

  • Dataset.batch(batch_size) :將數據集分紅批次,即對每 batch_size 個元素,使用 tf.stack() 在第 0 維合併,成爲一個元素。

還有 Dataset.repeat() (重複數據集的元素)、 Dataset.reduce() (與 Map 相對的聚合操做)、 Dataset.take() (截取數據集中的前若干個元素)等

def rot90(image, label):
    image = tf.image.rot90(image)
    return image, label

mnist_dataset = mnist_dataset.map(rot90)

for image, label in mnist_dataset:
    plt.title(label.numpy())
    plt.imshow(image.numpy()[:, :, 0])
    plt.show(
使用 Dataset.map() 將全部圖片旋轉 90 度
mnist_dataset = mnist_dataset.batch(4)

for images, labels in mnist_dataset:    # image: [4, 28, 28, 1], labels: [4]
    fig, axs = plt.subplots(1, 4)
    for i in range(4):
        axs[i].set_title(labels.numpy()[i])
        axs[i].imshow(images.numpy()[i, :, :, 0])
    plt.show()
使用 Dataset.batch() 將數據集劃分批次,每一個批次的大小爲 4
mnist_dataset = mnist_dataset.shuffle(buffer_size=10000).batch(4)

for images, labels in mnist_dataset:
    fig, axs = plt.subplots(1, 4)
    for i in range(4):
        axs[i].set_title(labels.numpy()[i])
        axs[i].imshow(images.numpy()[i, :, :, 0])
    plt.show()
使用 Dataset.shuffle() 將數據打散後再設置批次,緩存大小設置爲 10000
  • 設定一個固定大小爲 buffer_size 的緩衝區(Buffer);

  • 初始化時,取出數據集中的前 buffer_size 個元素放入緩衝區;

  • 每次須要從數據集中取元素時,即從緩衝區中隨機採樣一個元素並取出,而後從後續的元素中取出一個放回到以前被取出的位置,以維持緩衝區的大小。

所以,緩衝區的大小須要根據數據集的特性和數據排列順序特色來進行合理的設置。好比:

  • 當 buffer_size 設置爲 1 時,其實等價於沒有進行任何打散;

  • 當數據集的標籤順序分佈極爲不均勻(例如二元分類時數據集前 N 個的標籤爲 0,後 N 個的標籤爲 1)時,較小的緩衝區大小會使得訓練時取出的 Batch 數據極可能全爲同一標籤,從而影響訓練效果。通常而言,數據集的順序分佈若較爲隨機,則緩衝區的大小可較小,不然則須要設置較大的緩衝區。

使用 tf.data 的並行化策略提升訓練流程效率

mnist_dataset=mnist_dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
使用 Dataset.prefetch() 方法進行數據預加載
參數 buffer_size 既可手工設置,也可設置爲 tf.data.experimental.AUTOTUNE 從而由 TensorFlow 自動選擇合適的數值
mnist_dataset = mnist_dataset.map(map_func=rot90, num_parallel_calls=2)
使用Dataset.map() 也能夠利用多 GPU 資源,並行化地對數據項進行變換
這裏假設用於訓練的計算機具備 2 核的 CPU,咱們但願充分利用多核心的優點對數據進行並行化變換(好比前節的旋轉 90 度函數 rot90 )

數據集元素的獲取與使用

dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...))
for a, b, c, ... in dataset:
    # 對張量a, b, c等進行操做,例如送入模型進行訓練
使用 For 循環迭代獲取數據
dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...))
it = iter(dataset)
a_0, b_0, c_0, ... = next(it)
a_1, b_1, c_1, ... = next(it)
使用 iter() 顯式建立一個 Python 迭代器並使用 next() 獲取下一個元素

Keras 支持使用 tf.data.Dataset 直接做爲輸入。當調用 tf.keras.Model 的 fit() 和 evaluate() 方法時,能夠將參數中的輸入數據 x 指定爲一個元素格式爲 (輸入數據, 標籤數據) 的 Dataset ,並忽略掉參數中的標籤數據 y 。例如,對於上述的 MNIST 數據集

model.fit(x=train_data, y=train_label, epochs=num_epochs, batch_size=batch_size)
常規的 Keras 訓練方式
model.fit(mnist_dataset, epochs=num_epochs)
使用 tf.data.Dataset 後,能夠直接傳入 Dataset
因爲已經經過 Dataset.batch() 方法劃分了數據集的批次,因此這裏也無需提供批次的大小。

 

import tensorflow as tf
import os

num_epochs = 10
batch_size = 32
learning_rate = 0.001
data_dir = 'C:/datasets/cats_vs_dogs'
train_cats_dir = data_dir + '/train/cats/'
train_dogs_dir = data_dir + '/train/dogs/'
test_cats_dir = data_dir + '/valid/cats/'
test_dogs_dir = data_dir + '/valid/dogs/'

def _decode_and_resize(filename, label):
    image_string = tf.io.read_file(filename)            # 讀取原始文件
    image_decoded = tf.image.decode_jpeg(image_string)  # 解碼JPEG圖片
    image_resized = tf.image.resize(image_decoded, [256, 256]) / 255.0
    return image_resized, label

if __name__ == '__main__':
    # 構建訓練數據集
    train_cat_filenames = tf.constant([train_cats_dir + filename for filename in os.listdir(train_cats_dir)])
    train_dog_filenames = tf.constant([train_dogs_dir + filename for filename in os.listdir(train_dogs_dir)])
    train_filenames = tf.concat([train_cat_filenames, train_dog_filenames], axis=-1)
    train_labels = tf.concat([
        tf.zeros(train_cat_filenames.shape, dtype=tf.int32), 
        tf.ones(train_dog_filenames.shape, dtype=tf.int32)], 
        axis=-1)

    train_dataset = tf.data.Dataset.from_tensor_slices((train_filenames, train_labels))
    train_dataset = train_dataset.map(
        map_func=_decode_and_resize, 
        num_parallel_calls=tf.data.experimental.AUTOTUNE)
    # 取出前buffer_size個數據放入buffer,並從其中隨機採樣,採樣後的數據用後續數據替換
    train_dataset = train_dataset.shuffle(buffer_size=23000)    
    train_dataset = train_dataset.batch(batch_size)
    train_dataset = train_dataset.prefetch(tf.data.experimental.AUTOTUNE)

    model = tf.keras.Sequential([
        tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(256, 256, 3)),
        tf.keras.layers.MaxPooling2D(),
        tf.keras.layers.Conv2D(32, 5, activation='relu'),
        tf.keras.layers.MaxPooling2D(),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(64, activation='relu'),
        tf.keras.layers.Dense(2, activation='softmax')
    ])

    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=tf.keras.losses.sparse_categorical_crossentropy,
        metrics=[tf.keras.metrics.sparse_categorical_accuracy]
    )

    model.fit(train_dataset, epochs=num_epochs)
實例:cats_vs_dogs 圖像分類
使用 tf.data 結合 tf.io 和 tf.image創建 tf.data.Dataset 數據集,並進行訓練和測試的完整過程
使用前須將數據集解壓到代碼中 data_dir 所設置的目錄(此處默認設置爲 C:/datasets/cats_vs_dogs ,可根據本身的需求進行修改)
數據集可至 這裏 下載
 # 構建測試數據集
    test_cat_filenames = tf.constant([test_cats_dir + filename for filename in os.listdir(test_cats_dir)])
    test_dog_filenames = tf.constant([test_dogs_dir + filename for filename in os.listdir(test_dogs_dir)])
    test_filenames = tf.concat([test_cat_filenames, test_dog_filenames], axis=-1)
    test_labels = tf.concat([
        tf.zeros(test_cat_filenames.shape, dtype=tf.int32), 
        tf.ones(test_dog_filenames.shape, dtype=tf.int32)], 
        axis=-1)

    test_dataset = tf.data.Dataset.from_tensor_slices((test_filenames, test_labels))
    test_dataset = test_dataset.map(_decode_and_resize)
    test_dataset = test_dataset.batch(batch_size)

    print(model.metrics_names)
    print(model.evaluate(test_dataset))
測試模型
經過 prefetch() 的使用和在 map() 過程當中加入 num_parallel_calls 參數,模型訓練的時間可縮減至原來的一半甚至更低

 

 

TFRecord :TensorFlow 數據集存儲格式

TensorFlow 中的數據集存儲格式。當咱們將數據集整理成 TFRecord 格式後,TensorFlow 就能夠高效地讀取和處理這些數據集

TFRecord 能夠理解爲一系列序列化的 tf.train.Example 元素所組成的列表文件,而每個 tf.train.Example 又由若干個 tf.train.Feature 的字典組成。形式以下:

# dataset.tfrecords
[ { # example 1 (tf.train.Example) 'feature_1': tf.train.Feature, ... 'feature_k': tf.train.Feature }, ... { # example N (tf.train.Example) 'feature_1': tf.train.Feature, ... 'feature_k': tf.train.Feature } ] 

爲了將形式各樣的數據集整理爲 TFRecord 格式,咱們能夠對數據集中的每一個元素進行如下步驟:

  • 讀取該數據元素到內存;

  • 將該元素轉換爲 tf.train.Example 對象(每個 tf.train.Example 由若干個 tf.train.Feature 的字典組成,所以須要先創建 Feature 的字典);

  • 將該 tf.train.Example 對象序列化爲字符串,並經過一個預先定義的 tf.io.TFRecordWriter 寫入 TFRecord 文件。

而讀取 TFRecord 數據則可按照如下步驟:

  • 經過 tf.data.TFRecordDataset 讀入原始的 TFRecord 文件(此時文件中的 tf.train.Example 對象還沒有被反序列化),得到一個 tf.data.Dataset 數據集對象;

  • 經過 Dataset.map 方法,對該數據集對象中的每個序列化的 tf.train.Example 字符串執行 tf.io.parse_single_example 函數,從而實現反序列化。

 

將 cats_vs_dogs 二分類數據集的訓練集部分轉換爲 TFRecord 文件,並讀取該文件 

存儲爲 TFRecord 文件

import tensorflow as tf
import os

data_dir = 'C:/datasets/cats_vs_dogs'
train_cats_dir = data_dir + '/train/cats/'
train_dogs_dir = data_dir + '/train/dogs/'
tfrecord_file = data_dir + '/train/train.tfrecords'

train_cat_filenames = [train_cats_dir + filename for filename in os.listdir(train_cats_dir)]
train_dog_filenames = [train_dogs_dir + filename for filename in os.listdir(train_dogs_dir)]
train_filenames = train_cat_filenames + train_dog_filenames
train_labels = [0] * len(train_cat_filenames) + [1] * len(train_dog_filenames)  # 將 cat 類的標籤設爲0,dog 類的標籤設爲1
下載數據集 並解壓到 data_dir ,初始化數據集的圖片文件名列表及標籤。
with tf.io.TFRecordWriter(tfrecord_file) as writer:
    for filename, label in zip(train_filenames, train_labels):
        image = open(filename, 'rb').read()     # 讀取數據集圖片到內存,image 爲一個 Byte 類型的字符串
        feature = {                             # 創建 tf.train.Feature 字典
            'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),  # 圖片是一個 Bytes 對象
            'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))   # 標籤是一個 Int 對象
        }
        example = tf.train.Example(features=tf.train.Features(feature=feature)) # 經過字典創建 Example
        writer.write(example.SerializeToString())   # 將Example序列化並寫入 TFRecord 文件
迭代讀取每張圖片,創建 tf.train.Feature 字典和 tf.train.Example 對象,序列化並寫入 TFRecord 文件。

tf.train.Feature 支持三種數據格式:

  • tf.train.BytesList :字符串或原始 Byte 文件(如圖片),經過 bytes_list 參數傳入一個由字符串數組初始化的 tf.train.BytesList 對象;

  • tf.train.FloatList :浮點數,經過 float_list 參數傳入一個由浮點數數組初始化的 tf.train.FloatList 對象;

  • tf.train.Int64List :整數,經過 int64_list 參數傳入一個由整數數組初始化的 tf.train.Int64List 對象。

若是隻但願保存一個元素而非數組,傳入一個只有一個元素的數組便可

讀取 TFRecord 文件

raw_dataset = tf.data.TFRecordDataset(tfrecord_file)    # 讀取 TFRecord 文件

feature_description = { # 定義Feature結構,告訴解碼器每一個Feature的類型是什麼
    'image': tf.io.FixedLenFeature([], tf.string),
    'label': tf.io.FixedLenFeature([], tf.int64),
}

def _parse_example(example_string): # 將 TFRecord 文件中的每個序列化的 tf.train.Example 解碼
    feature_dict = tf.io.parse_single_example(example_string, feature_description)
    feature_dict['image'] = tf.io.decode_jpeg(feature_dict['image'])    # 解碼JPEG圖片
    return feature_dict['image'], feature_dict['label']

dataset = raw_dataset.map(_parse_example)
讀取創建的 train.tfrecords 文件,並經過 Dataset.map 方法,使用 tf.io.parse_single_example 函數對數據集中的每個序列化的 tf.train.Example 對象解碼。
feature_description 相似於一個數據集的 「描述文件」,經過一個由鍵值對組成的字典,告知 tf.io.parse_single_example 函數每一個 tf.train.Example 數據項有哪些 Feature,以及這些 Feature 的類型、形狀等屬性。
tf.io.FixedLenFeature 的三個輸入參數 shape 、 dtype 和 default_value (可省略)爲每一個 Feature 的形狀、類型和默認值。這裏咱們的數據項都是單個的數值或者字符串,因此 shape 爲空數組。
運行以上代碼後,咱們得到一個數據集對象  dataset ,這已是一個能夠用於訓練的  tf.data.Dataset 對象
import matplotlib.pyplot as plt 

for image, label in dataset:
    plt.title('cat' if label == 0 else 'dog')
    plt.imshow(image.numpy())
    plt.show()
從該數據集中讀取元素並輸出驗證

 

 

@tf.function :圖執行模式 *

追求高性能或部署模型,將模型轉換爲高效的 TensorFlow 圖模型

import tensorflow as tf
import time
from zh.model.mnist.cnn import CNN
from zh.model.utils import MNISTLoader

num_batches = 400
batch_size = 50
learning_rate = 0.001
data_loader = MNISTLoader()

@tf.function
def train_one_step(X, y):    
    with tf.GradientTape() as tape:
        y_pred = model(X)
        loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
        loss = tf.reduce_mean(loss)
        # 注意這裏使用了TensorFlow內置的tf.print()。@tf.function不支持Python內置的print方法
        tf.print("loss", loss)  
    grads = tape.gradient(loss, model.variables)
    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

if __name__ == '__main__':
    model = CNN()
    optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
    start_time = time.time()
    for batch_index in range(num_batches):
        X, y = data_loader.get_batch(batch_size)
        train_one_step(X, y)
    end_time = time.time()
    print(end_time - start_time)      
@tf.function 基礎使用方法
當被 @tf.function 修飾的函數第一次被調用的時候,進行如下操做:

在即時執行模式關閉的環境下,函數內的代碼依次運行。也就是說,每一個 tf. 方法都只是定義了計算節點,而並無進行任何實質的計算。這與 TensorFlow 1.X 的圖執行模式是一致的;
使用 AutoGraph 將函數中的 Python 控制流語句轉換成 TensorFlow 計算圖中的對應節點(好比說 whilefor 語句轉換爲 tf.whileif 語句轉換爲 tf.cond 等等;
基於上面的兩步,創建函數內代碼的計算圖表示(爲了保證圖的計算順序,圖中還會自動加入一些 tf.control_dependencies 節點);
運行一次這個計算圖;
基於函數的名字和輸入的函數參數的類型生成一個哈希值,並將創建的計算圖緩存到一個哈希表中。
在被 @tf.function 修飾的函數以後再次被調用的時候,根據函數名和輸入的函數參數的類型計算哈希值,檢查哈希表中是否已經有了對應計算圖的緩存。若是是,則直接使用已緩存的計算圖,不然從新按上述步驟創建計算圖。
@tf.function 內在機制
import tensorflow as tf

@tf.function
def square_if_positive(x):
    if x > 0:
        x = x * x
    else:
        x = 0
    return x

a = tf.constant(1)
b = tf.constant(-1)
print(square_if_positive(a), square_if_positive(b))
print(tf.autograph.to_code(square_if_positive.python_function))
AutoGraph:將 Python 控制流轉換爲 TensorFlow 計算圖
使用 tf.autograph 模塊的低層 API tf.autograph.to_code 將函數 square_if_positive 轉換成 TensorFlow 計算圖
 optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=learning_rate)
    num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
    # 創建計算圖
    X_placeholder = tf.compat.v1.placeholder(name='X', shape=[None, 28, 28, 1], dtype=tf.float32)
    y_placeholder = tf.compat.v1.placeholder(name='y', shape=[None], dtype=tf.int32)
    y_pred = model(X_placeholder)
    loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y_placeholder, y_pred=y_pred)
    loss = tf.reduce_mean(loss)
    train_op = optimizer.minimize(loss)
    sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
    # 創建Session
    with tf.compat.v1.Session() as sess:
        sess.run(tf.compat.v1.global_variables_initializer())
        for batch_index in range(num_batches):
            X, y = data_loader.get_batch(batch_size)
            # 使用Session.run()將數據送入計算圖節點,進行訓練以及計算損失函數
            _, loss_value = sess.run([train_op, loss], feed_dict={X_placeholder: X, y_placeholder: y})
            print("batch %d: loss %f" % (batch_index, loss_value))

        num_batches = int(data_loader.num_test_data // batch_size)
        for batch_index in range(num_batches):
            start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
            y_pred = model.predict(data_loader.test_data[start_index: end_index])
            sess.run(sparse_categorical_accuracy.update(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred))
        print("test accuracy: %f" % sess.run(sparse_categorical_accuracy.result()))
使用傳統的 tf.Session
TensorFlow 2 提供了 tf.compat.v1 模塊以支持 TensorFlow 1.X 版本的 API

tf.TensorArray :TensorFlow 動態數組 *

在即時執行模式下,你能夠直接使用一個 Python 列表(List)存放數組。

不過,若是你須要基於計算圖的特性(例如使用 @tf.function 加速模型運行或者使用 SavedModel 導出模型),就沒法使用這種方式了。

所以,TensorFlow 提供了 tf.TensorArray ,一種支持計算圖特性的 TensorFlow 動態數組。

其聲明的方式爲:

  • arr tf.TensorArray(dtype, size, dynamic_size=False) :聲明一個大小爲 size ,類型爲 dtype 的 TensorArray arr 。若是將 dynamic_size 參數設置爲 True ,則該數組會自動增加空間。

其讀取和寫入的方法爲:

  • write(index, value) :將 value 寫入數組的第 index 個位置;

  • read(index) :讀取數組的第 index 個值;

除此之外,TensorArray 還包括 stack() 、 unstack() 等經常使用操做,可參考 文檔 以瞭解詳情。

請注意,因爲須要支持計算圖, tf.TensorArray 的 write() 方法是不能夠忽略左值的!也就是說,在圖執行模式下,必須按照如下的形式寫入數組:

arr = arr.write(index, value) 

這樣才能夠正常生成一個計算圖操做,並將該操做返回給 arr 。而不能夠寫成:

arr.write(index, value) # 生成的計算圖操做沒有左值接收,從而丟失
import tensorflow as tf

@tf.function
def array_write_and_read():
    arr = tf.TensorArray(dtype=tf.float32, size=3)
    arr = arr.write(0, tf.constant(0.0))
    arr = arr.write(1, tf.constant(1.0))
    arr = arr.write(2, tf.constant(2.0))
    arr_0 = arr.read(0)
    arr_1 = arr.read(1)
    arr_2 = arr.read(2)
    return arr_0, arr_1, arr_2

a, b, c = array_write_and_read()
print(a, b, c)
簡單的示例

tf.config:GPU 的使用與分配 *

gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
cpus = tf.config.experimental.list_physical_devices(device_type='CPU')
print(gpus, cpus)
tf.config.experimental.list_physical_devices ,咱們能夠得到當前主機上某種特定運算設備類型(如 GPU 或 CPU )的列
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
tf.config.experimental.set_visible_devices(devices=gpus[0:2], device_type='GPU')
經過 tf.config.experimental.set_visible_devices ,能夠設置當前程序可見的設備範圍
限定當前程序只使用下標爲 0、1 的兩塊顯卡(GPU:0和 GPU:1
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
for gpu in gpus:
    tf.config.experimental.set_memory_growth(device=gpu, enable=True)
經過 tf.config.experimental.set_memory_growth 將 GPU 的顯存使用策略設置爲 「僅在須要時申請顯存空間」
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
tf.config.experimental.set_virtual_device_configuration(
    gpus[0],
    [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=1024)])
經過 tf.config.experimental.set_virtual_device_configuration 選項並傳入 tf.config.experimental.VirtualDeviceConfiguration 實例,設置 TensorFlow 固定消耗 GPU:0 的 1GB 顯存
gpus = tf.config.experimental.list_physical_devices('GPU')
tf.config.experimental.set_virtual_device_configuration(
    gpus[0],
    [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2048),
     tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2048)])
單 GPU 模擬多 GPU 環境
在實體 GPU GPU:0 的基礎上創建了兩個顯存均爲 2GB 的虛擬 GPU。

 

下一篇:三分鐘快速上手TensorFlow 2.0 (下)——模型的部署 、大規模訓練、加速

相關文章
相關標籤/搜索