教學課程:使用矩陣分解搭配 ML.NET 建置電影推薦工具

本教學課程會示範如何在 .NET Core 主控台應用程式中使用 ML.NET 建置電影推薦工具。 這些步驟會使用 C# 和 Visual Studio 2019。

在本教學課程中,您會了解如何:

  • 選取機器學習演算法
  • 準備及載入您的資料
  • 建置及定型模型
  • 評估模型
  • 部署及取用模型

您可以在 dotnet/samples 存放庫中找到本教學課程的原始程式碼。

機器學習工作流程

您將使用下列步驟來完成您的工作以及任何其他 ML.NET 工作:

  1. 載入您的資料
  2. 建置及定型您的模型
  3. 評估您的模型
  4. 使用您的模型

必要條件

選取適當的機器學習工作

有數種方式可以解決推薦問題,例如推薦電影清單或推薦相關產品清單;但在此情況下,您將預測使用者會給予該電影的評等 (1-5),如果特定電影的預測評等高於所定義閾值,即推薦該電影 (評等愈高,使用者喜歡特定電影的可能性愈高)。

建立主控台應用程式

建立專案

  1. 建立名為 "MovieRecommender" 的 C# 主控台應用程式。 按 [下一步] 按鈕。

  2. 選擇 .NET 6 作為要使用的架構。 按一下 [ 建立 ] 按鈕。

  3. 在您的專案中建立一個名為 Data 的目錄以儲存資料集:

    在 [方案總管] 中,以滑鼠右鍵按一下專案,然後選取 [新增]>[新增資料夾]。 輸入 "Data",然後按 Enter。

  4. 安裝 Microsoft.MLMicrosoft.ML.Recommender NuGet 套件:

    注意

    除非另有說明,否則此樣本會使用所提及 NuGet 套件的最新穩定版本。

    在 [方案總管] 中,以滑鼠右鍵按一下專案,然後選取 [管理 NuGet 套件]。 選擇 "nuget.org" 作為 [套件來源]、選取 [瀏覽] 索引標籤、搜尋 Microsoft.ML、從清單中選取該套件,然後選取 [安裝] 按鈕。 在 [預覽變更] 對話方塊上,選取 [確定] 按鈕,然後在 [授權接受] 對話方塊上,如果您同意所列套件的授權條款,請選取 [我接受]。 為 Microsoft.ML.Recommender 重複這些步驟。

  5. 在您的 Program.cs 檔案最上方新增下列 using 陳述式:

    using Microsoft.ML;
    using Microsoft.ML.Trainers;
    using MovieRecommendation;
    

下載您的資料

  1. 下載兩個資料集,並儲存至您先前建立的 Data 資料夾:

    • 以滑鼠右鍵按一下 recommendation-ratings-train.csv,然後選取 [另存連結 (或目標)...]

    • 以滑鼠右鍵按一下 recommendation-ratings-test.csv,然後選取 [另存連結 (或目標)...]

      請務必將 *.csv 檔案儲存至 Data 資料夾,或儲存在其他位置之後將 *.csv 檔案移至 Data 資料夾。

  2. 在 [方案總管] 中,以滑鼠右鍵按一下每個 *.csv 檔案,然後選取 [屬性]。 在 [進階] 底下,將 [複製到輸出目錄] 的值變更為 [有更新時才複製]。

    VS 中有較新版本時使用者選取複製的 GIF。

載入您的資料

ML.NET 程序的第一個步驟是準備並載入模型定型和測試資料。

推薦評等資料會分成 TrainTest 資料集。 Train 資料用來調整您的模型。 Test 資料用來以您的已定型模型進行預測並評估模型效能。 TrainTest 資料通常會分割為 80/20 比例。

以下是您 *.csv 檔案的資料預覽:

CVS 資料集預覽的螢幕擷取畫面。

在 *.csv 檔案中有四個資料行:

  • userId
  • movieId
  • rating
  • timestamp

