TensorFlow——機器學習編程框架


TensorFlow

TensorFlow是一個機器學習(即亦包括深度學習)的編程框架。

Tensor 張量

張量是tensorflow計算中數據的基本單位,通過.shape獲取形狀,.dtype獲取數值類型,.numpy()獲取數值(將張量以numpy數組形式返回)。

變量的域

兩種域(scope),名字域(name_scope)和變量域(variable_scope),關於創建和獲取變量時變量名解析策略,分別以tf.name_scope('')tf.variable_scope('')
在開啟指定scope(即以scope名字實參調用.name_scope('xx')/.variable_scope('xx'))后,對於在其中定義或獲取的變量,其名字具有scope名前綴,以/分隔scope名和給定的變量名參數。tf.Variable(name='')在兩種域下均受影響,tf.get_variable('')僅在variable_scope下受影響。.name_scope('')/.variable_scope('')可嵌套開啟。

import tensorflow as tf

with tf.name_scope('a'):
    v1=tf.Variable(1,name='v1')
    v2=tf.get_variable(name='v2', shape=[1,])
with tf.variable_scope('b'):
    v3=tf.Variable(1, name='v3')
    v4=tf.get_variable(name='v4', shape=[1,])
    
with tf.variable_scope('c'):
    with tf.name_scope('c2'):
        v5=tf.Variable(1, name='v5')

print(v1.name)      # a/v1:0
print(v2.name)      # v2:0
print(v3.name)      # b/v3:0
print(v4.name)      # b/v4:0
print(v5.name)      # c/c1/v5:0

常見方法及功能

(括號內為常用參數,帶問號表示可選參數,部分未帶問號參數也是可選參數但一般會顯式提供):

  • tf.Variable(init_value?,dtype, name?) 創建變量,如果已存在同名變量,則添加后綴'_ '。 tf.Variable(otherVar.initialized_value()) 用已存在變量初始化新定義變量
  • tf.get_variable(name) 在開啟reuse= REUSE_TRUE| AUTO_REUSE的變量域中意為獲取已定義變量,試圖獲取未定義的變量時將報錯,reuse=False時,為創建變量
  • tf.placeholder(dtype, shape) 定義占位符
  • tf.one_hot() 定義one-hot向量
  • tf.variable_scope(name_or_scope) 開啟variable_scope
  • tf.name_scope('name_or_scope') 開啟name_scope
  • tf.nn.embedding_lookup(params, ids)
  • ops.get_collection() 獲取指定圖的所有定義變量列表 .get_collection(ops.GraphKeys.GLOBAL_VARIABLES)
  • tf.variables_initializer(var_list=all_variables_list) 為給定變量定義初始化器
  • tf.global_variables_initializer() 定義全局變量初始化器

tf.Variable(name='a')返回對象的屬性.name為'a:0'。

tf.constant() 定義常量。

tf.zeros(shape, dtype=整型/浮點型) 定義全0的張量。

tf.ones(shape, dtype=) 定義全1的張量。

tf.concat([...], axis) 拼接張量。

數據類型:

  • tf.int32
  • tf.float32
  • ...

運算:

  • tf.add(a,b) 求和(加)
  • tf. 求差(減)
  • tf.multiply(a,b) 積(乘)
  • tf.square(x) 平方,element-wise。不改變張量形狀。
  • tf.div(a,b) 商(除)
  • tf.matmul(A,B) 矩陣乘積
  • tf.pow(x,y) 冪(次方 乘方) 對x中每個元素取冪,如果x,y是結構一樣的張量,則是將x中元素作為底,y中對位置元素作為指數取冪。
  • tf.argmax(input, axis) 求張量中的最大元素的索引,沿着軸向
  • tf.squared_difference() 平方差,element-wise。不改變張量形狀。
  • tf.reduce_mean() 均值
  • tf.reduce_sum()

學習率:

  • tf.train.exponential_decay()
  • tf.train.inverse_time_decay()
  • tf.train.natural_exp_decay()
  • tf.train.piecewise_constant()
  • tf.train.polynomial_decay()

(tf.nn)

  • tf.nn.softmax_cross_entropy_with_logits()

tf.train模塊是關於訓練的程序,如梯度下降優化器。

tf.feature_column

自動構建求導算式

利用tf.GradientTape()

例子-簡單算式的求導:

import tensorflow as tf

x = tf.Variable(initial_value=2.0)
with tf.GradientTape() as tape:     #在GradientTape上下文內的計算步驟將被自動記錄用以構建求導算式
    y = tf.square(x)
#利用GradientTape算導數值/偏導值時tape.gradient()要放到代碼塊`with ...:`外。
y_grad = tape.gradient(y, x)    #計算y關於x的導數
print(y,y_grad, x)

例子-關於向量的偏導(梯度):

import tensorflow as tf

X = tf.constant([[1.0,2.0],
              [3.0,4.0]])
y = tf.constant([[1.0],
               [2.0]])
w = tf.Variable(initial_value=[[1.0],
                             [2.0]])
b = tf.Variable(initial_value=1.0)
with tf.GradientTape() as tape:
    L=0.5*tf.reduce_sum(tf.square(tf.matmul(X, w)+b-y))
w_grad,b_grad = tape.gradient(L, [w,b])

例子-線性回歸:

#數據
# x: [...]
# y: [...]
# y = ax + b

import numpy as np
import tensorflow as tf

x_raw = np.array([...])
y_raw = np.array([...])
#首先將數據歸一化
x = (x_raw - x_raw.min())/(x_raw.max()-x_raw.min())
y = (y_raw - y_raw.min())/(y_raw.max()-y_raw.min())
x=tf.constant(x)
y=tf.constant(y)
a=tf.Variable(initial_value=0.0)
b=tf.Variable(initial_value=0.0)
vars=[a,b]

