gpxclear寄存器写0和写1_画图,搭积木,写对象 [TF 笔记 0]
前言
從2019年9月底到現在,TF 2.0 已經正式發布三個多月了。但其實很多和 2.0 相關的特性,比如說 eager 模式,@tf.function 裝飾器和 AutoGraph, 以及 keras 風格的模型,在 TF 1.x 的后期版本中已經可以使用了。在過去的一年里,我和我們組的成員也一直在斷斷續續的把我們的代碼框架往 TF 2.x 的風格開始遷移。
回想我最早用 Tensorflow 是在2016年底,那時候 TF 1.0 還沒有正式登場。在那之前涉及到所有神經網絡的工作用的一直是 Caffe 和 MatConvNet, 更早一些是 Theano 。這三年來對 TF 的一個感受就是:模型訓練越來越快,部署越來越方便,但也實現起來也越來越復雜。雖然對于 TF 也有些迷思 (比如說很長一段時間里 TF 的卷積 (tf.nn.conv2d) 都不支持 Group Convolution,最后還是來自 FB 的研究人員提交的 PR,而且直到現在 TF 的文檔里也壓根沒提這事。。。),但整體來說我覺得它還是很好的一個框架。
我近幾年的工作主要是人臉檢測與識別,以及短視頻分析。用 TF 主要是來訓練卷積神經網絡,也涉及到一些 LSTM 的工作。我會試著從模型搭建,模型訓練,模型導出,分布式訓練以及如何寫自定義的層 (Layer) / 優化器 (Optimizer)/ 學習率 (Learing Rate Schedule) / 損失函數 Loss Function 等各個方面來看使用 TF 訓練卷積神經網絡的一路發展過來的變化。聊一聊在這個過程中踩過的一些坑,試著總結一下經驗。
題圖是去年參加 ICCV 時在會場旁邊拍的一個圖書館,與內容無關。
1. Tensorflow的模型有幾種寫法? 從畫圖,到搭積木,再到寫對象
1.1 計算圖
在最早期的 Tensorflow 里,寫模型的過程其實就是構造計算圖 (Computational Graph) 的過程。計算圖中的每一個節點 (Node) 定義了一個操作 (Operation),而計算圖中的邊 (Edge) 就是計算的數據 (Tensor) 。(嚴格來說還有另一種定義節點依賴關系 (Control Dependencies) 的邊,它們不承載數據,但是控制計算圖執行時節點的順序關系。)
圖1 計算圖示例計算圖中的節點大致可以分為兩類,一類是數據節點,一類是計算節點。假設我們想要構建一個兩層的分類器,我們需要定義四個數據節點表示變量 [W1, b1, W2, b2],以及一個數據節點表示輸入數據 x,然后通過調用 tf.nn 下的各類計算節點來完成最后計算圖的構建,代碼如下所示:
def nn(x, num_classes):W1 = tf.Variable(tf.random_uniform([32, 64],-1,1), name='W1')b1 = tf.Variable(tf.zeros([64]), name='b1')W2 = tf.Variable(tf.random_uniform([64, num_classes],-1,1), name='W2')b2 = tf.Variable(tf.zeros([num_classes]), name='b2')dense_1 = tf.nn.relu(tf.matmul(x, W1) + b1)dense_2 = tf.nn.relu(tf.matmul(dense_1, W2) + b2)probs = tf.nn.softmax(dense_2)return probs x_tensor = tf.placeholder(tf.float32, shape=[None, 32], name='input') probs = nn(x_tensor, num_classes=10) TF 2.x (以及后期的 TF 1.x) 的一個重大的變化是 Variable 不再是一個與計算圖相關的全局變量, 而是與對象相關的一個局部變量。具體來說,在TF 1.x (早期的) 里,每當你新建一個 Variable 時,計算圖中會添加一個節點,我們可以通過 get_collection 的方法來收集圖中所有的 Variable。而在 TF2.x 里,每個 Variable是與創建它的對象相關的,對象可以是某個卷積層,也可以是某個模塊,也可以是整個模型。更多內容可以參考 TF community 的 RFC 文檔。以上的代碼完成了計算圖的構建。TF 1.x 對于很多的操作提供一個更高層的 API tf.layers (TF 1.x 比較后期的版本需要訪問 tf.compat.v1.layers)。layers 封裝了計算和生成相關變量的過程,用 layers 來構建計算圖的代碼如下:
def nn(x, num_classes):dense1 = tf.compat.v1.layers.dense(x, 64, activation=tf.nn.relu, use_bias=True, name='fc1')dense2 = tf.compat.v1.layers.dense(dense1, num_classes, activation=tf.nn.relu, use_bias=True, name='fc2')probs = tf.nn.softmax(dense2)return probsx_tensor = tf.placeholder(tf.float32, shape=[None, 32], name='input') probs = nn(x_tensor, num_classes=10)這種方法使用起來更方便,但本質和之前的方法是一樣的。
1.2 搭積木
關于 TF 2.0 的吐槽中,有一部分是關于 Keras 的。事實上從 TF 1.4 開始, Keras 就已經出現在了 TF 的 API 中。而且從 TF 1.9 開始,所有的 tf.layers 里的類,其實都是繼承自 Keras.layer 相對應的類。所以不管你喜歡不喜歡,只要你用了 tf.layers, 你就是在用 Keras 相關的設計 lol
用 Keras 搭建模型主要有兩種方式,分別是 Symbolic 式和 Imperative 式。(我實在不知道怎么翻譯這兩個詞。。。) 第一種就像是搭積木,第二種是直接寫一個 python 風格的對象 (pythonic) 。
搭積木的方法有兩種,第一種是序列化 (Sequential) 的方法。我們從輸入到輸出,將定義好的層一個個的連接起來,合并到一個 Sequential 的對象中去。
from tf.keras import layers model = tf.keras.Sequential([layers.Dense(64, activation='relu', input_shape=(32,), name='fc1'),layers.Dense(10, activation='relu', name='fc2'),layers.Activation(activation='softmax', name='sm')], name='nn')這種方法有一個缺陷是對于每一層,我們只能使用一個輸入一個輸出,所以這種方法對于大部分的現代卷積神經網絡 (ResNet, DenseNet) 是沒有用的。TF 提供了另一種搭積木的方法,就是使用 Functional API。在 Functional API 下,我們不僅定義層,同時直接搭建出計算的過程。
from tf.keras import layers def get_model(num_classes):inputs = keras.Input(shape=(32,), name='input')x = layers.Dense(64, activation='relu')(inputs)x = layers.Dense(10, activation='relu')(x) outputs = layers.Activation(activation='softmax')(x)model = keras.Model(inputs=inputs, outputs=outputs, name='nn')return model如果說 Sequential 的方式是在搭 Jenga, 那么 Function API 的方式更像是搭樂高,它更加的靈活,當我們需要使用多個輸入或者多個輸出的時候,只需要將將它們拼接起來即可,比如下面的這一個 Skip Connection 結構:
x1 = layers.Dense(64, activation='relu')(inputs)x2 = layers.Dense(64, activation='relu')(x1)x3 = x2 + x1但是這兩種方法依然會有一些問題:
1.3 最靈活的方法:Subclassing
為了得到最大的靈活性,TF 提供一種面對對象的方式來構造模型。具體來說,我們繼承一 個 keras.model ,自己定義模型初始化以及前向傳播的過程 (后向傳播的過程會由 TF 自動生成)。以下是一個 Subclassing 的例子:
from tf.keras import layersclass NN(tf.keras.Model):def __init__(self, units=64, num_classes=10):super(NN, self).__init__()self.units = unitsself.num_classes = num_classesself.dense1 = layers.Dense(self.units, activation='relu')self.dense2 = layers.Dense(self.num_classes, activation='relu')self.sm = layers.Activation(activation='softmax')def call(inputs, training=True):net = self.dense1(inputs)net = self.dense2(net) probs = self.sm(net)return probs在示例中我們可以看到,在 call 方法里我們有一個 argument 是 training (在這個例子中其實并沒有用,但是當我們使用 BN 還有 DropOut 時會非常有用),當調用模型時我們可以通過該參數來控制模型的運行模態:
# model initialization nn = NN(units=64, num_classes=10)# training mode probs = nn(x, training=True) # testing mode probs = nn(x, training=False) Keras.Model 的具體實現:keras.Model 繼承于 keras.Network , keras.Network 繼承于 keras.Layer 。具體來說,Layer 定義了基本的運算單元,以及 Variable 的初始化過程;Network 定義了網絡的結構,這里的結構同時包含了前向傳播和后向傳播 (也就說 Optimizer 也包含在了 Network 里) 的網絡,同時 Network 中也定義了 Save 方法,也就是說當我們導出模型時其實是從 Network 這個層次發起的;Model 在 Network 的基礎上添加了訓練 (training),驗證 (evaluation) 以及測試 (testing) 的實現。雖然在 TF 中較晚才引入,但其實 Subclassing 并不是一個多新的概念,事實上在其它的框架中 (pyTroch, mxNet 等)很早有就了。 keras 的作者 Fran?ois Chollet 曾經發過這么一個推來比較不同框架下寫一個 RNN 的代碼,看上去是不是很像?
圖2 不同框架 (TensorFlow, MXNet, Chainer, PyTorch) 下寫出的 RNN Image Credits: Fran?ois Chollet1.4 如何選擇
以上就是我使用過的一些方法,但是在實際應用中,我們到底該如何選擇呢?我覺得這個問題并沒有一個固定的答案,它是由你的任務,你的預期以及生產環境決定的。
- 如果你的模型和數據量都偏小,單卡訓練就可以,那么我覺得 Sequential 和 Function API 就足夠了;
- 如果你模型和數據量都比較大,訓練時間對你來說最關鍵,需要多卡 MirroredStrategy 甚至用到 TPU,或者你有很多自定義的層,那么 Subclassing 會更靈活;
- 如果部署問題對你來說是最關鍵的,后期需要做 TensorRT 或者 OpenVino 優化,那么用最底層的方式來寫模型可能會更合適。
---------------------------------------------------------------------------------------
下一篇會從模型訓練的角度來分析一下 TF 代碼風格的演變,主要會介紹和分析一下 TF 是如何從自定義訓練 (Session-based Custom Training), 到 Estimator,再到 Build-in 訓練,最后又回到自定義訓練 (GradientTape-based Custom Training) 的。
參考文獻:
總結
以上是生活随笔為你收集整理的gpxclear寄存器写0和写1_画图,搭积木,写对象 [TF 笔记 0]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 快手极速版弹幕功能怎么关闭
- 下一篇: java 图片压缩100k_如何将图片压