在機器學習服務中,用來進行預測的資料行稱為特徵,而傳回預測的資料行稱為標籤

您希望預測電影評等,因此評等資料行是 Label。 其他三個資料行 userIdmovieIdtimestamp 都是 Features,用來預測 Label

功能 標籤
userId rating
movieId
timestamp

由您決定使用哪些 Features 來預測 Label。 您也可以使用類似排列特徵重要性的方法,來協助您選取最合適的 Features

在此情況下,您應該排除 timestamp 資料行為 Feature,因為時間戳記並不會實際影響使用者對特定影片的評分方式,因此無法提供更精確的預測:

功能 標籤
userId rating
movieId

接下來,您必須定義輸入類別的資料結構。

將新類別新增至專案:

  1. 在 [方案總管] 中,以滑鼠右鍵按一下專案,然後選取 [新增] > [新項目]。

  2. 在 [新增項目] 對話方塊中,選取 [類別],然後將 [名稱] 欄位變更為 MovieRatingData.cs。 接著,選取 [新增] 按鈕。

MovieRatingData.cs 檔案隨即在程式碼編輯器中開啟。 將下列 using 陳述式新增至 MovieRatingData.cs 的最上方:

using Microsoft.ML.Data;

移除現有類別定義,並在 MovieRatingData.cs 中新增下列程式碼,來建立稱為 MovieRating 的類別:

public class MovieRating
{
    [LoadColumn(0)]
    public float userId;
    [LoadColumn(1)]
    public float movieId;
    [LoadColumn(2)]
    public float Label;
}

MovieRating 會指定輸入資料類別。 LoadColumn 屬性會指定應該載入資料集內的哪些資料行 (依資料行索引)。 userIdmovieId 資料行是您的 Features (您將給予模型來預測 Label 的輸入),而評等資料行是您將預測的 Label (模型的輸出)。

建立另一個類別 MovieRatingPrediction,藉由在 MovieRatingData.cs 中的 MovieRating 類別之後新增下列程式碼,以代表預測的結果:

public class MovieRatingPrediction
{
    public float Label;
    public float Score;
}

Program.cs 中,以下列程式碼取代 Console.WriteLine("Hello World!")

MLContext mlContext = new MLContext();

MLContext 類別是所有 ML.NET 作業的起點,且初始化 mlContext 會建立新的 ML.NET 環境,其可在模型建立工作流程物件之間共用。 就概念而言,類似於 Entity Framework 中的 DBContext

在檔案底部,建立名為 LoadData() 的方法:

(IDataView training, IDataView test) LoadData(MLContext mlContext)
{

}

注意

此方法將給出錯誤,直到您在下列步驟中新增 return 陳述式。

初始化您的資料路徑變數、從 *.csv 檔案載入資料,並將下列程式碼新增為 LoadData() 中的下一行程式碼來傳回 TrainTest 資料作為 IDataView 物件:

var trainingDataPath = Path.Combine(Environment.CurrentDirectory, "Data", "recommendation-ratings-train.csv");
var testDataPath = Path.Combine(Environment.CurrentDirectory, "Data", "recommendation-ratings-test.csv");

IDataView trainingDataView = mlContext.Data.LoadFromTextFile<MovieRating>(trainingDataPath, hasHeader: true, separatorChar: ',');
IDataView testDataView = mlContext.Data.LoadFromTextFile<MovieRating>(testDataPath, hasHeader: true, separatorChar: ',');

return (trainingDataView, testDataView);

ML.NET 中的資料會以 IDataView 介面表示。 IDataView 是彈性且有效率的表格式資料描述方式 (數值和文字)。 資料可以從文字或即時 (例如 SQL 資料庫或記錄檔) 載入至 IDataView 物件。

LoadFromTextFile() 會定義資料結構描述並讀入檔案中。 會接受資料路徑變數然後傳回 IDataView。 在此情況下,您提供 TestTrain 檔案的路徑,並指示文字檔案標頭 (以便其正確使用資料行名稱) 和逗號字元資料分隔符號 (預設的分隔符號是索引標籤)。