num_epoch=100
optimizer=tf.keras.optimizers.SGD(learning_rate=1E-3)
for ep in range(num_epoch):
    with tf.GradientTape() as tape:
        y_pred=a*x+b
        loss=0.5*tf.reduce_sum(tf.square(y-y_pred))
    grads=tape.gradient(loss, vars)
    optimizer.apply_gradients(grads_and_vars=zip(grads, vars)

print(a, b)

Session.run(): 第一個參數fetches為張量時,可以是單個張量也可以是張量列表,返回張量的計算結果。第二個參數feed_dict填值字典,其為張量填值,也可以為變量填值,鍵是張量/變量對象或其名字(注意不是定義張量/變量時的name實參,應是張量對應的.name屬性,即Tensor.name,具有形式"<op_name>:<output_index>",定義張量時的name實參一般只是這里的<op_name>)。

為什么需要變量初始化器(initializer):在執行模型的所有運算前,需要對變量進行初始化,通過Session.run()對傳入的初始化器進行變量初始化。

計算圖 Graph
class tf.Graph計算圖,表示數據流向。主要由tf.Tensor(張量)和tf.Opereation(操作)構成。

tf.Graph()生成計算圖實例。Graph.as_default()返回上下文管理器使得該圖作為默認圖。一般用法:with <graph>.as_default(): #后續各種tf操作

tf.get_default_graph()返回默認圖。

Tensor.graph()返回該張量所在的圖。

tf.app

tf.app:是什么
tf.app.flags:有什么用、對應何種配置? tf.app.flags.FLAGS:為何有該定義
tf.app.flags.DEFINE_string('key/name', value, 'comment')/_integer()...

TensorBoard

記錄和可視化觀察訓練過程中的變量的變化。

常用的是對於標量的觀察,利用tf.summary.scalar()來記錄。

summary_writer = tf.summary.create_file_writer('./tensorboard')     # 參數為用以保存記錄數據的目錄
for batch_index in range(num_batches):
    ...
    with summary_writer.as_default():   #打開記錄器
        tf.summary.scalar("loss", loss, step=batch_index)
        tf.summary.scalar("other-scalar", my_scalar, step=batch_index)     #其他標量

啟動tensorboard界面服務(HTTP協議),默認在端口6006提供服務。訪問地址 http://127.0.0.1:6006

tensorboard --logdir=./tensorboard

Eager Execution 即時運行

tensorflow eager api: tensorflow具有一個叫做 Eager Execution的特性(與之相對的是Graph Execution),相對於計算圖模式而言,eager execution模式下,調用tensorflow下的運算操作會立即計算出結果后返回,而b不像構建計算圖,后者要等到被調用.run()才計算。eager api面向簡單模型、小規模數據,接口相對簡單方便,相關數據可使用python數據結構。
Tensorflow 2中默認啟用即時運行。

from __future__ import absolute_import, division, print_function
import tensorflow as tf
import tensorflow.contrib.eager as tfe
 
# enabled by default in tensorflow 2.x
tf.enable_eager_execution()

# tf 2中關閉即時運行模式
#tf.compat.v1.disable_eager_execution()
 
x = [[2.]]
m = tf.matmul(x, x)
print("hello, {}".format(m))  # => "hello, [[4.]]"

a=tf.constant(5.0)
b=tf.constant(2.0)

print("*: {}".format(a*b))  # 10.0,直接出計算結果,沒有計算圖,也不需tf.Session

在tensorflow 2.x+ 中計算模式模式是即時運行(Eager Execution),若想以計算圖方式執行,可借助@tf.function,被注解的函數將以計算圖模式被執行,被注解函數存在限制,其中應盡量使用tf的操作,tf數據結構或numpy數據結構,因部分python特征不受支持(如python的print()應換之以tf.print())。

tf.TensorArray是tensorflow 2.x+中計算圖模式下的動態數組解決方案。

Tensorflow Keras

Keras是一套簡單易用靈活的深度學習接口框架,tensorflow對其內置支持,相關接口在tf.keras下。

Keras中模型相關的關鍵概念是“模型”(Model)和“層”(Layer)。一個神經網絡模型由“層”堆疊構成,一個層包括對一個張量的一次變換和一次激活。Tensorflow Keras內置了常見的層,在tf.keras.layers下。“模型”中定義了神經網絡的結構,以及訓練預測相關的組件操作。

Keras Model 模型

模型對於的類為tf.keras.Model,模型實例是可調用對象,對其調用將產生輸出(output_y = model(input_X))。

堆疊層為模型的簡單例子:

from tensorflow import keras

model = keras.Sequential([
    keras.layers.Xxx(),
    keras.layers.Xxx(),
    ])
    
model.compile(optimizer='',
                loss='',
                metrics=['accuracy'])
                
model.fit(train_X, train_y, epoch=5)

test_loss, test_acc = model.evaluate(test_X, test_y)

y=model.predict(X)

可自定義模型,其中需要被定義的方法主要有初始化方法__init__(self)以及專門用於被重寫的接受輸入后產生輸出的方法call(self, input)(注意不是__call()__,該方法在tf.keras.Model中已有定義,其中調用了call(),此外還包括其他一些操作)。

模型構建 Keras Sequential/Functional API

Keras中提供了將若干子模型/層順序串連后作為模型的接口 tf.keras.models.Sequential([model1, model2,...]),即Sequential API 。

Sequential API不能定義多輸入/輸出等較為復雜的模型,Keras提供了Functional API來構建模型。

Keras Functional API構建模型的示例代碼:

inputs = tf.keras.Input(shape=(28, 28, 1))
x = tf.keras.layers.Flatten()(inputs)
x = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)(x)
x = tf.keras.layers.Dense(units=10)(x)
outputs = tf.keras.layers.Softmax()(x)
model = tf.keras.Model(inputs=inputs, outputs=outputs)

模型訓練配置
compile()方法配置訓練過程。
參數:

  • optimizer 優化器。內置優化器在tf.keras.optimizers.下。
  • loss 損失函數。內置損失函數在tf.keras.losses.下。
  • metrics 評估指標。內置指標在tf.keras.metrics.下。

模型訓練
fit()方法,訓練模型。
參數:

  • x 訓練輸入數據。
  • y 訓練數據的監督數據。
  • epochs 訓練輪數。
  • batch_size 批大小。
  • validation_data 驗證集。

模型測試
evaluate()方法,評估/測試模型。
參數:

  • x 測試數據。
  • y 測試數據的監督數據。

模型預測
Model.predict(),參數:

  • x

tf.keras.Model.fit()過程及示例代碼:

# boxed
import tensorflow as tf
from tensorflow import keras
import numpy as np

mnist = keras.datasets.mnist

(train_x, train_y), (test_x, test_y) = mnist.load_data()

train_x = np.expand_dims(train_x.astype(np.float32) / 255.0, axis=-1)
test_x = np.expand_dims(test_x.astype(np.float32) / 255.0, axis=-1)

# hyper parameters
num_epochs = 5
batch_size = 50
learning_rate = 0.001

