Jan 2025【日本東北】溫泉三昧之旅④ 在大雪飄落的雪國,追尋秘湯之宿與極上美食(福島~探訪豬苗代湖、磐梯山、會津若松城&千年古湯 蘆之牧溫泉)

圖片
今早6點半起床,準備7點去使用昨天事先預約好的貸切露天風呂,這是「御宿萬葉亭」的名物,每一組住宿客人可以免費使用40分鐘。庭園裡的半露天的風呂,有兩個浴缸,一次只能容納最多三個人同時使用,左邊圓形的浴缸水溫很高、右邊橢圓形的浴缸水溫較低。雖然旅館裡面也有露天浴池,但是早晨享受一下這個庭園裡的露天風呂,也是一種特別的享受。 細密的雪花輕輕飄落,落在庭園的樹木和灌木叢上,覆蓋上一層柔軟的白色。原本翠綠的植物,此刻都披上了銀裝,枝條低垂,彷彿在向雪國的冬天致敬。 遠處的樹林,也都被白雪覆蓋,形成一片靜謐的雪白世界。偶爾,一陣微風吹過,樹枝輕輕搖晃,雪花便如柳絮般飄落,在空中舞動。我泡在熱氣氤氳的溫泉浴池中,早晨的寒意逐漸消散,而欣賞著這如詩如畫的雪景,身心都得到了徹底的放鬆。 聽聞中之澤溫泉的溫泉源自百年前的一場驚天動地的災難,根據我在旅館裡看到的白板資料,那場災難就發生在西元1888年,也就是明治21年的7月15日。某天的夏日清晨,本應是寧靜祥和的,卻被一連串不祥的預兆打破。從幾天前開始,大地就隱隱作祟,到了7點左右,地鳴聲愈發劇烈。早上7點45分,一陣劇烈的搖晃襲來,還沒等眾人回過神,便聽到一聲震耳欲聾的轟鳴,只見磐梯山的方向升起了一根巨大的柱狀物! 這根由岩石、土壤和水蒸氣組成的巨柱,扶搖直上,直衝雲霄,那景象,簡直就像是電影裡的末日場景!據說當時的景象如同「日食」發生一般,天地瞬間陷入一片黑暗。緊接著,夾雜著火山灰和被岩漿加熱的水蒸氣的「熱雨」傾盆而下,這場「熱雨」引發了大規模的土石流,不僅摧毀了沿途的村莊,更將河流截斷,形成了三個新的堰塞湖。而這三個湖泊,就是如今大名鼎鼎的檜原湖、小野川湖和秋元湖。 白板上那張手繪的「會津磐梯山爆發」示意圖,生動地還原了當年的場景。磐梯山被炸掉了一大塊,山腳下的村莊被夷為平地,原本流淌的河流被截斷,形成了三個巨大的湖泊。這幅景象,讓人不禁感嘆大自然的威力,也對當年受災的民眾感到深深的同情。 然而,這場災難卻也孕育出了新的生命——溫泉。白板上寫著:「中之澤溫泉,從江戶時代開始就是湯治場」。據說,這裡的源泉來自安達太良山的火山口,直線距離約7公里。沼之平地區在過去還是人們採集硫磺的地方,這裡出產的硫磺純度高達99%,品質極佳。 這裡的溫泉,屬於酸性泉,自古以來就以其強大的療效而聞名。泉水源自地下水與天水、加上活躍的火山活動交互影響的...

【筆記】使用 TensorFlow Keras 訓練圖像分類模型,透過 Streamlit 將模型部署為網路應用 ,並集成到 Android 應用程式上

這裡記錄一下這幾天學習利用 Tensorflow Keras 和現有的圖像資料集,訓練一個簡單的圖像分類(Image Classification)模型,並透過 StreamlitGitHub,將模型部署到網路服務上來測試,接著將模型轉換為適用於行動裝置的 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;它是一個高度抽象的深度學習框架,簡單易用。

參考文章:

卷积层Convolutional - Keras 中文文档

深度学习入门,Keras Conv2D参数详解

接著在筆記本中,加入以下幾行 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 結束時評估模型的性能。

以下代碼用來進行模型評估和保存模型,這裡執行了幾個步驟,包括評估模型性能、保存模型以及將模型轉換為 TensorFlow Lite 格式。:

這部分代碼評估了訓練好的模型在測試集上的性能。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' 的文件中。然後我們就可以下載這個模型,以便在資源有限的嵌入式設備上運行。

參考資料:

保存和加载 Keras 模型

转换 TensorFlow 模型

接著只要在 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 範例(也有 iOSRaspberry 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 的錯誤信息,內容如下:


看來是在調用 model.process() 時,需要傳入 TensorBuffer 型別的物件,而我稍早轉換出來的模型,卻是 TensorImage 型別。查了資料,後來在 Stack Overflow 上面找到這篇文章有相關的討論,看起來是少了元數據(metadata) 這個東西。

現在新建一個叫做 converter.py 的腳本,並上傳到 Google Drive 上面,來解決這個問題。

完整的 converter.py 腳本內容如下:

腳本中使用 TensorFlow Lite 相關的元數據寫入工具來為模型添加元數據(metadata),這些元數據可以在部署模型時提供有關模型的附加信息,例如輸入預處理、輸出後處理、類別標籤等。


首先我們引入 TensorFlow 2.x 版本,確保使用的 TensorFlow 版本符合要求。然後,引入了與元數據寫入相關的工具,包括 image_classifierwriter_utils

將類別標籤列表寫入到名為 'labels.txt' 的文本文件中,每一行包含一個類別標籤。然後,使用 image_classifier.MetadataWriter 類來生成元數據。在這個例子中,元數據包含了有關影像分類模型的信息。需要提供以下參數:

  • _TFLITE_MODEL_PATH:轉換為 TensorFlow Lite 格式的模型文件路徑。
  • _INPUT_NORM_MEAN_INPUT_NORM_STD:輸入影像的正規化參數,用於處理輸入影像。
  • _LABELS_FILE:包含類別標籤的文本文件路徑
接下來印出生成的元數據的 JSON 表示,用於驗證元數據是否生成正確。最後,使用 metadata_generator.populate() 方法將生成的元數據集成到 TensorFlow Lite 模型中,並將包含元數據的模型保存到名為 "converted_model_with_metadata.tflite" 的文件。

現在我們可以在 Colab 筆記本中,將 converter.py 腳本從雲端硬碟目錄,複製到執行階段的文件夾下,然後執行這個腳本。執行完成後,就可以在側邊欄看到一個名為 "converted_model_with_metadata.tflite" 的模型文件了。

下載這個文件,將它命名為你要的名字,並複製到剛才 Android 專案的 ml 文件夾下,Rebuild project 後,可以發現稍早的 Type mismatch 錯誤信息不見了。

在 Android 手機上測試模型

現在可以成功編譯專案並安裝到手機上進行測試,以下幾張截圖是採用這個圖像分類模型的 Android 應用程式執行結果之畫面。不出所料,預測表現一般般,呵呵。

註:這個範例中,是直接將模型打包在 Android 應用程式裡。如果要將模型部署到雲端(如 Firebase ML),讓客戶端可以持續更新最新的模型,可以參考上一篇文章裡面的說明。





利用 Streamlit 將模型部署為 Web 應用程式

TFLite 模型擁有體積小、推理速度快之特性,也使其有利於部署在雲端。 現在我們可以來創建一個簡單的場景圖像分類 Web 應用程序。我們可以在網頁中上傳圖像,然後利用我們訓練出來的模型,推斷該圖像是屬於何種場景。

這裡我們使用 StreamlitGitHub 來部署這個 Web 應用程式。Streamlit 是一個特別設計給機器學習與資料科學使用的開源框架,不需要任何網頁的知識,只需要用 Python 語法,就能輕鬆開發和部署 Dashboard 和 Web 應用程式。


首先建立一個 streamlit_app.py 腳本,完整的代碼內容如下:

在這個腳本中,我們導入了 Streamlit、TensorFlow、os、numpy 和 pathlib 等函式庫,用於創建應用程式、處理圖像和操作文件路徑。

class_names = ["buildings", "forest", "glacier", "mountain", "sea", "street"] 這個列表包含了模型預測的類別標籤,用於顯示預測結果。

然後使用 TensorFlow Lite 解譯器載入了預先訓練的 TensorFlow Lite 模型。解譯器被配置並分配了資源。

set_input_tensor 和 get_predictions 這兩個函數用於設置輸入張量和獲取預測結果。前者用於將輸入影像設置為解譯器的輸入,後者用於獲取預測的類別。

我們允許用戶上傳圖像文件,並且會顯示上傳後的圖像。上傳的圖像會被保存到 'Models' 文件夾中(這裡我為求方便,直接使用和模型一樣的文件夾,你可以自定義這個文件夾名稱,例如 tempDir,但記得稍後要在 GitHub 專案中建立 tempDir 這個文件夾)。

當用戶點擊 "Get Predictions" 按鈕後,它會呼叫 get_predictions 函數,進行圖像分類並顯示預測結果。預測結果將以文字顯示在 Web 應用程式上。

現在新建一個 GitHub 專案,然後將 streamlit.py 腳本和一個裡面包含 TFLite 模型文件的 Models 文件夾,上傳到這個 GibHub 專案上。

專案目錄的結構會像這樣:


然後前往 Streamlit 註冊一個帳戶,並連結至你的 GitHub Profile,然後就可以在 Deploy 頁面中,選擇稍早建立的這個 Repository。最底下的 App URL 是這個 Web 應用程式的網址,你可以自定義網址前端的名稱。最後點選 Deploy! 按鈕即可完成應用程式的部署。


部署完成後,我們就可以透過 App URL 連結,打開這個簡單的圖像分類 Web 應用程式了。點選 Browse files 上傳一張照片,然後點 Get Predictions 按鈕,就能進行預測了!


以下是幾張測試截圖,結果看起來還行!




使用 TensorFlow Lite Model Maker 進行圖像分類

其實我們可以直接使用 TFLite Model Maker 來進行圖像分類模型的訓練,這篇教學裡面有如何使用 Model Maker 庫來調整和轉換在移動設備上對花卉資料集進行分類並產生圖像分類模型的說明。

只要將 Intel Image Classification 圖像資料集取代這篇教學所使用的花卉資料集,就能訓練出相同功能的 TFLite 模型了,且代碼更少、使用方式更簡單。

下面是使用 TFLite Model Maker 來訓練模型的 Python 腳本 image_classify2.py


將這個腳本上傳到 Google Drive,並且在 「TFLite-Image-Classification-Python-3.9.ipynb」 筆記本上執行,就能產生可直接使用 TFLite 模型了。


本文完

熱門文章

【秋季清邁遊 Part 5|Visit Mae Ya Waterfall and Wachirathan Waterfall】The 6 Day Itinerary To Explore Chiang Mai And Northern Thailand's Mountains

台中【馨苑小料理】西區店|巷弄裡的人氣台菜店|米其林必比登推薦美食|提供合菜、個人套餐

2019.10.9~13【令和元年の紅葉の山旅へ PART①】秋の贅沢、黒部川の源流へ北アルプス深部を縦走5日間!Day0、Day1(前泊、折立~藥師岳山莊)

Feb, 2024【台中西區】桃太郎日本料理|隱身巷弄裡的39年老字號無菜單料理|食材新鮮、自然美味

Jan 2025【日本東北】溫泉三昧之旅① 在大雪飄落的雪國,追尋秘湯之宿與極上美食(秋田~探訪有日本溫泉界頂點之稱的秘湯乳頭溫泉鶴之湯)

Jan 2025【苗栗泰安】泰安警光山莊泡湯&彰化CP值極高的日本料理|沐藏料理所X海龍王|彰化板前料理 ♨️🍁🥢🍲

Mar 2025【新竹五峰】油羅山森呼吸:擁抱原始柳杉林秘境之美(羅山林道第一登山口往返)

Jan 2025【日本東北】溫泉三昧之旅② 在大雪飄落的雪國,追尋秘湯之宿與極上美食(仙台~品嚐極上米澤牛&奧羽的百年藥湯-鎌先溫泉)

April, 2023【彰化芬園】步道平坦好走!彰化八卦山「挑水古道」+十八觀音步道、碧山古道O形環走

Jan 2025【日本東北】溫泉三昧之旅④ 在大雪飄落的雪國,追尋秘湯之宿與極上美食(福島~探訪豬苗代湖、磐梯山、會津若松城&千年古湯 蘆之牧溫泉)

文章列表

Contact

名稱

以電子郵件傳送 *

訊息 *