###### tags: `tensorflow` # 0. Tensorflow 2.0 不負責任教學 ![](https://i.imgur.com/J7GcrTy.png) > 想到的時候就做點筆記,不然過三天之後就忘光了(x > [name=Carrot蘿蔔][time=Sun, May 31, 2020 11:33 AM][color=#DA0000] --- ## 前言 沒想到再回到tensorflow懷抱的這天 版本已經更新到了2.1.0 蘿蔔頭頂上都新開了幾場初雪 TF更早已不是當初那青澀的少年了 遙想起sess.run()的那個年代 其實說不定還是帶著幾分懷念的呢 ~ ![](https://i.imgur.com/hk6UosB.png) 好吧並沒有,我再也不想用session來開圖了 那根本就不是應該出現在python style的語言形式 :laughing: 隨著版本更迭來到了2.0.0 昔日神經網路API的另外一大巨頭<font size= 4, color=red>Keras</font> 現在被收錄到了TF底下 對於我這一路走來都信奉著TF神教的狂熱者 真的是一大福音呢 ~ 儘管我認為總有一天也要把pytorch學起來就是了 大家做的事情都差不多 不要細分那麼多彼此嘛 ~ 以下記錄了一些蘿蔔使用TF2開荒以來 遇到的各式各樣神奇的問題 又或是一些值得記錄下來的code 希望這篇文章的內容可以為需要TF2教學的你 或是過了幾個月之後忘東忘西的蘿蔔 帶來相當實質的幫助 ~~ ## 快速模型寫法 * Overview ```python import tensorflow as tf import numpy as np from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense def customize_model(x_train): # 以tensorflow的shape順序而言,第一維是batch,不需要指定給Input inputs = Input(shape= x_train.shape[1:]) conv1 = Conv2D(16, (3, 3), activation= "relu", padding= "same")(inputs) conv2 = Conv2D(16, (3, 3), activation= "relu", padding= "same")(conv1) pool = MaxPooling2D((2, 2), padding= "same")(conv2) flatten = Flatten()(pool) output = Dense(10, activation= "softmax")(flatten) model = tf.keras.Model(inputs, output) model.compile(optimizer= "Adam", loss= "categorical_crossentropy", metrics= ["accuracy"]) return model my_model = customize_model(x_train, y_train, x_valid, y_valid) history = my_model.fit(x_train, y_train, validation_data= (x_valid, y_valid), batch_size= 64, epochs= 50, shuffle= True, callback= []) ``` 在快速模型寫法當中 我們可以先直觀的定義一個ForwardPropagation的function 就像上面的`customize_model`一樣 讓假想的訓練資料一層一層的傳遞下去 直到獲得`output`之後 再使用`tf.keras.Model()`指定輸入tensor與輸出tensor 便完成了model物件初步的建立 下一步呢則是為model指定optimizer以及loss function 使用`model.compile()` 並且傳入參數`optimizer= "Adam"`以及`loss= "categorical_crossentropy"` 目前這裡是假設要做一個圖片類別的分類器啦 參數的選擇會因為任務型態的不同而有所差別 在model物件回傳回來之後 便接著使用`model.fit()`開始訓練 ## 通用模型寫法 一般而言 在通用模型中,我們會需要實作以下幾個區塊 他們分別為 :::warning * ### Models and Layers * 這應該沒什麼好說的,沒有模型的話難道要練元氣彈嗎XD * ### Hyper parameters * 我們在這一區定義出所有的超參數、路徑、損失函數、優化器等等 並同時把模型建立出來 * ### train_step and valid_step * 這兩個method會被定義成`@tf.function` 主要負責計算模型的前後傳播和梯度下降 * ### checkpoint_manager * 訓練好的model和op當然要好好保存起來呀~ ckpt manager可以幫你辦到這件事情 * ### train_model * 我們在這裡定義訓練的主要流程 (翻譯:各式各樣複雜的處理XD) ::: 這些區塊在快速寫法中 都被keras高級的API包的妥妥貼貼 造成這些寫法雖然很方便 但真要實作某些內容的時候卻又綁手綁腳 於是,我們終究還是得回來看看這些底層的寫法 並把所有區塊都寫成自己想要的樣子吧! > 底層?? > Am I a joke to you??? > [name=TensorFlow] ### Models and Layers * Customized Layer Class ```python class LAYER_NAME(tf.keras.layers.Layer): def __init__(self): super(LAYER_NAME, self).__init__() # 一些你需要的sublayers self.dense = Dense(10, activation= "softmax") def SOME_FUNCTIONS(self): # 一些你會用到的member funciton pass def call(x): # model正向傳播的流程 output = self.dense(x) return output ``` 這裡最重要的是class繼承自<font color=#B90000, size= 3>tf.keras.layers.Layer</font> 而且一定要把需要的layer定義成member * Customized Model Class ```python class MODEL_NAME(tf.keras.Model): def __init__(self): super(MODEL_NAME, self).__init__() # 一些你需要的layers self.layer = LAYER_NAME() def SOME_FUNCTIONS(self): # 一些你會用到的member funciton pass def call(x): # model正向傳播的流程 output = self.layer(x) return output ``` Model class則是繼承自<font color="#B90000">tf.keras.Model</font> ### Hyper parameters ```python # 一些你的模型中會用到的超參數 CHANNEL = 16 BATCH_SIZE = 128 EPOCH = 200 # 用超參數來為子資料夾命名,不然你根本就不知道哪個是哪個XD RUN_SETTING = "{}channel_{}batchsize_{}epoch".format(CHANNEL, BATCH_SIZE, EPOCH) CKPT_PATH = os.path.join("ckpt", RUN_SETTING) LOG_PATH = os.path.join("logs", RUN_SETTING) MODEL = MODEL_NAME() OPTIMIZER = tf.keras.optimizers.Adam() LOSS_OBJECT = tf.keras.losses.CategoricalCrossentropy() # 這是等等用來寫tensorboard的兩個物件 metrics_loss = tf.keras.metrics.CategoricalCrossentropy() metrics_acc = tf.keras.metrics.CategoricalAccuracy() ``` ### train_step and valid_step * train_step function ```python # 單個tape的寫法 @tf.function def train_step(x, y): with tf.GradientTape() as YOUR_TAPE: y_pred = MODEL(x) loss = LOSS_OBJECT(y, y_pred) gradients = YOUR_TAPE.gradient(loss, MODEL.trainable_variables) OPTIMIZER.apply_gradients(zip(gradients, MODEL.trainable_variables)) # 如果你要把數據記錄到tensorboard上面的話 # 就在這裡把loss和acc存進metrics metrics_loss(loss) metrics_acc(y, y_pred) ``` ```python # 多個tape的寫法,例如GAN中的generator和discriminator @tf.function def train_step(noise, img_true): with tf.GradientTape() as g_tape, tf.GradientTape() as d_tape: img_fake = Generator(noise) score_fake = Discriminator(img_fake) score_true = Discriminator(img_true) g_loss = loss_object(tf.ones_like(score_fake), score_fake) d_loss = loss_object(tf.zeros_like(score_fake), score_fake) +loss_object(tf.ones_like(score_true), score_true) g_grad = g_tape.gradient(g_loss, Generator.trainable_variables) d_grad = d_tape.gradient(d_loss, Discriminator.trainable_variables) g_optimizer.apply_gradients(zip(g_grad, Generator.trainable_variables)) d_optimizer.apply_gradients(zip(d_grad, Discriminator.trainable_variables)) # 如果你要把數據記錄到tensorboard上面的話 # 就在這裡把loss和acc存進metrics metrics_loss(g_loss) metrics_acc(score_fake, tf.ones_like(score_fake)) ``` 首先train_step必須被定義為<font color=red>@tf.function</font> 接著資料流將會經過以下數個階段 1. 通過模型計算Forward Propagation 2. 用損失函數計算loss 3. 取出模型中可訓練參數的梯度 4. 讓optimizer對參數進行梯度下降 5. 記錄參數以便在tensorboard上觀察數據變化 (optional) 值得一提的是,如果你的網路中存在著需要<font color=red>迭代訓練的多個模型</font> 比如說GAN中的**generator和discriminator** 那就可以使用上方第二個多個tape的寫法 各自定義出各自的tape,獨立地進行梯度下降 這樣一來 就算網路架構再複雜都不用怕啦 ~ ### checkpoint_manager ```python # 定義這個ckpt要存下那些物件,通常會把model本身以及optimizer存起來 ckpt = tf.train.Checkpoint(model= YOUR_MODEL, optimizer= YOUR_OPTIMIZER) ckpt_manager = tf.train.CheckpointManager(ckpt, CKPT_PATH, max_to_keep= 5) if ckpt_manager.latest_checkpoint: ckpt.restore(ckpt_manager.latest_checkpoint) # 確認最新的資料是已經練到第幾個epoch的權重 last_epoch = int(ckpt_manager.latest_checkpoint.split("-")[-1]) print("讀取最新的 checkpoint,模型已訓練 {} 個 epochs ".format(last_epoch)) # 將model和optimizer從ckpt中讀取出來 model = ckpt.model optimizer = ckpt.optimizer else: last_epoch = 0 print("沒找到 checkpoint,即將開始重新訓練。") # 後續在train_model當中,可以自行決定存檔週期 # 使用以下的函式 # 這個回傳值為儲存路徑,其實用不太到 ckpt_save_path = ckpt_manager.save() ``` 這個大區塊主要處理了以下幾件事情 1. 定義儲存路徑 2. 定義ckpt以及manager 3. 檢查目標路徑中是否存在著之前已經訓練過的網路 如果有的話就讀進來測試或繼續訓練 沒有的話就重頭開始 4. <font color=red>(在訓練的迴圈中)</font>用manager儲存權重 千萬記得要在訓練過程中才call`ckpt_manager.save()`呀 我們會在下個區塊中再次看到它~ ### train_model ```python # 這個writer等等會拿來將數據寫到tensorboard上 summary_writer = tf.summary.create_file_writer(LOG_PATH) for epoch in range(last_epoch + 1, EPOCH + 1): # 怒train它一發,這裡假設你已經把資料預處理成batch的形式 for x, y in zip(x_train, y_train): train_step(x, y) # 存檔! 再次申明這個回傳值基本上用不太到 ckpt_save_path = ckpt_manager.save() # 寫入tensorboard with summary_writer.as_default(): tf.summary.scalar("Train_Loss", metrics_loss.result(), step=epoch) tf.summary.scalar("Train_Accuracy", metrics_accuracy.result(), step=epoch) # 把存在metrics裡的內容清空 metrics_loss.reset_states() metrics_acc.reste_states() ``` train_model的核心步驟如下 1. 建立一個用來遍歷epoch的迴圈 2. 把每個batch丟進去train 3. ckpt存檔 4. logs存檔 (寫入tensorboard) 5. 重置metrics的內容