model = keras.Sequential([
    keras.layers.Flatten(),
    keras.layers.Dense(100, activation=tf.nn.relu),
    keras.layers.Dense(10, activation=tf.nn.softmax)
])

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

model.fit(train_x, train_y, batch_size=batch_size, epochs=num_epochs)

test_loss, acc = model.evaluate(test_x, test_y)

print('loss: {}, acc: {}'.format(test_loss, acc))


#------------------#
# unbox
#以下代碼來自tensorflow微信公眾號
class MNISTLoader():
    def __init__(self):
        mnist = tf.keras.datasets.mnist
        (self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data()
        # MNIST中的圖像默認為uint8(0-255的數字)。以下代碼將其歸一化到0-1之間的浮點數,並在最后增加一維作為顏色通道
        self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1)      # [60000, 28, 28, 1]
        self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1)        # [10000, 28, 28, 1]
        self.train_label = self.train_label.astype(np.int32)    # [60000]
        self.test_label = self.test_label.astype(np.int32)      # [10000]
        self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]

    def get_batch(self, batch_size):
        # 從數據集中隨機取出batch_size個元素並返回
        index = np.random.randint(0, np.shape(self.train_data)[0], batch_size)
        return self.train_data[index, :], self.train_label[index]

class MLP(tf.keras.Model):
    def __init__(self):
        super().__init__()
        self.flatten = tf.keras.layers.Flatten()    # Flatten層將除第一維(batch_size)以外的維度展平
        self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
        self.dense2 = tf.keras.layers.Dense(units=10)

    def call(self, inputs):         # [batch_size, 28, 28, 1]
        x = self.flatten(inputs)    # [batch_size, 784]
        x = self.dense1(x)          # [batch_size, 100]
        x = self.dense2(x)          # [batch_size, 10]
        output = tf.nn.softmax(x)
        return output

num_epochs = 5
batch_size = 50
learning_rate = 0.001

model = MLP()
data_loader = MNISTLoader()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)

    num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
    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()))
        grads = tape.gradient(loss, model.variables)
        optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

    sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
    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])
        sparse_categorical_accuracy.update_state(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred)
    print("test accuracy: %f" % sparse_categorical_accuracy.result())

優化方法 Optimizers

相關工具方法在tf.keras.optimizers.下。

常用Adam優化方法,AdamOptimizer(實參可以字符串'adam'代替)。

優化器的方法apply_gradients(grads_and_vars=[(梯度1,變量1), (梯度2, 變量2)])

損失函數 Loss Functions

模塊tf.keras.losses.下。

自定義損失函數,通過繼承類tf.keras.losses.Loss重寫方法call(y_true, y_pred)來實現。

sparse_softmax_crossentropy

交叉熵(cross entropy)損失:
sparse_categorical_crossentropycategorical_crossentropy都用於計算交叉熵,兩者的參數列表皆為(y_true, y_pred),差別在於前者的y_true參數允許傳入int類型的標簽類別。對於實參預測值y_predint型的真實值y,兩個交叉熵函數用以下形式調用得到的結果是一樣的:

loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)

loss = tf.keras.losses.categorical_crossentropy(
    y_true=tf.one_hot(y, depth=tf.shape(y_pred)[-1]),
    y_pred=y_pred
)

評估指標 Metrics

進行准確度(accuracy)度量。在模塊tf.keras.metrics下。

自定義評估指標,通過繼承類tf.keras.metrics.Metric,定義方法update_state(y_true, y_pred, sample_weight=None)result()來實現。

兩個重要的方法 更新狀態update_state(y_true=, y_pred=)、獲取結果(准確度)result()

Estimators

Estimator是一種高層接口,其封裝了訓練、評估(測試)、預測、導出等功能。
tf.estimator.Estimator是estimators的基類。

tf.keras.estimator.model_to_estimator(keras_model=)是將一個tf.keras.Model轉為tf.estimator.Estimator的工具函數。

Estimator()
tf.estimator.Estimator()構造函數的參數:

  • model_fn: 模型構建函數,其應返回tf.estimator.EstimatorSpec,其參數列表應是:
    • features 這同train(),evaluate(),predict()input_fn里的features(input_fn返回值的第一個元素)。
    • labelsinput_fn返回值的第二個元素。
    • mode:為tf.estimator.ModeKeys.TRAIN/EVAL/PREDICT,表明是訓練/評估/預測。
    • params
    • config
  • model_dir: 保存模型參數、計算圖結構、事件、檢查點等數據的目錄,為None則模型目錄將使用config中的參數,若config中也未設置,則使用自動創建的臨時目錄。默認None
  • config:類型為estimator.RunConfig,運行過程的相關配置。
  • params:模型的超參,將傳遞給model_fnparams參數,類型為字典,鍵值的類型應為python基本類型。
  • warm_start_from:熱啟動時的檢查點或SavedModel啟動數據位置,類型為路徑或tf.estimator.WarmStartSettings

Estimator.export_saved_model()
tf.estimator.Estimator.export_saved_model() 將計算圖導出為SavedModel。
參數

  • export_dir_base
  • serving_input_receiver_fn:無參函數,應返回tf.estimator.export.ServingInputReceivertf.estimator.export.TensorServingInputReceiver
  • as_text: 是否將SavedModel proto寫為文本格式.
  • checkpoint_path 要導出的檢查點的路徑。為None(默認值)時表示導出模型目錄路徑(estimator中的model_dir信息)中的最新檢查點。

Estimator.train()
參數:

  • input_fn 輸入數據的構建函數,其應返回一個tf.data.DataSet其輸出為一個元組(features, labels),或返回一個元組(features, labels),二者中的features是一個tf.Tensor或特征名(字符串)到tf.Tensor的字典。
  • hooks
  • steps
  • max_steps
  • saving_listeners

Estimator.evaluate()
參數:

  • input_fn 輸入數據的構建函數,應返回(features, labels)DataSet,函數拋異常tf.errors.OutOfRangeErrorStopIteration時表示終止評估。
  • steps 評估模型輸入數量,為None時評估直到input_fn拋異常。
  • hooks
  • checkpoint_path
  • name

Estimator.predict()
參數:

  • input_fn 輸入特征的構建函數,該函數拋異常tf.errors.OutOfRangeErrorStopIteration時表示終止預測。
  • predict_keys
  • hooks
  • checkpoint_path
  • yield_single_examples

Estimator.latest_checkpoint() 查找並返回模型目錄中的最新檢查點。

層 Keras Layers

層對應的類為tf.keras.layers.Layer