新增下列程式碼以呼叫 LoadData() 方法並傳回 TrainTest 資料:

(IDataView trainingDataView, IDataView testDataView) = LoadData(mlContext);

建置及定型您的模型

請使用下列程式碼,在緊接著 LoadData() 方法之後,建立 BuildAndTrainModel() 方法:

ITransformer BuildAndTrainModel(MLContext mlContext, IDataView trainingDataView)
{

}

注意

此方法將給出錯誤,直到您在下列步驟中新增 return 陳述式。

將下列程式碼新增至 BuildAndTrainModel() 以定義資料轉換:

IEstimator<ITransformer> estimator = mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "userIdEncoded", inputColumnName: "userId")
    .Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "movieIdEncoded", inputColumnName: "movieId"));

由於 userIdmovieId 代表使用者與電影標題,而非真正的值,所以您會使用 MapValueToKey() 方法來將每個 userId 和每個 movieId 轉換成數值索引鍵類型 Feature 資料行 (推薦演算法所接受的格式),並將其新增為新的資料集資料行:

userId movieId 標籤 userIdEncoded movieIdEncoded
1 1 4 userKey1 movieKey1
1 3 4 userKey1 movieKey2
1 6 4 userKey1 movieKey3

將下列程式碼新增為 BuildAndTrainModel() 中的下一行程式碼,以選擇機器學習演算法,並將其附加至資料轉換定義中:

var options = new MatrixFactorizationTrainer.Options
{
    MatrixColumnIndexColumnName = "userIdEncoded",
    MatrixRowIndexColumnName = "movieIdEncoded",
    LabelColumnName = "Label",
    NumberOfIterations = 20,
    ApproximationRank = 100
};

var trainerEstimator = estimator.Append(mlContext.Recommendation().Trainers.MatrixFactorization(options));

MatrixFactorizationTrainer 是您的推薦定型演算法。 當您擁有使用者過去如何評等產品的資料時,矩陣分解是推薦的常見方法,此亦為本教學課程資料集的情況。 當您有不同的可用資料時,也有其他推薦演算法 (請參閱其他推薦演算法一節以深入了解)。

在此案例中,Matrix Factorization 演算法使用的方法稱為「共同篩選」,此方法假設如果使用者 1 與使用者 2 對特定問題具有相同的意見,則使用者 1 對其他問題的想法較可能與使用者 2 相同。

比方說,如果使用者 1 對電影的評分與使用者 2 類似,則使用者 2 較可能享受使用者 1 已觀看並給予高度評分的電影:

Incredibles 2 (2018) The Avengers (2012) Guardians of the Galaxy (2014)
使用者 1 已觀看及已按讚的電影 已觀看及已按讚的電影 已觀看及已按讚的電影
使用者 2 已觀看及已按讚的電影 已觀看及已按讚的電影 尚未觀看 -- 推薦電影

Matrix Factorization 定型器具有數個選項,您可以在以下演算法超參數一節中深入了解。

將下列內容新增為 BuildAndTrainModel() 方法中的下一行程式碼,調整模型為合適於 Train 資料並傳回已定型模型:

Console.WriteLine("=============== Training the model ===============");
ITransformer model = trainerEstimator.Fit(trainingDataView);

return model;

Fit() 方法會以所提供的定型資料集來定型模型。 技術上來說,其會轉換資料並套用定型來執行 Estimator 定義,並傳回已定型模型,也就是 Transformer

如需 ML.NET 中模型定型工作流程的詳細資訊,請參閱 什麼是 ML.NET 及其運作方式?

將下列內容新增為對 LoadData() 方法呼叫下方的下一行程式碼,以呼叫 BuildAndTrainModel() 方法並傳回定型模型:

ITransformer model = BuildAndTrainModel(mlContext, trainingDataView);

評估您的模型

