從零開始學機器學習——構建一個推薦web應用

努力的小雨發表於2024-10-17

首先給大家介紹一個很好用的學習地址:https://cloudstudio.net/columns

今天,我們終於將分類器這一章節學習完活了,和迴歸一樣,最後一章節用來構建web應用程式,我們會回顧之前所學的知識點,並新增一個web應用用來讓模型和使用者互動。所以今天的主題是美食推薦。

美食推薦 Web 應用程式

首先,請不要擔心,本章節並不會涉及過多的前端知識點。我們此次的學習重點在於機器學習本身,因此我們的目標是將模型打包,使得前端使用者能夠與模型進行直接的介面互動,而不再依賴於後端輸入的形式。

在前面的迴歸章節中,我們學習瞭如何使用第三方依賴包 pickle 來建立一個後臺生成的 .pkl 字尾的模型檔案,並透過 Flask 框架載入該模型,從而在後臺暴露介面供呼叫和分析。今天,我們將探索一個新的知識點——ONNX Web,這將進一步拓寬我們在機器學習模型部署與應用方面的視野。

ONNX Web

ONNX Web 是一個用於在瀏覽器中執行 ONNX 模型的工具和庫,主要用於深度學習模型的推理。ONNX(Open Neural Network Exchange)是一個開放的深度學習模型交換格式,它允許不同深度學習框架之間共享模型。ONNX Web 使得開發者可以將 ONNX 模型直接在網頁上執行,通常用於機器學習和深度學習的前端應用。

開發步驟

  • 準備 ONNX 模型:首先,需要一個經過訓練並匯出的 ONNX 模型。
  • 引入 ONNX Web 庫:在你的前端專案中引入 ONNX Web 的 JavaScript 庫。可以透過 npm 安裝或直接在 HTML 中使用 CDN。
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.9.09/dist/ort.min.js"></script> 
  • 載入模型:使用 ONNX Web API 載入模型並進行推理。
const session = await ort.InferenceSession.create('./model.onnx');
  • 進行推理:準備輸入資料,並使用載入的模型進行推理。
  • 處理輸出:根據模型的輸出格式處理結果,並在應用中展示。

好的,經過對開發步驟的全面瞭解後,接下來讓我們開始逐步構建一個功能齊全的 Web 應用程式。

構建模型

首先,我們將使用之前清洗後的菜品資料集來訓練一個分類模型。

import pandas as pd 
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score,precision_score,confusion_matrix,classification_report

data = pd.read_csv('../data/cleaned_cuisines.csv')
X = data.iloc[:,2:]
y = data[['cuisine']]
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.3)
model = SVC(kernel='linear', C=10, probability=True,random_state=0)
model.fit(X_train,y_train.values.ravel())
y_pred = model.predict(X_test)
print(classification_report(y_test,y_pred))

關於上述這段程式碼流程,大家可能已經對其有了一定的瞭解,因此我將不再進行單獨的講解。

執行結果如下所示:模型的精度表現相對令人滿意,達到了一個不錯的水平。

              precision    recall  f1-score   support

     chinese       0.71      0.72      0.72       238
      indian       0.90      0.86      0.88       259
    japanese       0.76      0.75      0.75       248
      korean       0.83      0.78      0.80       233
        thai       0.73      0.82      0.77       221

    accuracy                           0.79      1199
   macro avg       0.79      0.79      0.78      1199
weighted avg       0.79      0.79      0.79      1199

模型轉換到 Onnx

由於使用到了第三方依賴庫,我們需要安裝一下,命令如下:

! pip install skl2onnx

from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

initial_type = [('float_input', FloatTensorType([None, 380]))]
options = {id(model): {'nocl': True, 'zipmap': False}}
onx = convert_sklearn(model, initial_types=initial_type, options=options)
with open("./model.onnx", "wb") as f:
    f.write(onx.SerializeToString())

這段程式碼的整體目的是準備將一個 Scikit-Learn 模型轉換為 ONNX 格式的過程,定義了輸入資料的結構以及轉換時的一些配置選項。

  • initial_type 是一個列表,定義了模型的輸入特徵及其資料型別。
  • 'float_input' 是輸入的名稱,可以是任意字串,用於標識輸入。
  • FloatTensorType([None, 380]) 指定輸入資料的形狀。這裡的 [None, 380] 表示:
    • 第一個維度是 None,表示可以接受任意數量的樣本(即批處理大小)。
    • 第二個維度是 380,表示每個樣本有 380 個特徵。
  • options 是一個字典,用於指定轉換過程中的一些選項。
    • id(model) 獲取模型的唯一識別符號(ID),用作字典的鍵。
    • 選項中:
      • 'nocl': True 表示在轉換時不使用類別標籤的對映(Class Label Mapping)。
      • 'zipmap': False 表示在輸出的 ONNX 模型中不使用 ZipMap 功能,這意味著輸出將是一個多維陣列,而不是一個字典結構。通常在處理分類問題時,ZipMap 可能會將類別標籤轉換為字典形式,但這裡選擇保持原始輸出。

最後一步則是將模型寫入檔案即可。

視覺化模型工具——Netron