可自定義層,通過繼承類tf.keras.layers.Layer,重寫方法__init__()build(self, input_shape)call(self, inputs)call()在每次計算時會被調用, build()只會被調用一次,是在層實例第一次執行計算(被調用call())前被調用。

Dense:一維全連接層

全連接層tf.keras.layers.Dense是Keras中最基礎和常用的層之一,其對輸入矩陣進行仿射變換以及激活函數操作。
其主要參數有:

  • units 輸出單元數(即輸出維度)。
  • input_shapeinput_dim) 輸入形狀,在作為輸入層(第一層)時需要指定。
  • activation 激活函數,默認無(即單位函數 $f(x)=x$)。
  • use_bias 是否定義偏置單元。默認True
  • kernel_initializer, bias_initializer,輸入(稱為kernel)和偏置的初始化器。默認tf.glorot_uniform_initializer,欲初始化為0則設置tf.zeros_initializer

Embedding:嵌入層

將輸入詞轉為數值向量。這里的詞是一個整數代碼,比如,該整數定義為詞在詞典中的索引,而詞典的大小即為構造參數中的input_dim
參數:

  • input_dim 詞典大小。
  • output_dim 詞嵌入向量空間的維度。
  • ……
  • input_length 序列長度,當序列是固定長度時。若Embedding后接Flatten然后接Dense時,該參數必需,否則無法推斷輸出層的維度。

輸入尺寸,為 (batch_size, sequence_length) 的 2D 張量。輸出尺寸為 (batch_size, sequence_length, output_dim) 的 3D 張量。

Flatten 扁平化層

將高階張量扁平化為一階張量,即向量,的神經層。常用於從卷積層到全連接層的過渡。扁平化層不改變批大小batch_size,即展平除第一階外的其他階(實際最終輸出的張量是二階的而非一階的)。如mnist中可用Flatten層將形狀為[60000, 28, 28, 1]的四階張量展平為[60000, 784]的二階張量(第一階是批大小batch_size,不受影響)。

GRU 門控循環單元

GRU(Gated Recurrent Unit),門控循環單元,有兩種門,更新門和重置門(LSTM有三種門)。

LSTM 長短期記憶


所謂輸出'logit',一般指的是未歸一化的概率,其后一般作為softmax層的輸入。

tf.data 數據工具

tf.data.DataSet

tf.data.Dataset是數據集的抽象。