一旦您將模型定型後,即可將測試資料用於評估模型的執行情況。

請使用下列程式碼,在緊接著 BuildAndTrainModel() 方法之後,建立 EvaluateModel() 方法:

void EvaluateModel(MLContext mlContext, IDataView testDataView, ITransformer model)
{

}

將下列程式碼新增至 EvaluateModel() 以轉換 Test 資料:

Console.WriteLine("=============== Evaluating the model ===============");
var prediction = model.Transform(testDataView);

Transform() 方法會對測試資料集之多個提供的輸入資料列進行預測。

將下列內容新增為 EvaluateModel() 方法中的下一行程式碼來評估模型:

var metrics = mlContext.Regression.Evaluate(prediction, labelColumnName: "Label", scoreColumnName: "Score");

在您設定好預測後,Evaluate() 方法會評估模型,將預測值與測試資料集中的實際 Labels 進行比較,並傳回模型的執行情況。

將下列內容新增為 EvaluateModel() 方法中的下一行程式碼,將您的評估計量列印到主控台:

Console.WriteLine("Root Mean Squared Error : " + metrics.RootMeanSquaredError.ToString());
Console.WriteLine("RSquared: " + metrics.RSquared.ToString());

將下列內容新增為對 BuildAndTrainModel() 方法呼叫下方的下一行程式碼,以呼叫 EvaluateModel() 方法:

EvaluateModel(mlContext, testDataView, model);

到目前為止,輸出看起來應類似下列文字:

=============== Training the model ===============
iter      tr_rmse          obj
   0       1.5403   3.1262e+05
   1       0.9221   1.6030e+05
   2       0.8687   1.5046e+05
   3       0.8416   1.4584e+05
   4       0.8142   1.4209e+05
   5       0.7849   1.3907e+05
   6       0.7544   1.3594e+05
   7       0.7266   1.3361e+05
   8       0.6987   1.3110e+05
   9       0.6751   1.2948e+05
  10       0.6530   1.2766e+05
  11       0.6350   1.2644e+05
  12       0.6197   1.2541e+05
  13       0.6067   1.2470e+05
  14       0.5953   1.2382e+05
  15       0.5871   1.2342e+05
  16       0.5781   1.2279e+05
  17       0.5713   1.2240e+05
  18       0.5660   1.2230e+05
  19       0.5592   1.2179e+05
=============== Evaluating the model ===============
Rms: 0.994051469730769
RSquared: 0.412556298844873

在此輸出中,有 20 個反覆項目。 在每個反覆項目中,錯誤的量值會減少並逐漸接近 0。

root of mean squared error (RMS 或 RMSE) 被用來測量模型預測值與測試資料集觀察值之間的差異。 技術上來說,其為誤差平方之平均值的平方根。 此計量值越低,模型就越好。

R Squared 表示資料符合模型的程度。 範圍為 0 到 1。 值為 0 時,表示資料是隨機的,也就是與模型不相符。 值為 1 時,表示模型與資料完全相符。 R Squared 分數愈接近 1 愈好。

建立成功的模型是一個需要反覆嘗試的程序。 此模型一開始的品質較低,因為此教學課程是使用小型的資料集來提供快速的模型定型。 如果您對於模型的品質感到不滿意,可以嘗試為它提供較大的定型資料集,或選擇不同的定型演算法,並針對每個演算法搭配不同的超參數來改善它。 如需詳細資訊,請參閱下面的改善您的模型一節。

使用您的模型

現在您可以使用您的已定型模型對新資料進行預測。

請使用下列程式碼,在緊接著 EvaluateModel() 方法之後,建立 UseModelForSinglePrediction() 方法:

void UseModelForSinglePrediction(MLContext mlContext, ITransformer model)
{

}

將下列程式碼新增至 UseModelForSinglePrediction(),使用 PredictionEngine 來預測評等:

