###### tags: `tensorflow`
# 0. Tensorflow 2.0 不負責任教學

> 想到的時候就做點筆記,不然過三天之後就忘光了(x
> [name=Carrot蘿蔔][time=Sun, May 31, 2020 11:33 AM][color=#DA0000]
---
## 前言
沒想到再回到tensorflow懷抱的這天
版本已經更新到了2.1.0
蘿蔔頭頂上都新開了幾場初雪
TF更早已不是當初那青澀的少年了
遙想起sess.run()的那個年代
其實說不定還是帶著幾分懷念的呢 ~

好吧並沒有,我再也不想用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的內容