Dataset.from_tensor_slices()
tf.data.Dataset.from_tensor_slices((train_examples, train_labels)) 生成Dataset對象,從numpy數組等數據,該方法適用於數據量較小、能被全部裝入內存的場景。(對於數據量大的,考慮使用tf.dta.TFRocrdDataset

from_tensor_slices()可接受多個張量形成元組的形成作為輸入,每個張量(即元組的每個元素)的第一階的大小需相同(如輸入矩陣和標簽列向量,第一階的大小是樣本數量,此時輸入矩陣行數和標簽個數應該相同)。

DataSet中的常用方法

  • map(map_func, num_parallel_calls=tf.data.experimental.AUTOTUNE) 對其中的元素進行映射,提供參數num_parallel_calls時將使得映射並行化。
  • shuffle(buffer_size) 打亂數據,參數buffer_size是所用緩沖區的大小,打亂程度取決於緩沖區大小。打亂過程是,從緩沖區中隨機取出一個元素,其空缺以原數據中下一條補上。特別地,buffer_size=1等於沒有打亂數據。另外,大片連續的同標簽數據在使用較小的緩沖區大小實參時,其打亂效果也很差,同樣很可能是大片連續同標簽的。
  • batch(batch_size) 分組為批,將每batch_size個元素打包成一個批(使用tf.stack()進行堆疊)。
  • prefetch(buffer_size=tf.data.experimental.AUTOTUNE) 預取。
  • repeat(), reduce(), take().

Keras支持Dataset作為輸入,Model.fit(),Model.evaluate()時其可接受元素為(樣本輸入,樣本標簽)二元組的DataSet對象(此時fit(),evaluate()中的參數y被忽略。

tf.keras.datasets下有部分常見數據集的下載和操作的相關方法。

函數Dataset.zip()

TFRecord 數據集存儲格式

可將數據集以TFRecord格式存儲到文件,以便高效處理。

TFRecord由一系列tf.train.Example序列化后構成,Exmaple則是由若干命名的特征tf.train.Feature構成,命名方式通過字典數據結構實現。

tf.train.Feature支持以下類型數據:

  • tf.train.BytesList 二進制數據。
  • tf.train.FloatList 浮點型數據。
  • tf.train.Int64List 整型數據(int32/int64)。

創建數據時提供給構造函數tf.train.BytesList/FloatList/Int64List(value=[])的參數value的值應是數組,單個元素數據應該裹以數組。

TFRcord格式文件的生成由tf.io.TFRecordWriter寫器來完成,寫器的.write()方法接受的是Exmaple序列化為字符串的值即write(Exmaple.SerializeToString())

讀取以tf.data.TFRecordDataset(filenames)讀器來完成,然后通過方法map()來還原數據集,其參數是一個反序列化函數,一般會借助tf.io.parse_single_example()

示例代碼:

import tensorflow as tf

filename='a.tfrecord'
#寫
with tf.io.TFRecordWriter(filename) as writer:
    for (img_file, label) in zip(img_files, labels):
        img=open(img_file, 'rb').read()
        feats={
            'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img])),
            'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])
        }
        exmaple=tf.train.Example(features=tf.train.Features(feature=feats))
        writer.write(exmaple.SerializeToString())
        
#讀
raw_ds = tf.data.TFRecordDataset(tfrecord_file)
feat_desc= {
    'image': tf.io.FixedLenFeature([], tf.string),
    'label': tf.io.FixedLenFeature([], tf.int64)
}
def parse_one(exmaple_str):
    feat_dict = tf.io.parse_single_example(exmaple_str, feat_desc)
    img=tf.io.decode_jpeg(feat_dict['image'])
    label=feat_dict['label']
    return img,label

ds = raw_ds.map(parse_one)

模型保存與加載 Model Saving/Export & Loading

模型可被保存為文件,之后在其他程序中被加載出來使用。

模型可在訓練過程中隨時被保存(檢查點checkpoint機制),之后被加載出來繼續訓練。

模型的保存與加載時涉及兩方面的數據,模型的結構和權重。模型的結構即是構成模型的網絡層結構、各層類型、輸入輸出參數形狀、激活函數、優化器等等;模型權重即是各層權重張量的數值。模型的結構與權重可分開保存到各自文件,也可保存到一個文件。加載同樣。

Keras Model Saving & Loading

整個模型(結構+權重)

  • 保存tf.keras.Model.save(),實例方法。
    參數:

    • filepath 文件路徑
    • save_format 以指定格式保存,為'h5''tf',前者是Keras H5格式,后者是tensorflow SavedModel格式。默認由文件名推斷而來,當其擴展名是.h5.keras時格式為'h5',否則為'tf'。
  • 加載tf.keras.models.load_model(),函數。參數為模型文件路徑,不需要提供類似保存時的save_format='h5|tf'參數。

僅模型結構

  • 保存tf.keras.Model的實例方法.to_json(file).to_yml(file),分別保存為json和yml格式文件。
  • 加載tf.keras.models.model_from_json(),model_from_yml()

僅權重

  • 保存tf.keras.Model.save_weights()
    參數:
    • filepath 文件路徑
    • save_format 以指定格式保存,為'h5''tf',后者為tensorflow checkpoint格式。默認由文件名推斷而來,當其擴展名為.h5時為'h5',否則為'tf'。
  • 加載tf.keras.Model.load_weights(),實例方法。參數為文件路徑,不需要類似保存時的數據格式參數save_format=。

示例代碼(利用keras的callback機制和類ModelCheckpoint進行斷點保存與恢復)


checkpoint_path = "training_1/cp.ckpt"
#checkpoint_path = "training/ckp_{epoch}"       #自動捕獲變量epoch的值,以做替換


checkpoint_dir = os.path.dirname(checkpoint_path)

#配置,為保存行為
cp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path,
                                                 save_weights_only=True,    #僅保存權重(否則保存包括了模型結構的完整模型數據)
                                                 verbose=1)

model.fit(...,
        callbacks=[cp_callback])    #加入callback參數將使得保存操作一直伴隨訓練進行

#手動保存權重
model.save_weights('model_weights.h5')

#加載權重
m= #通過代碼構建出同樣的模型結構
m.load_weights(checkpoint_path)

#從檢查點系列文件中查找最新者
ckpt_path = tf.train.latest_checkpoint(checkpoint_dir)
#加載
model = tf.keras.models.load_model(ckpt_path)


#保存整個模型,不僅包括權重,還有模型及其配置、優化器等,以HDF5格式文件保存
model = #通過代碼構建模型結構
#訓練
model.fit(train_x, train_y)
#訓練好后准備保存
#保存
model.save('my_model.h5')

model.save('mymodel', save_format='tf') #以tf `SavedModel`格式保存
model.save('mymodel', save_format='h5') #顯式指定以keras h5格式保存

#加載模型
model = tf.keras.models.load_model('my_model.h5')
#load_model還可加載SavedModel格式的模型,*不需要*提供類似保存時的參數save_format='tf'

#保存模型結構,可為json格式或yml格式
model.to_json('my_model.json')
model.save_weights('model_weights.h5')

#加載
#從文件加載模型結構
model = tf.keras.models.model_from_json('my_model.json')
#加載權重
modle.load_weights('model_weights.h5')

tf.train.Checkpoint

Tensorflow有檢查點機制以保存和恢復數據(模型的權重、變量、優化器等),但不能保存模型的結構。適用於知道源碼,或其他能夠構建出原模型結構的場景。

tf.train.Checkpoint(**kwargs)的參數時鍵值對,鍵是任意的,但恢復時需要用到鍵名,值可以是模型(但模型的結構不會被保存)、變量、優化器等。

save(file_prefix)保存數據為檢查點,參數$file_prefix是保存路徑前綴(目錄及文件名前綴),保存時將會生成系列文件checkpoint, $file_prefix-<n>.index, $file_prefix-<n>.data-00000-of-00001,其中的<n>為數字,是自動生成的序號,時間上越新保存的檢查點其序號越大。save()將返回系列文件的路徑前綴,形如$file_prefix-<n>,該值用於恢復方法restore()的實參。

restore()恢復數據。參數是save()返回的路徑前綴,形如"$file_prefix- ",其中 是數字序號。需要注意的是,這個字符串實參並非完全作為文件路徑來被理解,如保存到目錄"out/model.ckpt",假設 save()返回"out/model.ckpt-1",則 restore()的實參應為"out/model.ckpt-1",不可以是"out//model.ckpt",盡管在作為文件路徑來理解時后者與前者指向的路徑是一致的。而實參"out/../out/model.ckpt-1"又是可以的。

tf.train.latest_checkpoint():檢測給定目錄中最新的檢查點路徑(用於restore())。參數是目錄的路徑(不是文件路徑的前綴)。

tf.train.CheckpointManager:限制最多保留的檢查點(系列)文件、自定義編號。使用方法,保存模型不再直接調tf.train.Checkpoint.save(),而是通過CheckpointManager.save()來實現。

tf.train.CheckpointManager()參數:

  • checkpoint 受管的tf.train.Checkpoint實例。
  • directory 保存目錄。
  • checkpoint_name 系列文件的共有文件名前綴(不含編號)。
  • max_to_keep 檢查點保留的最多個數。

CheckpointManager.save() 參數:

  • checkpoint_number 編號,為None(默認)時表示自動編號。

保存的示例的代碼:

model1 = new MyModel()
model1.fit(...)     #訓練模型
#經過一系列處理,待保存的模型、變量具有數據
#然后保存
ckpt = tf.train.CheckPoint(my_model = model1, my_var1 = var1, my_optimizer100 = optimizer1)

ckpt.save("out/model.ckpt")     #返回值可能是 out/model.ckpt-1

#或CheckpointManager
ckpt = tf.train.Checkpoint(my_model = model1, ...)
manager = tf.train.CheckpointManager(ckpt, directory='out', checkpoint_name='model.ckpt', max_to_keep=3)
for batch_index in range(num_batchs):
    ... model1 ...
    if ...: #隔一段時間保存一次
        manager.save(checkpoint_number=batch_index)

恢復的示例代碼:

model = new MyModel()
#恢復
ckpt = tf.train.CheckPoint(my_model = model)    #鍵名與保存時一致
ckpt.restore('out/model.ckpt-1')
#或檢測最新檢查點的文件名
ckpt.restore(tf.train.latest_checkpoint('out'))

Tensorflow SavedModel

SavedModel是tensorflow中的一種模型完整存儲的格式(及相關工具),其主要應用場景是在tensorflow不同語言接口間(python,java,c++)、或tensorflow不同平台間(tensorflow lite, tensorflow serving, tensorflow hub)使用和部署tensorflow程序/模型。

SavedModel格式存儲(python)

  • 利用實例方法tf.keras.Model.save(, save_format='tf')來保存一個keras模型為SavedModel格式。
  • 或利用tf.saved_model.save(dir, model), 或tf.saved_model.simple_save(), 或tf.saved_model.builder.SavedModelBuilder
  • 對Esatimator,利用實例方法tf.Estimator.export_saved_model()來保存。

SavedModel包含若干關鍵的信息,所謂"signature",即命名函數,以保存計算圖。

對於tf.keras.Model類型的模型可利用上述兩個方法很簡便地進行存儲為SavedModel,但自定義模型則需要做另外的工作。

自定義模型,其中以@tf.function裝飾的函數可被保存到SavedModel。

加載SavedModel(Python):利用tf.saved_model.loader.load(), tf.saved_model.load()

存儲和加載的示例代碼(Python):

model = tf.keras.Model(...)
#保存為SavedModel
model.save('my-dir', save_format='tf')
#或通過tf.saved_model.save()
tf.saved_model.save(model, 'my-dir2')
#或通過tf.saved_model.simple_save()
tf.saved_model.simple_save()
#或通過tf.saved_model.builder.SavedModelBuilder
builder = tf.saved_model.builder.SavedModelBuilder("my-dir4")
signature = predict_signature_def(inputs={'Input': x},
                                  outputs={'Output': y})
builder.add_meta_graph_and_variables(sess=sess,
                                     tags=['my-tag'],
                                     signature_def_map={'predict': signature})
builder.save()

#加載一個SavedModel
m = tf.saved_model.loader.load(...)
m = tf.saved_model.load(...)

#自定義模型保存為SavedModel
import tensorflow as tf

class MyMo(tf.Module):      #需要繼承tf.Module,因被存儲對象需要是Trackable
    def __init__(self):
        super(MyMo, self).__init__()
        self.k = tf.Variable(2.0)

    @tf.function(input_signature=[tf.TensorSpec([], tf.float32, name='in_x')])
    def __call__(self, x):
        return x * self.k

    @tf.function(input_signature=[tf.TensorSpec([], tf.float32)])   //signature參數值必須是list或tuple
    def set_k(self, new_k):
        self.k.assign(new_k)


m = MyMo()
print(m(tf.constant(0.0)).numpy())
print(m(tf.constant(2.0)).numpy())
tf.saved_model.save(m, 'out/m')

#加載
import tensorflow as tf

m = tf.saved_model.load('out/m')
print(m(tf.constant(3.0)).numpy())
m.set_k(tf.constant(2.0))
print(m(tf.constant(3.0)).numpy())

利用saved_model_cli查看已存儲的SavedModel的結構:

saved_model_cli show --dir out\m\4 --all

輸出:

MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['__saved_model_init_op']:
  The given SavedModel SignatureDef contains the following input(s):
  The given SavedModel SignatureDef contains the following output(s):
    outputs['__saved_model_init_op'] tensor_info:
        dtype: DT_INVALID
        shape: unknown_rank
        name: NoOp
  Method name is:

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['in_x'] tensor_info:
        dtype: DT_FLOAT
        shape: ()
        name: serving_default_in_x:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['output_0'] tensor_info:
        dtype: DT_FLOAT
        shape: ()
        name: StatefulPartitionedCall:0
  Method name is: tensorflow/serving/predict

加載SavedModel(Java):利用org.tensorflow.SavedModelBundle

def main() {
    val m = SavedModelBundle.loader("out/m")
                .withTags("serve")      //默認tag是'serve'
                .load();
    //或
    val m = SavedModelBundle.load("out/m", "serve");
    Tensor xFeed = Tensor.create(2.0F);     //構建模型輸入
    val output = m.session()
                      .runner()
                      .feed("serving_default_in_x:0", xFeed)    //輸入的名字可通過命令行工具saved_model_cli查看SavedModel而來
                      .fetch("StatefulPartitionedCall:0")
                      .run()
                      .get(0);
                     
    println(out.floatValue())
}

ServingInputReceiver

tf.estimator.export.ServingInputReceiver是導出為SavedModel的函數中的參數serving_input_receiver_fn的返回類型。
ServingInputReceiver的返回類型應是features:TensorSparseTensor或字典string/intTensor/SparseTensor,非字典數據將被包裝為字典,其只有一個鍵feature,即{'feature': tenso}ServingInputReceiver只能用於能夠允許這種字典結構作為輸入的模型。對於僅接受張量而非字典的為輸入的模型,導出模型時相關參數應該使用類型TensorServingInputReceiver,其不會進行這種字典包裝。

__new__ 方法

@staticmethod
__new__(
    cls,
    features,
    receiver_tensors,
    receiver_tensors_alternatives=None
)

TensorServingInputReceiver

tf.estimator.export.TensorServingInputReceiver,類似ServingInputReceiver,但后者返回的只會是字典即使提供的只是張量都會被自動包裝為字典,這要求模型必須能處理類型為字典的輸入,而對於那些只接受類型為張量的輸入的模型,此時不能使用ServingInputReceiver,而應用TensorServingInputReceiver,其則不會自動進行包裝,返回用戶提供的值。

tf.estimator.export.build_raw_serving_input_receiver_fn()
參數

  • features 字符串到張量的字典
  • default_batch_size 批次大小,默認None

tf.estimator.export.build_parsing_serving_input_receiver_fn()
參數

  • feature_spec 字符串到VarLenFeature/FixedLenFeature的字典
  • default_batch_size

示例代碼

簡單例子 $y=kx+b$運算:

import tensorflow as tf     #一般將tensorflow重命名為tf

#定義常量並初始化
pi = tf.constant(3.14159, name='pi')     #定義名為pi值為3.14159的常量

#定義變量
x = tf.Variable(0.0, name='x', dtype=tf.float32)  #定義變量,可提供初始化值、可指定數據類型(一般都顯式指定),默認數據類型由初始化值推斷而來
k = tf.Variable(1.0,name='k',dtype=tf.float32)

#定義運算(計算圖computation graph) y=kx+π
kx = tf.multiply(k,x, name='kx')
y = tf.add(kx,pi,name='y')
#tensorflow中有'+'、'-'、'*'、'÷'、等運算符,但由於存在重載、覆蓋,故一般顯示使用對應英文名方法來定義運算

#創建初始化器
init_op = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init_op)   #執行初始化
    output=sess.run(y)  #執行計算
    print('value: ', output)
    

