【筆記】使用 TensorFlow Keras 訓練圖像分類模型,透過 Streamlit 將模型部署為網路應用 ,並集成到 Android 應用程式上
這裡記錄一下這幾天學習利用 Tensorflow Keras 和現有的圖像資料集,訓練一個簡單的圖像分類(Image Classification)模型,並透過 Streamlit 和 GitHub,將模型部署到網路服務上來測試,接著將模型轉換為適用於行動裝置的 TFLite 模型,並集成到 Android 應用程式上的過程。另外也直接透過 TFLite Model Maker 庫來訓練 TFLite 模型,並比較兩者的預測準確度。
註:TensorFlow Lite Model Maker 庫可以簡化將 TensorFlow 神經網路模型適配和轉換為特定輸入資料的過程。
我使用這篇文章裡面的 Intel Image Classification 圖像資料集來進行學習和測試。它包含了建築物(buildings)、森林(forest)、冰河(glacier)、山岳(mountain)、海洋(sea)、街道(street)等六種類型的圖像,總共有 14,000 多張訓練圖像、3000 多張驗證圖像,以及 7000 多張測試圖像,可以用來訓練出一個簡單的圖像分類模型。
下載完後照片資料集後,將 seg_train、seg_pred、seg_test 這三個資料和裡面的照片,上傳到 Google Drive 上,然後開啟一個 Colab 筆記本來編輯。因為 Python 3.10 版本在使用 TFLite Model Maker 套件時,會有已知的問題,所以我將筆記本中的 Python 執行環境設定為 3.9 版本。這裡是我的 Python 3.9 Playground 筆記本(可以作為一個starter樣板)
這個starter樣板的內容如下:
然後建立一個 image_pred.py 文件,並上傳到 Google Drive 雲端硬碟上,以下是這個 Python 腳本的完整內容:
這個腳本利用深度學習(Deep Learning)來訓練並產生圖像分類模型(Image Classification Model)。
這裡使用 Keras 的卷積函數Conv2D(),來實現一個簡單的卷積神經網路(Convolutional Neural Network, CNN),然後在 Intel Image Classification 圖像資料集的基礎上,來訓練和評估此 CNN。
註:Keras 是在TensorFlow 2上建立的高層API;它是一個高度抽象的深度學習框架,簡單易用。
參考文章:
接著在筆記本中,加入以下幾行 Linux 指令,這是用來將這個腳本複製到 Colab 執行階段的目錄下,同時初始化一些目錄的配置:
!cp -v /content/gdrive/MyDrive/tflite-model-maker-workaround/image_pred.py .
!mkdir -p seg_pred && ln -s /content/gdrive/MyDrive/tflite-model-maker-workaround/seg_pred ./seg_pred/seg_pred
!mkdir -p seg_train && ln -s /content/gdrive/MyDrive/tflite-model-maker-workaround/seg_train ./seg_train/seg_train
!mkdir -p seg_test && ln -s /content/gdrive/MyDrive/tflite-model-maker-workaround/seg_test ./seg_test/seg_test
!mkdir -p ./output/train
!mkdir -p ./output/val
腳本裡使用 splitfolders 函數,來將資料集分成訓練集和驗證集,比例為 80% 和 20%。
然後,建立一個名為 datagen 的圖像資料生成器(ImageDataGenerator),並設置多個參數,用於進行圖像資料增強(data augmentation)
每個參數的含義:
- rescale=1.0/255:這個參數是一個圖像預處理操作,它用於對圖像進行正規化(Normalization)。在這裡,rescale = 1.0 / 255 意味著將圖像的像素值縮放到 [0, 1] 的範圍內。具體來說,它將圖像中的每個像素值除以 255.0,將像素值從原來的範圍 [0, 255] 縮放到了 [0, 1]。
- rotation_range=20:表示圖像可以被隨機旋轉的範圍是 -20 度到 +20 度之間。
- width_shift_range=0.2 和 height_shift_range=0.2:這兩個參數控製圖像在水平和垂直方向上的隨機平移範圍,取值範圍是 [0, 1]。在這裡,圖像可以被隨機水平平移和垂直平移的範圍都是圖像寬度(或高度)的 20%。
- shear_range=0.2:這個參數控製圖像的隨機錯切變換的程度。取值範圍是 [0, 1]。在這個範例中,圖像可以被隨機錯切的程度為圖像寬度(或高度)的 20%
- zoom_range=0.2:這個參數控製圖像的隨機縮放範圍。取值範圍是 [0, 1]。在這個範例中,圖像可以被隨機縮放的範圍是 80% 到 120% 之間。
- horizontal_flip=True:這個參數控制是否對圖像進行隨機水平翻轉(左右翻轉)。
- fill_mode='nearest':這個參數定義了填充像素的策略,當圖像發生平移、錯切等變換時,可能會出現一些空白區域。'nearest':表示使用最近鄰插值來填充這些空白區域。
為什麼要進行這樣的正歸化操作呢?正歸化有助於訓練神經網絡,因為它可以使輸入數據的範圍保持一致,避免了某些特徵對訓練的主導影響。通常情況下,將圖像像素值縮放到 [0, 1] 或 [-1, 1] 的範圍內是一個常見的預處理步驟,以有助於神經網絡的收斂和訓練效果。
接下來,建立三個 DataGenerator,它們分别用於訓練集、驗證集和測試集。這些生成器將載入和預處理圖像,並以批量的形式提供給模型進行訓練和評估。
代碼中的 flow_from_directory 是從 ImageDataGenerator 抽取訓練樣本,供模型訓練每個epoch時使用。用於解決資料集過大,無法一次性載入記憶體的困境。
以下代碼用來建立模型:
這裡使用 Keras 建立一個卷積神經網絡(CNN)模型,用於處理影像分類任務。首先從 Keras 函式庫中引入了 Sequential 模型類別以及幾個常用的網絡層,包括 Conv2D(二維卷積層)、MaxPool2D(二維最大池化層)、Flatten(扁平化層)和 Dense(全連接層)。
建立模型:
首先建立一個序列模型,並將其存儲在 model 變數中。模型中的各個網絡層將按照順序添加到這個序列模型中。
這裡添加的第一個卷積層,使用 32 個大小為 3x3 的過濾器,並使用 ReLU 激活函數。
input_shape 設置為 (input_size, input_size, 3),表示輸入的影像尺寸為 input_size x input_size,且具有 3 個通道(RGB 彩色影像)。padding='same' 表示使用補零(zero-padding)使輸入和輸出的尺寸保持一致。接著,添加了一個最大池化層,這將減少特徵圖的尺寸。
接下來,重複添加更多的卷積和池化層。每個卷積層都會增加過濾器的數量,這樣模型可以捕捉更多的特徵。在最後一個池化層後,添加了一個扁平化層,將特徵圖轉換為一維的向量,以便與後面的全連接層進行連接。
最後一層是全連接層,輸出的節點數目為 6,並使用 Softmax 激活函數,這將產生一個 6 維的向量,表示輸入影像屬於 6 個不同類別中的每個類別的概率。
總結而言,這腳本中定義了一個簡單的卷積神經網絡,用於影像分類。模型以卷積和池化層逐漸提取影像特徵,然後使用全連接層進行分類。該模型的輸入影像尺寸為 150x150,輸出為 6 個類別的概率分佈。
model.summary() 可以顯示模型的摘要信息,包括每一層的名稱、輸出形狀、參數數量等。
模型編譯(Compile)
使用 compile 方法對模型進行編譯。這裡使用 Adam 優化器,設定學習率為 0.001。優化器用於調整模型參數以最小化損失函數。然後設定損失函數為 categorical crossentropy,這是在多類別分類任務中常用的損失函數。最後指定要在訓練過程中追蹤的評價指標,這裡選擇了準確度(accuracy)。
模型訓練(Fit)
- 使用 fit 方法開始模型的訓練過程。這個方法需要提供訓練數據生成器 train_generator,以及設定一些訓練相關的參數:
- steps_per_epoch:每個訓練 epoch 中,從訓練生成器中提取的批次數目。這個值通常是 總樣本數 // 批次大小。
- validation_steps:每個驗證 epoch 中,從驗證生成器中提取的批次數目。類似地,這個值是 驗證樣本數 // 批次大小。
- epochs:訓練的 epoch 數,即將整個訓練數據集過多少次模型。
- validation_data:可選的,可以提供一個驗證數據生成器,用於在每個 epoch 結束時評估模型的性能。
這部分代碼評估了訓練好的模型在測試集上的性能。model.evaluate(test_generator) 會使用測試數據生成器 test_generator 來評估模型,並返回一個包含損失值和準確度的列表。然後,這些值被印出,顯示測試集上的損失和準確度。
然後將訓練好的模型保存到指定的路徑 '/content/output' 文件夾中,這時可以在 Colab 側邊欄的 output 文件夾下,看到一個名為 saved_model.pb 的模型文件。
因為我們之後要將這個模型集成到 Android 應用程式上,所以這裡使用 TensorFlow Lite 轉換器,來將 Keras 模型轉換為 TensorFlow Lite 模型(.tflite 文件格式)。
首先,使用 tf.lite.TFLiteConverter.from_keras_model(model) 創建一個轉換器對象,然後將 Keras 模型 model 傳遞給這個轉換器。接著,使用 converter.convert() 方法將模型進行轉換,就能獲得轉換後的 TensorFlow Lite 模型了。
最後,將轉換後的 TensorFlow Lite 模型保存到名為 'model.tflite' 的文件中。然後我們就可以下載這個模型,以便在資源有限的嵌入式設備上運行。
參考資料:
接著只要在 Colab 筆記本上執行代碼,就能產生我們要的模型了。在這個例子中,大約需要數十分鐘的執行時間。我們透過 Keras 的 model.summary() 函數生成的模型架構摘要中,顯示了每一層的名稱、輸出形狀和參數數量,可以方便了解模型的設計和配置。
可以看到 conv2d 層的輸出形狀是 (None, 150, 150, 32),表示輸出是一個 150x150 的特徵圖,具有 32 個通道。同樣地,max_pooling2d 層的輸出形狀是 (None, 75, 75, 32),表示尺寸被減半並且通道數不變。
flatten 層將最後一個池化層的輸出扁平化為一維向量,形狀為 (None, 4096)。最後的 dense 層是全連接層,輸出形狀是 (None, 6),表示最終模型有 6 個輸出節點(6 個類別)。
將 TFLite 模型部署到 Android 應用程式
如何將模型集成到 Android 應用程式上?簡單來說就是需要一個可以開啟相機鏡頭,並從鏡頭看到的畫面進行預測,或者是從行動裝置的相簿裡選擇一張照片來進行預測。
這裡我只需要一個簡單的 Sample APP 來測試模型,所以沒必要花時間自己去寫一個 APP。我在 Tensorflow 教學網站上,找到一個簡單的 Android 範例(也有 iOS 和 Raspberry PI 的範例)。我們可以直接下載這個 Sample Code 來使用!
直接下載這個範例,然後在 Android Studio 開發環境中打開專案,選擇裡面的 finish 模塊,編譯並執行到手機就行了!
我在 build 這個範例時,遇到了兩個問題和錯誤, 其中一個是:Unable to make field private final java.lang.String java.io.File.path accessible: module java.base does not "opens java.io" to unnamed module
解決方式:Fix it by upgrading Grade Android Plugin Version to 7.0.3 in android/build.gradle
另一個問題是:java.lang.IllegalAccessError: class org.jetbrains.kotlin.kapt3.base.KaptContext (in unnamed module @0x74f83670) cannot access class com.sun.tools.javac.util.Context (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.util to unnamed module
解決方式:Upgrade Kotlin Gradle Plugin Version to 1.6.0+
解決了以上問題後,將稍早轉出來的TFLite模型,複製到專案的ml文件夾中,但是 Android Studio 編輯器上卻顯示了 Type mismatch 的錯誤信息,內容如下:
- _TFLITE_MODEL_PATH:轉換為 TensorFlow Lite 格式的模型文件路徑。
- _INPUT_NORM_MEAN 和 _INPUT_NORM_STD:輸入影像的正規化參數,用於處理輸入影像。
- _LABELS_FILE:包含類別標籤的文本文件路徑