Console.WriteLine("=============== Making a prediction ===============");
var predictionEngine = mlContext.Model.CreatePredictionEngine<MovieRating, MovieRatingPrediction>(model);

PredictionEngine 是一種便利的 API,可讓您在單一資料執行個體上接著執行預測。 PredictionEngine 不是安全執行緒。 可接受在單一執行緒或原型環境中使用。 為了提升效能和執行緒安全性,請使用 PredictionEnginePool 服務,以建立 PredictionEngine 物件的 ObjectPool 供整個應用程式使用。 請參閱本指南,以瞭解如何在ASP.NET Core Web API 中使用 PredictionEnginePool

注意

PredictionEnginePool 服務延伸模組目前處於預覽狀態。

建立稱為 testInputMovieRating 執行個體,並將下列內容新增為 UseModelForSinglePrediction() 方法中的後續程式碼來將其傳遞至預測引擎:

var testInput = new MovieRating { userId = 6, movieId = 10 };

var movieRatingPrediction = predictionEngine.Predict(testInput);

Predict() 函式會在資料的單一資料行進行預測。

您可以接著使用 Score 或預測的評等,來判斷您是否想要將電影 movieId 10 推薦給使用者 6。 Score 愈高,使用者喜好特定電影的可能性愈高。 在此案例中,假設您推薦預測評等 > 3.5 的電影。

若要列印結果,請將下列內容新增為 UseModelForSinglePrediction() 方法中的後續程式碼:

if (Math.Round(movieRatingPrediction.Score, 1) > 3.5)
{
    Console.WriteLine("Movie " + testInput.movieId + " is recommended for user " + testInput.userId);
}
else
{
    Console.WriteLine("Movie " + testInput.movieId + " is not recommended for user " + testInput.userId);
}

將下列內容新增為對 EvaluateModel() 方法呼叫後面的下一行程式碼,以呼叫 UseModelForSinglePrediction() 方法:

UseModelForSinglePrediction(mlContext, model);

此方法的輸出看起來應類似下列文字:

=============== Making a prediction ===============
Movie 10 is recommended for user 6

儲存模型

若要使用您的模型在終端使用者應用程式中進行預測,您必須先儲存模型。

請使用下列程式碼,在緊接著 UseModelForSinglePrediction() 方法之後,建立 SaveModel() 方法:

void SaveModel(MLContext mlContext, DataViewSchema trainingDataViewSchema, ITransformer model)
{

}

SaveModel() 方法中新增下列程式碼來儲存您的已定型模型:

var modelPath = Path.Combine(Environment.CurrentDirectory, "Data", "MovieRecommenderModel.zip");

Console.WriteLine("=============== Saving the model to a file ===============");
mlContext.Model.Save(model, trainingDataViewSchema, modelPath);

此方法會將已定型模型儲存至 .zip 檔案 (在 "Data" 資料夾中),其之後可用於其他 .NET 應用程式中來進行預測。

將下列內容新增為對 UseModelForSinglePrediction() 方法呼叫後面的下一行程式碼,以呼叫 SaveModel() 方法:

SaveModel(mlContext, trainingDataView.Schema, model);

使用您已儲存的模型

儲存定型模型之後,您就可以在不同的環境中取用模型。 請參閱儲存和載入定型模型,以了解如何在應用程式中運作定型機器學習模型。

結果

完成上述步驟後,請執行主控台應用程式 (Ctrl + F5)。 上述單一預測的結果應該如下所示。 您可能會看到警告或處理中訊息,但為了讓結果變得清楚,這些訊息已從下列結果中移除。