tensorflow placeholder例子:

import tensorflow as tf
import numpy as np

#定義k,pi,y,初始化器
pi = tf.constant(3.14159, name='pi')
x = tf.placeholder(tf.float32, [None, 1], name='x')
k = tf.Variable(1.0, name='k', dtype=tf.float32)
kx = tf.multiply(k, x, name='kx')
y = tf.add(kx, pi, name='y')

#定義占位符(變量符號,計算時才給值)
x=tf.placeholder(tf.float32,[None,1], name='x')     # 維度:[None,1]表示第一軸向長度暫不可知,由給的值或者計算式確定

#占位符需在計算時“填值”(feed)
with tf.Session() as sess:
    tf.run(init_op)
    v=tf.run(y, feed_dict={x: np.arange(0, 10)[:, np.newaxis]})
    print('value: ', v)     #將輸出列向量,因給變量符號填值為列向量

mnist例子(單層神經網絡,交叉熵損失,softmax輸出):

import tensorflow as tf
#超參
learning_rate = 0.5
epochs = 10
batch_size = 100

n_x = 28 * 28  # number of features 樣本特征量
k = 10  # (n_y) number of classes 預測數字為0~9中的一個,用one-hot向量表示
n_h = 300  # number of hidden units

X = tf.placeholder(tf.float32, [None, n_x])  # 模型輸入。圖片像素28x28,
Y = tf.placeholder(tf.float32, [None, k])  # 監督值。單個樣本的標簽表示為10維的one-hot向量
W1 = tf.Variable(tf.random_normal([n_x, n_h]), name='W1')  # 以正態分布隨機初始化
b1 = tf.Variable(tf.random_normal([n_h], stddev=0.03), name='b1')
W2 = tf.Variable(tf.random_normal([n_h, k]))
b2 = tf.Variable(tf.random_normal([k], stddev=0.03))