Netron 是一個用於視覺化和分析深度學習模型的開源工具,支援多種模型格式,包括 ONNX、TensorFlow、Keras、PyTorch 等。它提供了一個直觀的圖形介面,幫助使用者理解和檢查模型結構、層、引數等資訊。

開源地址:https://github.com/lutzroeder/Netron?tab=readme-ov-file

其中包含了多種系統的安裝版本,方便使用者根據自己的需求進行選擇和安裝。此外,它還提供了線上Web應用,使用者可以直接透過瀏覽器訪問,無需額外安裝任何軟體。

線上Web應用地址如下:https://netron.app/

image

在我們將剛才訓練好的模型上傳後,可以清晰地檢視模型的詳細資訊。

image

同樣,你可以透過點選每一個框框來進一步探索模型的具體資訊。例如,如果我們點選了“SVMClassifier”這一選項,螢幕上將會彈出一個詳細的對話方塊,如下所示。

image

好的,以後如果你想檢視模型的具體資訊和效能表現,當然可以利用這個視覺化工具作為參考。

Web 應用程式

這次,我們將不再使用Python後臺來啟動應用,而是完全依賴一個前端靜態頁面來實現所有功能。

<!DOCTYPE html>
<html>
    <header>
        <title>Cuisine Matcher</title>
    </header>
    <body>
        <h1>Check your refrigerator. What can you create?</h1>
        <div id="wrapper">
            <div class="boxCont">
                <input type="checkbox" value="4" class="checkbox">
                <label>apple</label>
            </div>
        
            <div class="boxCont">
                <input type="checkbox" value="247" class="checkbox">
                <label>pear</label>
            </div>
        
            <div class="boxCont">
                <input type="checkbox" value="77" class="checkbox">
                <label>cherry</label>
            </div>

            <div class="boxCont">
                <input type="checkbox" value="126" class="checkbox">
                <label>fenugreek</label>
            </div>

            <div class="boxCont">
                <input type="checkbox" value="302" class="checkbox">
                <label>sake</label>
            </div>

            <div class="boxCont">
                <input type="checkbox" value="327" class="checkbox">
                <label>soy sauce</label>
            </div>

            <div class="boxCont">
                <input type="checkbox" value="112" class="checkbox">
                <label>cumin</label>
            </div>
        </div>
        <div style="padding-top:10px">
            <button onClick="startInference()">What kind of cuisine can you make?</button>
        </div>      
        <!-- import ONNXRuntime Web from CDN -->
        <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.9.0/dist/ort.min.js"></script>
        <script>
        const ingredients = Array(380).fill(0);
        
        const checks = [...document.querySelectorAll('.checkbox')];
        
        checks.forEach(check => {
            check.addEventListener('change', function() {
                // toggle the state of the ingredient
                // based on the checkbox's value (1 or 0)
                ingredients[check.value] = check.checked ? 1 : 0;
            });
        });

        function testCheckboxes() {
            // validate if at least one checkbox is checked
            return checks.some(check => check.checked);
        }

        async function startInference() {

            let atLeastOneChecked = testCheckboxes()

            if (!atLeastOneChecked) {
                alert('Please select at least one ingredient.');
                return;
            }
            try {
                // create a new session and load the model.
                
                const session = await ort.InferenceSession.create('./model.onnx');

                const input = new ort.Tensor(new Float32Array(ingredients), [1, 380]);
                const feeds = { float_input: input };

                // feed inputs and run
                const results = await session.run(feeds);

                // read from results
                alert('You can enjoy ' + results.label.data[0] + ' cuisine today!')

            } catch (e) {
                console.log(`failed to inference ONNX model`);
                console.error(e);
            }
        }
               
    </script>
    </body>
</html>

大致解釋下js部分的程式碼:

  • 建立一個長度為 380 的陣列 ingredients,初始值為 0,用於表示選擇的食材狀態。
  • 獲取所有的核取方塊,併為每個核取方塊新增 change 事件監聽器。當核取方塊狀態變化時,根據核取方塊的 value 更新 ingredients 陣列,選中為 1,未選中為 0。
  • testCheckboxes 函式用於檢查是否至少選中一個核取方塊。
  • startInference 函式首先檢查是否選中至少一個食材。如果沒有,則彈出提示框。
    • 如果有選中食材,非同步載入 ONNX 模型 (model.onnx)。
    • 建立一個張量 input,形狀為 [1, 380],用以儲存食材資訊。
    • 呼叫模型的 run 方法進行推理,並獲取結果。
    • 結果中的 label 資料用於顯示推薦的菜餚。

我們來執行一下,這裡用到了http-server依賴:

npm install --global http-server

執行後,如圖所示:

image

至此,我們已經完成了整個Web應用程式的構建過程。

總結

在這次學習旅程中,我們成功構建了一個美食推薦的Web應用程式,探索了機器學習和Web開發的交集。透過使用ONNX Web,我們能夠將訓練好的模型直接整合到瀏覽器中,讓使用者可以與模型進行互動,而無需依賴後端,這極大地提高了使用者體驗。結合Netron這一強大的視覺化工具,我們不僅能夠分析和理解模型的內部結構,還可以直觀地展示模型的效能與特點。

至此,我們的分類章節圓滿結束,感謝大家的認真學習和參與。在下一次的課程中,我們將深入探討聚類技術,瞭解其在資料分析和機器學習中的重要應用。


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章