<jupyter_start><jupyter_text>基於TesnorFlow平台的基本模型建立--- 載入必要函式庫以及資料集為了在`TensorFlow`建立模型，我們需要導入以下函式庫：* tensorflow * tensorflow-datasets以及常用的資料處理函式庫：* Numpy * Pandas我們可以透過`tf.config.list_physical_devices('GPU')`來確認GPU是否可用，並選擇在CPU或是GPU上運行我們的程式。<jupyter_code>import tensorflow as tf 
import tensorflow_datasets as tfds
import numpy as np
import pandas as pd

tf.config.list_physical_devices('GPU')<jupyter_output><empty_output><jupyter_text>--- 匯入MNIST資料集在此範例中，我們一樣會使用MNIST資料集進行示範。在載入時，我們需要使用`tensorflow-datasets`的`tfds.load()`函式來抽取MNSIT資料集。並利用經由`tf.data.Dataset`的`map()`方法對資料集進行轉置；最後，我們設定批次數量為64。我們將分別導入訓練資料集以及測試資料集作為訓練以及測試使用。<jupyter_code>train_ds, train_ds_info = tfds.load(name="mnist", 
                    split='train', 
                    with_info=True, 
                    as_supervised=True)

test_ds, test_ds_info = tfds.load(name="mnist", 
                    split='test', 
                    with_info=True, 
                    as_supervised=True)

def preprocess(x, y):
    x = x/255
    x = tf.reshape(x, [784])

    y = tf.one_hot(y, 10)  
    return x, y

train_ds = train_ds.map(lambda x, y: preprocess(x, y)).batch(64)
test_ds = test_ds.map(lambda x, y: preprocess(x, y)).batch(64)<jupyter_output>[1mDownloading and preparing dataset mnist/3.0.1 (download: 11.06 MiB, generated: 21.00 MiB, total: 32.06 MiB) to /root/tensorflow_datasets/mnist/3.0.1...[0m<jupyter_text>--- 建立順序式模型在`TensorFlow`之中，建立順序是模型可以透過`tf.kears.Sequential()`函式來進行。主要有兩種作作法。1. 先行建立`model`物件，並透過`model.add()`方法為模型添加結構。2. 利用`tf.kears.Sequential`建立模型時，直接在參數內填入模型結構。  方法一<jupyter_code>model_1 = tf.keras.Sequential()
model_1.add(tf.keras.layers.InputLayer(input_shape=(784)))
model_1.add(tf.keras.layers.Dense(128, activation='relu'))
model_1.add(tf.keras.layers.Dense(64, activation='relu'))
model_1.add(tf.keras.layers.Dense(10, activation='softmax'))

model_1.summary()<jupyter_output>Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 128)               100480    
                                                                 
 dense_1 (Dense)             (None, 64)                8256      
                                                                 
 dense_2 (Dense)             (None, 10)                650       
                                                                 
=================================================================
Total params: 109,386
Trainable params: 109,386
Non-trainable params: 0
_________________________________________________________________<jupyter_text>方法二<jupyter_code>model_2 = tf.keras.Sequential([
                             tf.keras.layers.InputLayer(input_shape=(784)), 
                             tf.keras.layers.Dense(128, activation='relu'), 
                             tf.keras.layers.Dense(64, activation='relu'), 
                             tf.keras.layers.Dense(10, activation='softmax'),
                            ])

model_2.summary()<jupyter_output>Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_3 (Dense)             (None, 128)               100480    
                                                                 
 dense_4 (Dense)             (None, 64)                8256      
                                                                 
 dense_5 (Dense)             (None, 10)                650       
                                                                 
=================================================================
Total params: 109,386
Trainable params: 109,386
Non-trainable params: 0
_________________________________________________________________<jupyter_text>--- 設定各項超參數、損失函數以及優化器<jupyter_code>EPOCHS = 20 #@param {type:"slider", min:1, max:100, setp: 1}
LR = 1e-2 #@param {type:"number"}
OPTIMIZER = "adam" #@param ["adam", "SGD"]
LOSS_FN = "binary_crossentropy" #@param ["binary_crossentropy", "categorical_crossentropy"]

if OPTIMIZER == 'adam':
    optimizer = tf.keras.optimizers.Adam(learning_rate = LR)
elif OPTIMIZER == 'SGD':
    momentum = 0.4 #@param {type:"slider", min:0, max:1, step:0.1}
    optimizer = tf.keras.optimizers.SGD(learning_rate = LR, 
                                        momentum = momentum)<jupyter_output><empty_output><jupyter_text>--- 進行訓練在建立模型，並設定好超參數以及優化器之後，我們可以利用`model.compile()`對模型進行編譯，並利用`model.fit()`進行訓練。 編譯模型在模型建立之後，我們必須利用`model.compile()`對模型的優化器、損失函數以及評估量尺等選項進行設定。需要設定的選項如下：* 優化器（optimizer）* 損失函數（loss function）* 評估量尺（metrics）可選設定如下：* 損失函數權重（loss weights）* 權重矩陣（weighted metrics）* 除錯模式（run_eagerly）> 更多選項可以至[官方文件](https://www.tensorflow.org/api_docs/python/tf/keras/Modelcompile)了解<jupyter_code>model_1.compile(loss = LOSS_FN, 
                optimizer = optimizer, 
                metrics=['acc'])<jupyter_output><empty_output><jupyter_text>--- 進行訓練當模型編譯完成後，我們就可以開始進行訓練。訓練的方式為利用`model.fit()`函數搭配相關的參數進行訓練。`model.fit()`在訓練結束後，將會回傳一個帶有訓練歷史資訊的物件，我們可以建立一個`history`變數用以儲存回傳值，並在後續對訓練過程進行了解。必要的參數有：* 輸入資料以及標籤（x, y）* 批次內資料數量（batch_size）* 週期數量（epochs）* 顯示模式（verbose）> 資料輸入的形式以及設定可以參照我們先前的[介紹](https://hackmd.io/@davidho9713/HyfHBzcxY/%2F86N1lvF2SdaRkcnuuWeJTg)。可選設定如下：* 回測函式（callbacks）* 驗證資料集以及標籤（validation_data）* 驗證比例（validation_split）> 更多的選項可至[官方文件](https://www.tensorflow.org/api_docs/python/tf/keras/Modelcompile)查閱。<jupyter_code>history = model_1.fit(train_ds, 
            epochs = EPOCHS, 
            verbose = 1)<jupyter_output>Epoch 1/20
938/938 [==============================] - 29s 25ms/step - loss: 0.0420 - acc: 0.9302
Epoch 2/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0226 - acc: 0.9651
Epoch 3/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0187 - acc: 0.9713
Epoch 4/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0166 - acc: 0.9752
Epoch 5/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0154 - acc: 0.9769
Epoch 6/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0137 - acc: 0.9798
Epoch 7/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0130 - acc: 0.9805
Epoch 8/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0121 - acc: 0.9821
Epoch 9/20
938/938 [==============================] - 8s 9ms/step - loss: 0.0113 - acc: 0.9834
Epoch 10/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0111 - acc: 0.9843
Epoch 11/20
938/938 [==========================[...]<jupyter_text>--- 查看學習曲線在上一個部份，我們對模型進行了訓練，並取得了一個帶有模型訓練資訊的`history`變數。我們可以先透過內建的`__dict__`方法查看`history`物件下帶有哪些資訊。<jupyter_code>print(f"Type of history: {type(history)}")

history.__dict__<jupyter_output>Type of history: <class 'keras.callbacks.History'><jupyter_text>由上方的執行結果，我們可以得知幾項資訊：1. `history`變數為`keras.callbacks.History`物件，並非字典物件。雖然可以利用`__dict__`方法將內部資料以字典方式來輸出，但並無法利用字典物件所具有的`.keys()`、`.values()`以及`.items()`方法來提取資訊。2. `history`變數下的`history`分枝儲存了以下重要資訊：    * `acc`    * `loss`接下來我們將利用`acc`以及`loss`資訊來作圖，顯示訓練中的準確率以及損失值變化。<jupyter_code>import matplotlib.pyplot as plt

x = [i for i in range(1, EPOCHS+1)]
plt.figure(figsize=(8,6))
plt.plot(x, history.history['loss'], label='loss')
plt.legend(loc='upper right')
plt.xlabel('Epochs')
plt.ylabel('loss')
plt.show()

plt.figure(figsize=(8,6))
plt.plot(x, history.history['acc'], label='acc')
plt.legend(loc='upper right')
plt.xlabel('Epochs')
plt.ylabel('acc')
plt.show()<jupyter_output><empty_output><jupyter_text>--- 利用Functional API 建立自定義模型先前的部份中，我們介紹了如何利用`tf.keras.Sequential`建立順序式模型。對於機器學習專案，常態下我們需要建立各式各樣的自訂模型結構。例如`ResNet-50`中所利用的殘差連結（Shortcut connection/Residual connection）就是一種無法利用順序式模型來建立的結構。我們將在此章節示範如何利用`Functional API`建立自定義的模型結構。> 本章將不會實作殘差連結。有興趣者可以參閱此[介紹](https://medium.com/@hupinwei/%E6%B7%B1%E5%BA%A6%E5%AD%B8%E7%BF%92-resnet%E4%B9%8B%E6%AE%98%E5%B7%AE%E5%AD%B8%E7%BF%92-f3ac36701b2f)`Functional API`是一個由`Keras`套件所提供的實作方法，可以讓我們以更加彈性的方式建立我們所希望的模型結構。無論是非線性拓樸，或是共享層以及多重輸入/輸出都可以藉由`Functional API`來實踐。參考我們在先前所建立的順序式模型，如果我們要利用`Functional API`來建立相同的模型，要如何來達成呢？且看以下範例。<jupyter_code># 建立輸入層
inputs = tf.keras.layers.Input(shape=(784))

# 建立全連結層
dense_1 = tf.keras.layers.Dense(128, activation='relu')(inputs)
dense_2 = tf.keras.layers.Dense(64, activation='relu')(dense_1)
outputs = tf.keras.layers.Dense(10, activation='softmax')(dense_2)


# 封裝模型
model = tf.keras.Model(inputs = inputs, 
                       outputs = outputs, 
                       name='functional_api_model')

model.summary()<jupyter_output>Model: "functional_api_model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_3 (InputLayer)        [(None, 784)]             0         
                                                                 
 dense_6 (Dense)             (None, 128)               100480    
                                                                 
 dense_7 (Dense)             (None, 64)                8256      
                                                                 
 dense_8 (Dense)             (None, 10)                650       
                                                                 
=================================================================
Total params: 109,386
Trainable params: 109,386
Non-trainable params: 0
_________________________________________________________________<jupyter_text>在上方的範例中，我們於第二行利用`tf.keras.Input`函式建立了模型的輸入點，用以承接導入模型的資料；從第五行開始，我們建立了一系列的全連結層。利用`Functional API`建立模型時最大的不同在於，我們將會在每一層的設定時，額外於行末利用括號宣告將會輸入的資料。以第五行為例：```dense_1 = tf.keras.layers.Dense(128, activation='relu')(inputs)```在`Dense`函數的後方，與以往不同的多出了一個`(inputs)`，這寫法的意義在於，告知`Functional API`，本層的輸入函數為`inputs`所輸出的結果。我們可以在第六行以及第七行發現相同的寫法。在完成模型結構的建構後，最重要的一步就是將模型進行封裝。在順序式模型中，因為具有明確的順序關係，`Sequential`函式將會自動封裝整個模型。在利用`Functional API`建構的模型中，我們必須自行宣告模型的輸入以及輸出，並利用`tf.keras.Model`函式對模型進行封裝。  編譯並進行訓練在完成對模型的封裝以後，我們就可以利用與先前相同的方法，對模型進行編譯以及訓練。<jupyter_code>model.compile(loss = LOSS_FN, 
                optimizer = optimizer, 
                metrics=['acc'])

history = model.fit(train_ds, 
            epochs = EPOCHS, 
            verbose = 1)<jupyter_output>Epoch 1/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0598 - acc: 0.8998
Epoch 2/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0343 - acc: 0.9441
Epoch 3/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0289 - acc: 0.9528
Epoch 4/20
938/938 [==============================] - 8s 9ms/step - loss: 0.0262 - acc: 0.9581
Epoch 5/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0242 - acc: 0.9617
Epoch 6/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0227 - acc: 0.9639
Epoch 7/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0216 - acc: 0.9656
Epoch 8/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0202 - acc: 0.9678
Epoch 9/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0197 - acc: 0.9682
Epoch 10/20
938/938 [==============================] - 8s 8ms/step - loss: 0.0192 - acc: 0.9694
Epoch 11/20
938/938 [============================[...]<jupyter_text>同樣的，我們也能用`.fit()`函式回傳的模型訓練資料進行作圖，並顯示訓練中的準確度以及損失值變化。<jupyter_code>import matplotlib.pyplot as plt

x = [i for i in range(1, EPOCHS+1)]
plt.figure(figsize=(8,6))
plt.plot(x, history.history['loss'], label='loss')
plt.legend(loc='upper right')
plt.xlabel('Epochs')
plt.ylabel('loss')
plt.show()

plt.figure(figsize=(8,6))
plt.plot(x, history.history['acc'], label='acc')
plt.legend(loc='upper right')
plt.xlabel('Epochs')
plt.ylabel('acc')
plt.show()<jupyter_output><empty_output><jupyter_text>--- 利用自定義類別建立模型在先前的部份，我們嘗試利用`Functional API`建立自訂結構的模型。但在此之上，我們還能有更加彈性的選擇，那就是利用繼承了`tf.keras.Model`類別來進行模型的建立。此方法有幾個必要的元素：1. 一個繼承了`tf.keras.Model`的類別。2. 類別內必須實作`__init__`以及`call`兩個方法。3. 類別必須利用`spuer().__init__()`方法繼承`tf.keras.Model`的初始化設定。以下是利用此方法建立模型的範例。我們將使用與先前相同的模型結構來進行示範。 模型設定<jupyter_code>class MLP_MODEL(tf.keras.Model):
    def __init__(self, **kargs):
        super(MLP_MODEL, self).__init__(**kargs)
        self.dense_1 = tf.keras.layers.Dense(128, activation='relu')
        self.dense_2 = tf.keras.layers.Dense(64, activation='relu')
        self.dense_3 = tf.keras.layers.Dense(10, activation='softmax')
    def call(self, inputs):
        y = self.dense_1(inputs)
        y = self.dense_2(y)
        outputs = self.dense_3(y)
        return outputs

model = MLP_MODEL()<jupyter_output>Epoch 1/20
938/938 [==============================] - 13s 11ms/step - loss: 0.2687
Epoch 2/20
938/938 [==============================] - 5s 5ms/step - loss: 0.1131
Epoch 3/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0770
Epoch 4/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0566
Epoch 5/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0428
Epoch 6/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0337
Epoch 7/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0277
Epoch 8/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0239
Epoch 9/20
938/938 [==============================] - 5s 6ms/step - loss: 0.0205
Epoch 10/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0179
Epoch 11/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0175
Epoch 12/20
938/938 [==============================] - 5s 5ms/step - loss: 0.0150
Epoch 13/20
938/938 [==[...]<jupyter_text>以上這種方法，又稱`模型子類化(model subclassing)`。有別於先前的建立模式，利用這種方式所建立的模型不需要經由`model.compile()`函式進行編譯，也不需要利用`model.fit()`進行訓練。> 若使用者希望，也仍然可以在建立好模型後利用`.compile()`以及`.fit()`來進行訓練。利用此方法建立的模型將具有更大的自由度以及更加彈性的架構，但相對的其設定也變得更加複雜且繁瑣。  設定損失函數以及優化器首先，我們一樣要設訂我們所選用的損失函數以及優化器。予以往不同，在此方法中不能透過給定`loss='categorical_crossentropy`以及`optimizer='adam'`來進行指定，必須直接建立一個物件來進行使用。所以我們透過下方第一行以及第二行的程式碼來進行優化器和損失函數的建立。```loss_object = tf.keras.losses.CategoricalCrossentropy()optimizer = tf.keras.optimizers.Adam()```在訓練過程中，每一批次的資料都會進行損失值的計算，並回傳給我們所建立的變數。而每一週期的損失值，即為該週期內所有損失值的平均。我們可以透過將計算好的損失值傳入`tf.keras.metrics.Mean()`函式進行計算，並得到該當週期的損失值。在準確率的部份，我們可以利用將預測值以及真實值傳入`tf.keras.metrics.CategoricalAccuracy()`函式來進行比對，並求出準確率。> 為何選用`CategoricalAccuracy`？因為我們採用`one-hot`編碼對真實值進行編碼，並且輸出10個元素的陣列，故需要利用`CategoricalAccuracy`來對兩個`one-hot`編碼的陣列進行比對，並計算準確率。<jupyter_code>loss_object = tf.keras.losses.CategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam()

train_loss = tf.keras.metrics.Mean(name='train_loss')
train_acc = tf.keras.metrics.CategoricalAccuracy(name='train_acc')

test_loss = tf.keras.metrics.Mean(name='test_loss')
test_acc = tf.keras.metrics.CategoricalAccuracy(name='test_acc')<jupyter_output><empty_output><jupyter_text>設定訓練以及測試流程透過`模型子類化`建立的模型，需要利用`tf.GradientTape`函式來進行訓練；梯度的計算，模型的優化則透過先前建立的`loss_object`、`optimizer`以及相對應的`metrics`物件進行處理。我們可以將訓練以及測試的流程包裝成函數來簡化我們的流程。在`train_step`中，首先我們先宣告```with tf.GradientTape() as tape:```來讓程式紀錄訓練過程中的梯度變化。然後利用將資料導入模型來取得預測資訊，再利用損失函數計算損失值。再取得損失值之後，我們可以計算梯度並利用梯度進行優化。最後我們將利用期間取得的損失值以及預測資訊來計算週期內的損失值以及準確率。在測試階段，因為不需要進行梯度的計算，故可以省略呼叫`tf.GradientTape`和計算梯度以及優化模型的部份。或許有人注意到，我們在`train_step`以及`test_step`函式的定義前加了一個`@tf.function`的裝飾器。這是在`TensorFlow 2.0`以上版本中加入的動態計算圖功能，可以將使用者建立的函式在`eager`模式下進行運算，並在初次運算時建立動態圖，並在後續重複調用。> 詳細介紹可至[本文章](https://medium.com/%E8%BB%9F%E9%AB%94%E4%B9%8B%E5%BF%83/tensorflow-%E5%AF%A6%E5%8B%99%E8%A7%A3%E6%9E%90tf-function-1-%E7%9C%8B%E4%BC%BC%E7%B0%A1%E5%96%AE%E5%AF%A6%E8%80%8C%E8%A4%87%E9%9B%9C-d344d6a33f44)了解<jupyter_code>@tf.function
def train_step(images, labels):
    with tf.GradientTape() as tape:
        pred = model(images, training=True)
        loss = loss_object(labels, pred)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    train_loss(loss)
    train_acc(labels, pred)

@tf.function
def test_step(images, labels):
    test_pred = model(images, training=False)
    test_losses = loss_object(labels, test_pred)

    test_loss(test_losses)
    test_acc(labels, test_pred)<jupyter_output><empty_output><jupyter_text>進行訓練以及測試在完成了訓練及測試流程的編寫夠，我們就可以透過將資料導入建立好的函式來進行訓練和測試了。在這邊需要注意的只有必須在每週其開始時將各項量尺的狀態進行重設。<jupyter_code>for epoch in range(EPOCHS):
    # Reset the metrics at the start of the next epoch
    train_loss.reset_states()
    train_acc.reset_states()
    test_loss.reset_states()
    test_acc.reset_states()

    for images, labels in train_ds:
        train_step(images, labels)

    for test_images, test_labels in test_ds:
        test_step(test_images, test_labels)

    print(
    f'Epoch {epoch + 1}, '
    f'Loss: {train_loss.result()}, '
    f'Accuracy: {train_acc.result() * 100}, '
    f'Test Loss: {test_loss.result()}, '
    f'Test Accuracy: {test_acc.result() * 100}'
    )<jupyter_output>Epoch 1, Loss: 0.10257095098495483, Accuracy: 97.24166107177734, Test Loss: 0.1258823424577713, Test Accuracy: 96.86000061035156
Epoch 2, Loss: 0.09232383221387863, Accuracy: 97.52833557128906, Test Loss: 0.1455402821302414, Test Accuracy: 96.68000030517578
Epoch 3, Loss: 0.09847172349691391, Accuracy: 97.39666748046875, Test Loss: 0.15411247313022614, Test Accuracy: 96.55000305175781
Epoch 4, Loss: 0.0830605998635292, Accuracy: 97.76166534423828, Test Loss: 0.15152032673358917, Test Accuracy: 96.80000305175781
Epoch 5, Loss: 0.08318177610635757, Accuracy: 97.80332946777344, Test Loss: 0.15016533434391022, Test Accuracy: 96.76000213623047
Epoch 6, Loss: 0.07981283962726593, Accuracy: 97.91667175292969, Test Loss: 0.15467844903469086, Test Accuracy: 96.81999969482422
Epoch 7, Loss: 0.07160687446594238, Accuracy: 98.11000061035156, Test Loss: 0.13336355984210968, Test Accuracy: 97.05999755859375
Epoch 8, Loss: 0.07107507437467575, Accuracy: 98.13833618164062, Test Loss: 0.191660836338996[...]
{'fraction_non_alphanumeric': 0.20241877055118987, 'fraction_numerical': 0.08549294989689572, 'mean_word_length': 4.096279466060778, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 64, 'https://': 5, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 23, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '82123', 'license': 'MIT', 'n_tokens_mistral': 9351, 'n_tokens_neox': 8784, 'n_words': 889, 'path': 'Build_model_tensorflow.ipynb', 'repo_name': 'davidho27941/ML4NO', 'size': '17943'}