Z1 = tf.add(tf.matmul(X, W1), b1)
A1 = tf.nn.relu(Z1)

Z2 = tf.add(tf.matmul(A1, W2), b2)
Y_hat = tf.nn.softmax(Z2)  # 模型預測值
Y_hat_clipped = tf.clip_by_value(Y_hat, 1e-10, 1 - 1e-7)  # 修剪預測值避免數值問題
# 損失函數。tf.sum是沿着軸向axis=1的,是因數據排列時軸向axis=1代表特征,
# .sum()結果是樣本量為長度的一個向量,對此求均值(.mean())
J = -tf.reduce_mean(tf.reduce_sum(Y * tf.log(Y_hat_clipped) + (1 - Y) * tf.log(1 - Y_hat_clipped), axis=1))

optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(J)

init_op = tf.global_variables_initializer()

pred = tf.equal(tf.argmax(Y, 1), tf.argmax(Y_hat, 1))  # m x 1 matrix of True|False
accuracy = tf.reduce_mean(tf.cast(pred, tf.float32))

# loading dataset

from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets(mnistdatacode_dir + '/data', one_hot=True, reshape=True)

# 訓練
with tf.Session() as sess:
    sess.run(init_op)
    n_batch = int(len(mnist.train.labels) / batch_size)
    for epoch in range(epochs):
        avg_loss = 0.0
        for i in range(n_batch):
            X_batch, Y_batch = mnist.train.next_batch(batch_size)
            _, c = sess.run([optimizer, J], feed_dict={X: X_batch, Y: Y_batch})
            avg_loss += c / n_batch
        print("epoch: {}, loss = {:.2f}%".format((epoch + 1), avg_loss * 100))
    acc = sess.run(accuracy, feed_dict={X: mnist.test.images, Y: mnist.test.labels})
    print()
    print("acc: ", acc)


#翻車了。輸出的epoch loss達3000%+,acc僅一二十個百分點

貓狗分類示例:

#例子思路參考自tensorflow中文微信公眾號
import tensorflow as tf
import os

# URL to download dataset
#https://www.floydhub.com/fastai/datasets/cats-vs-dogs/2/ train/(531M), valid/(44M)
data_dir='D:/Downloads/dogs-vs-cats/'
batch_size=32
num_epochs=3
learning_rate=1E-3

def _decode_resize(file,label):
    img_bin=tf.io.read_file(file)
    img=tf.image.decode_jpeg(img_bin)
    img=tf.image.resize(img, [256,256])/255.0
    return img, label

def load_ds(dir):
    dog_dir=os.path.join(dir,'dogs')
    cat_dir=os.path.join(dir, 'cats')
    dg=tf.constant([os.path.join(dir,'dogs',fn) for fn in os.listdir(dog_dir)])
    ct=tf.constant([os.path.join(dir,'cats',fn) for fn in os.listdir(cat_dir)])
    dx=tf.concat([ct, dg], axis=0)
    dy=tf.concat([tf.zeros(ct.shape,dtype=tf.int32),
                 tf.ones(dg.shape, dtype=tf.int32)], axis=0)
    ds=tf.data.Dataset.from_tensor_slices((dx, dy))
    ds=ds.shuffle(buffer_size=10000)
    ds=ds.map(map_func=_decode_resize, num_parallel_calls=tf.data.experimental.AUTOTUNE)
    ds=ds.batch(batch_size)
    ds=ds.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
    return ds

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(units=32, activation='relu'),
    tf.keras.layers.Dense(units=2, activation='softmax')
])

print('compiling')
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]
)
print('loading data')
tr_ds=load_ds(os.path.join(data_dir, 'train'))
print('training')
model.fit(tr_ds,epochs=num_epochs)

print('loading test data')
test_ds=load_ds(os.path.join(data_dir, 'valid'))
print('evaluating')
print(model.metrics_names)
print(model.evaluate(test_ds))

#測試是否可用GPU,當安裝的是CPU版tensorflow(即未安裝tensorflow-gpu)時會返回False
tf.test.is_gpu_avaiable()

# tf庫是否編譯入了CUDA支持(CPU版本時返回False, Nvidia GPU版本時應返回True)
tf.test.is_built_with_cuda()

tf.__version__  # tf庫的版本

安裝

安裝tensorflow(CPU版):

#激活一個conda環境
conda env list
# conda activate <CONDA-ENV>
source activate <CONDA-ENV>

#安裝tensorflow
pip install tensorflow