=============== Training the model ===============
iter      tr_rmse          obj
   0       1.5382   3.1213e+05
   1       0.9223   1.6051e+05
   2       0.8691   1.5050e+05
   3       0.8413   1.4576e+05
   4       0.8145   1.4208e+05
   5       0.7848   1.3895e+05
   6       0.7552   1.3613e+05
   7       0.7259   1.3357e+05
   8       0.6987   1.3121e+05
   9       0.6747   1.2949e+05
  10       0.6533   1.2766e+05
  11       0.6353   1.2636e+05
  12       0.6209   1.2561e+05
  13       0.6072   1.2462e+05
  14       0.5965   1.2394e+05
  15       0.5868   1.2352e+05
  16       0.5782   1.2279e+05
  17       0.5713   1.2227e+05
  18       0.5637   1.2190e+05
  19       0.5604   1.2178e+05
=============== Evaluating the model ===============
Rms: 0.977175077487166
RSquared: 0.43233349213192
=============== Making a prediction ===============
Movie 10 is recommended for user 6
=============== Saving the model to a file ===============

恭喜! 您現在已成功建置推薦電影的機器學習服務模型。 您可以在 dotnet/samples 存放庫中找到本教學課程的原始程式碼。

改善模型

有幾種方式可讓您改善模型的效能,以便您進行更精確的預測。

資料

為每位使用者和影片識別碼新增更多具有足夠樣本的已定型資料,有助於改善推薦模型的品質。

交叉驗證是一種評估模型技術,會將資料隨機分割成子集 (而不是像本教學課程從資料集擷取出測試資料),並採用部分群組作為定型資料,以及部分群組作為測試資料。 此方法在模型品質方面的表現優於定型/測試分割。

功能

在本教學課程中,您只會使用資料集所提供的三個 Features (user idmovie idrating)。

雖然這是不錯的起點,但在實際操作時,建議您新增其他屬性或 Features (例如年齡、性別、地理位置等),如果這些也包含在資料集內。 新增更多相關 Features 有助於改善推薦模型的效能。

如果您不確定哪 Features 一個可能最與機器學習工作有關,您也可以使用「特徵貢獻計算」 ( () 和 排列特徵重要性,ML.NET 提供來探索最具影響力 Features 的 。

演算法超參數

雖然 ML.NET 提供最佳的預設定型演算法,但您也可以變更演算法的超參數來進一步微調效能。

針對 Matrix Factorization,您可以使用 NumberOfIterationsApproximationRank 等超參數,查看其是否可提供您更好的結果。

例如,本教學課程中的演算法選項如下:

var options = new MatrixFactorizationTrainer.Options
{
    MatrixColumnIndexColumnName = "userIdEncoded",
    MatrixRowIndexColumnName = "movieIdEncoded",
    LabelColumnName = "Label",
    NumberOfIterations = 20,
    ApproximationRank = 100
};

其他建議演算法

具備共同篩選的矩陣分解演算法,僅為執行電影推薦的其中一種方法。 在許多情況下,您可能會沒有可用的評等資料,並只有使用者的電影觀看記錄。 而在其他情況下,您擁有的資料可能不只是使用者評等資料。

演算法 狀況 範例
單一類別矩陣分解 當您只需要 userId 和 movieId 時,請使用此選項。 此推薦類型乃根據共同採購案例或經常同時購買的產品,也就是會根據客戶自己的採購訂單記錄向客戶推薦一組產品。 >試試看
欄位感知分解機器 當您所擁有的功能多於 userId、productId 和評等 (如產品描述或產品價格) 時,請使用此選項來進行推薦。 此方法也會使用共同作業篩選方法。 >試試看

新使用者案例

共同篩選的一個常見問題是冷啟動問題,也就是當您有一個不具有先前資料的新使用者,以至於無法進行推斷時。 此問題的解決方法通常是要求新使用者建立設定檔,並對他們先前看過的電影評分 (舉例來說)。 雖然此方法會對使用者造成一些負擔,但可以為沒有評等記錄的新使用者提供一些起始資料。

資源

本教學課程中使用的資料衍生自 MovieLens 資料集

後續步驟

在本教學課程中,您已了解如何:

  • 選取機器學習演算法
  • 準備及載入您的資料
  • 建置及定型模型
  • 評估模型
  • 部署及取用模型

前進到下一個教學課程來深入了解