#安裝tensorflow 2
#要求pip >= 19.0
pip --version   #查看pip版本
pip install --upgrade pip   #升級pip
#pip install tensorflow==       #查看tensorflow包的可用版本
pip install tensorflow
#pip install tensorflow==2.0.0

GPU加速
需要

  • Nvidia顯卡及其驅動
  • CUDA工具集
  • cuDNN
  • 設置PATH環境變量
  • tensorflow(GPU版)

cuDNN的版本號是自身獨立的,沒有對應CUDA版本(如cudnn7.6適配的是cuda10),cudatoolkit的大版本號一般對應cuda大版本號(cudatoolkit10對應cuda10)。

GPU版tensorflow: tensorflow-gpu

#不必安裝依賴包'tensorflow'(即CPU版),直接安裝包'tensorflow-gpu'即可
pip install tensorflow-gpu

#運行
CUDA_VISIBLE_DEVICES=0 python FILE
#語句CUDA_VISIBLE_DEVICES=0 是設置臨時環境變量使得cuda環境使用GPU設備0
#可設置使用多個GPU設備 CUDA_VISIBLE_DEVICES=0-3 (設備號在0到3的設備,共4個) CUDA_VISIBLE_DEVICES=0,2 (設備0和2)

錯誤:CUDA driver version is insufficient for CUDA runtime version。

python庫的cudatoolkit版本和系統CUDA環境版本不一致。升級CUDA環境,或降級ccudatoolkit的python庫。
降級:

conda uninstall cudatoolkit     #會同時卸載tensorflow
conda install cudatoolkit=9.2
conda install tensorflow-gpu

如果同時安裝了CPU版和GPU版,默認運行GPU版。如果需要運行cpu版,可在使用with tf.device(''):指定要使用的設備。

測試GPU是否可用:

tf.test.is_gpu_available()
#同接口
tf.compat.v2.test.is_gpu_available()
tf.compat.v1.test.is_gpu_available()

Tensorflow Java API

依賴庫的(maven)坐標org.tensorflow:libtensorflow,需要GPU支持的需額外添加依賴org.tensorflow:libtensorflow_jni_gpu

在maven pom.xml中添加TF依賴:

<dependency>
    <groupId>org.tensorflow</groupId>
    <artifactId>tensorflow</artifactId>
    <version>1.15.0</version>
</dependency>
<dependency>
    <groupId>org.tensorflow</groupId>
    <artifactId>libtensorflow_jni_gpu</artifactId>
    <version>1.15.0</version>
</dependency>

Tensor代表張量,其內存在堆外(off-heap memory),一定要記得釋放。

SavedModelBundle代表TF SavedModel,使用完畢后記得釋放。其.session()始終返回同一個會話,即每次.session()返回值都一樣,不要調用其Session.close()除非后續一直不會再使用,否則再次跑模型時將異常,提示會話已關閉,建議釋放資源通過SavedModelBundle.close()而非Session.close(),前者除了釋放Session還會釋放Graph,而后者僅釋放了Session自己。

Scala代碼:

val labelList:List[Label] = List("pos", "neg")
val int2label = labelList.view.zipWithIndex.map(_.swap).toMap

//加載模型
val tfModel = SavedModelBundle.load("/path/to/model", "serve")

def predict(texts: scala.collection.Seq[InputTextExample]): Seq[Label] = {
    import scala.language.existentials
    val (inputIds_arr, inputMask_arr, segmentIds_arr) = text2input_arr(texts)
    Using.resource(     //自動釋放
      Using.resources(
        Tensor.create(inputIds_arr),    //創建Tensor使用了堆外內存,需釋放,這里采用自動釋放
        Tensor.create(inputMask_arr),
        Tensor.create(segmentIds_arr)
      ) { (inputIds, inputMask, segmentIds) =>
        tfModel.session
          .runner()
          .feed("input_ids", inputIds)
          .feed("input_mask", inputMask)
          .feed("segment_ids", segmentIds)
          .fetch("loss/Softmax")
          .run()
          .get(0)
      }
    ) { r =>

      assert(r.shape()(0) == texts.length && r.shape()(1) == labelList.length)

      val probs = Array.ofDim[Float](texts.length, labelList.length)
      r.copyTo(probs)
      probs.view.map(p => argmax(p)).map(int2label).toSeq
    }
  }

def close() {
    tfModel.close()
}

def text2input_arr(texts: scala.collection.Seq[InputTextExample]): (Array[Array[Int]], Array[Array[Int]], Array[Array[Int]]) = {...}

Java代碼示例:

public void init() {
    tfModel = SavedModelBundle.load("/path/to/model", "serve")
}

public java.util.List<Label> predict(texts) {
	int[][] inputIds_arr = ...;
	int[][] intpuMask_arr = ...;
	int[][] segmentIds_arr = ...;

	float[][] probs = new float[texts.length][labelList.size()];
    //利用try-with-resource語法糖自動釋放
	try (
			Tensor inputIds = Tensor.create(inputIds_arr);
			Tensor inputMask = Tensor.create(intpuMask_arr);
			Tensor segmentIds = Tensor.create(segmentIds_arr);
			Tensor r = tfModel.session().runner()
					.feed("input_ids", inputIds)
					.feed("input_mask", inputMask)
					.feed("segment_ids", segmentIds)
					.fetch("loss/Softmax").run().get(0);
	) {
		r.copyTo(probs);
	}
}

public void close() {
    tfModel.close()
}

注意版本TF Java API 1.14.0(即org.tensorflow:libtensorflow:1.14.0)有明顯低級漏洞(BUG),其根本無法初始化TF,因無法加載TF Jar自己解壓出的C++庫(至少在linux平台,其他平台暫未測試),提示錯誤

"java.lang.UnsatisfiedLinkError: /tmp/tensorflow_native_libraries-157xxxxxxxx-0/libtensorflow_jni.so: libtensorflow_framework.so.1: cannot open shared object file: No such file or directory"。

其中目錄"/tmp/tensorflow_xxx"是一個臨時目錄,故因運行而異。

FAQ

ERROR: gpu ... getattr...
cudnn、cudatoolkit等庫是否安裝?版本是否符合?是否有GPU硬件?

windows環境下錯誤信息:access viloation
如果存在讀取文件/目錄情況,有可能是因為不能找到文件,即路徑錯誤(如google bert代碼中checkpoint路徑錯誤時)。這種問題也可能因某個庫的版本間接導致。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM