Учебник. Создание приложения для рекомендации фильмов с использованием матричной факторизации и ML.NET

В этом руководстве показано, как построить приложение для рекомендации фильмов с помощью ML.NET в консольном проекте .NET Core. Используется C# и Visual Studio 2019.

В этом руководстве вы узнаете, как:

  • выбрать алгоритм машинного обучения;
  • подготовить и загрузить данные;
  • создать и обучить модель;
  • оценить модель;
  • развернуть и использовать модель.

Исходный код для этого руководства можно найти в репозитории dotnet/samples.

Рабочий процесс машинного обучения

Решение поставленной задачи, как и любой задачи в ML.NET, состоит из следующих этапов:

  1. Загрузка данных
  2. Создание и обучение модели
  3. Оценка модели
  4. Использование модели

Предварительные требования

Выбор подходящей задачи машинного обучения

Есть несколько подходов к проблеме предоставления рекомендаций, например в отношении фильмов или связанных продуктов. В данном случае мы будем прогнозировать оценку (от 1 до 5), которую пользователь поставил бы определенному фильму, и рекомендуем этот фильм, если оценка выше установленного порога (чем выше оценка, тем больше вероятность того, что фильм понравится пользователю).

Создание консольного приложения

Создание проекта

  1. Создайте консольное приложение C# с именем MovieRecommender. Нажмите кнопку Далее.

  2. Выберите .NET 6 в качестве используемой платформы. Нажмите кнопку Создать .

  3. Создайте каталог с именем Data в папке проекта, чтобы сохранить в нем набор данных:

    В обозревателе решений щелкните проект правой кнопкой мыши и выберите Добавить>Новая папка. Введите имя папки Data и нажмите клавишу "ВВОД".

  4. Установите пакеты NuGet Microsoft.ML и Microsoft.ML.Recommender:

    Примечание

    В этом примере используется последняя стабильная версия пакетов NuGet, упомянутых выше, если не указано иное.

    В обозревателе решений щелкните проект правой кнопкой мыши и выберите Управление пакетами NuGet. Выберите в качестве источника пакета "nuget.org", откройте вкладку Обзор, найдите Microsoft.ML, выберите пакет в списке и нажмите кнопку Установить. Нажмите кнопку ОК в диалоговом окне Предварительный просмотр изменений, а затем нажмите кнопку Принимаю в диалоговом окне Принятие условий лицензионного соглашения, если вы согласны с указанными условиями лицензионного соглашения для выбранных пакетов. Повторите эти действия для пакета Microsoft.ML.Recommender.

  5. Добавьте следующие операторы using в начало файла Program.cs:

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

Скачивание данных

  1. Скачайте два набора данных и сохраните их в ранее созданную папку Data.

    • Щелкните файл recommendation-ratings-train.csv правой кнопкой мыши и выберите команду "Сохранить ссылку (объект) как...".

    • Щелкните файл recommendation-ratings-test.csv правой кнопкой мыши и выберите команду "Сохранить ссылку (объект) как...".

      Убедитесь, что файлы *.csv сохранены в папке Данные или после сохранения в другом месте переместите файлы *.csv в папку Данные .

  2. В Обозреватель решений щелкните правой кнопкой мыши каждый из файлов *.csv и выберите Свойства. В разделе Дополнительно для параметра Копировать в выходной каталог установите значение Копировать более позднюю версию.

    GIF пользователя, который выбирает в VS пункт

Загрузка данных

Первый шаг в процессе работы с ML.NET — подготовка и загрузка данных для обучения и тестирования модели.

Данные для оценки рекомендаций разделяются на наборы Train и Test. Данные Train служат для обучения модели. Данные Test используются для прогнозирования на основе обученной модели и оценки ее эффективности. Данные Train и Test обычно делятся в отношении 80/20.

Ниже приведен предварительный просмотр данных из файлов *.csv:

Снимок экрана с окном предварительного просмотра набора данных CVS.

В файлах *.csv есть четыре столбца:

  • userId
  • movieId
  • rating
  • timestamp

В машинном обучении столбцы, используемые для прогнозирования, называются признаками, а столбец с итоговым прогнозом — меткой.

Нам нужно прогнозировать рейтинг фильмов, поэтому меткой (Label) является столбец rating. Остальные столбцы (userId, movieId и timestamp) — это признаки 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;

Создайте класс MovieRating, удалив из файла MovieRatingData.cs существующее определение класса и добавив следующий код:

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

MovieRating — это класс входных данных. Атрибут LoadColumn определяет столбцы в наборе данных, которые следует загрузить (по индексам). Столбцы userId и movieId — это признаки Features (входные данные модели для прогнозирования метки Label), а столбец rating — это прогнозируемая метка Label (выходные данные модели).

Создайте еще один класс, MovieRatingPrediction, который будет представлять результаты прогнозирования. Для этого добавьте следующий код после класса MovieRating в файле MovieRatingData.cs:

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, которая может использоваться всеми объектами в рамках процесса создания модели. По существу он аналогичен классу DBContext в Entity Framework.

В конце файла создайте метод с именем LoadData():

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

}

Примечание

Этот метод будет вызывать ошибку, пока вы не добавите оператор return при выполнении дальнейших инструкций.

Инициализируйте переменные, содержащие пути к данным, загрузите данные из файлов CSV и получите наборы данных Train и Test как объекты IDataView, добавив следующий код в метод LoadData():

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 позволяет гибко и полно описывать табличные данные (числовые и текстовые). Данные можно загружать в объект IDataView из текстового файла или в режиме реального времени (например, из базы данных SQL или файлов журнала).

Метод LoadFromTextFile() определяет схему данных и считывает файл. Он принимает переменные, содержащие пути к данным, и возвращает объект IDataView. В данном случае вы указываете пути к файлам Test и Train, а также заголовок текстового файла (чтобы использовались правильные имена столбцов) и разделитель данных в виде запятой (по умолчанию разделителем является символ табуляции).

Добавьте следующий код для вызова метода LoadData() и получения наборов данных Train и Test:

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

Создание и обучение модели

Создайте метод BuildAndTrainModel() сразу после метода LoadData(), вставив в него следующий код:

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"));

Так как столбцы userId и movieId содержат индексы пользователей и фильмов, а не реальные значения, необходимо с помощью метода 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) использует метод, называемый "совместная фильтрация". Он основывается на том допущении, что если мнение двух людей по какому-либо вопросу совпадает, то и по другому вопросу они будут склонны иметь одинаковое мнение.

Например, если два пользователя одинаково оценили определенный фильм, то второму из них с большой вероятностью понравится другой фильм, который посмотрел и высоко оценил первый пользователь.

Incredibles 2 (2018) The Avengers (2012) Guardians of the Galaxy (2014)
Пользователь 1 Посмотрел фильм и поставил отметку "Нравится" Посмотрел фильм и поставил отметку "Нравится" Посмотрел фильм и поставил отметку "Нравится"
Пользователь 2 Посмотрел фильм и поставил отметку "Нравится" Посмотрел фильм и поставил отметку "Нравится" Не смотрел фильм — РЕКОМЕНДОВАТЬ

Алгоритм разложения матрицы (Matrix Factorization) имеет ряд параметров, о которых можно прочитать в разделе Гиперпараметры алгоритма ниже.

Обучите модель на основе данных Train и получите обученную модель, добавив в метод BuildAndTrainModel() следующие строки кода:

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);

Оценка модели

После обучения модели оцените ее эффективность с помощью тестовых данных.

Создайте метод EvaluateModel() сразу после метода BuildAndTrainModel(), вставив в него следующий код:

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

}

Преобразуйте данные Test, добавив в метод EvaluateModel() следующий код:

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.

Построение успешных моделей — итеративный процесс. Изначально эта модель имеет низкое качество, так как в руководстве используются небольшие наборы данных для быстрого обучения. Если вам требуется модель более высокого качества, можно попытаться улучшить ее, использовав более крупные наборы данных для обучения или выбрав другие алгоритмы обучения с разными гиперпараметрами для каждого алгоритма. См. подробнее об улучшении модели в разделе ниже.

Использование модели

Теперь обученную модель можно использовать для прогнозирования на основе новых данных.

Создайте метод UseModelForSinglePrediction() сразу после метода EvaluateModel(), вставив в него следующий код:

void UseModelForSinglePrediction(MLContext mlContext, ITransformer model)
{

}

Для прогнозирования оценки используйте класс PredictionEngine. Добавьте в метод UseModelForSinglePrediction() следующий код:

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

Класс PredictionEngine представляет собой удобный API, позволяющий осуществить прогнозирование на основе единственного экземпляра данных. PredictionEngine не является потокобезопасным. Допустимо использовать в средах прототипов или средах с одним потоком. Для улучшенной производительности и потокобезопасности в рабочей среде используйте службу PredictionEnginePool, которая создает ObjectPool объектов PredictionEngine для использования во всем приложении. См. руководство по использованию PredictionEnginePool в веб-API ASP.NET Core.

Примечание

Расширение службы PredictionEnginePool сейчас доступно в предварительной версии.

Создайте экземпляр MovieRating с именем testInput и передайте его в PredictionEngine, добавив следующие строки кода в метод 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

Сохранение модели

Чтобы модель можно было использовать для прогнозирования в приложениях для конечных пользователей, ее нужно сохранить.

Создайте метод SaveModel() сразу после метода UseModelForSinglePrediction(), вставив в него следующий код:

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 id, movie id и rating).

Хотя для начала этого достаточно, на практике может потребоваться добавить дополнительные атрибуты или признаки Features (например, возраст, пол, географическое местоположение и другие). Добавление релевантных признаков (Features) может помочь повысить эффективность модели рекомендаций.

Если вы не уверены, какие признаки (Features) являются наиболее релевантными для конкретной задачи машинного обучения, можно использовать специальные методы, предоставляемые платформой ML.NET с целью выявления наиболее весомых признаков (Features): определение вклада признака и важность комбинаций признаков.

Гиперпараметры алгоритма

Алгоритмы, предоставляемые платформой ML.NET по умолчанию, достаточно эффективны, однако их можно еще более улучшить, настроив их гиперпараметры.

В случае с разложением матрицы (Matrix Factorization) можно поэкспериментировать с такими гиперпараметрами, как NumberOfIterations и ApproximationRank.

Например, в этом учебнике алгоритм имеет следующие параметры:

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

Другие алгоритмы рекомендаций

Алгоритм разложения матрицы с совместной фильтрацией — лишь один из подходов к рекомендации фильмов. Зачастую данные по оценкам могут отсутствовать, то есть доступны только данные по просмотренным пользователями фильмам. В других случаях могут быть доступны дополнительные данные.

Алгоритм Сценарий Пример
Разложение матрицы одного класса Следует использовать при наличии только столбцов userId и movieId. Такой тип рекомендаций предназначен для сценария совместных покупок, то есть покупателям будет рекомендоваться набор продуктов исходя из их истории заказов. >Попробуйте
Факторизационные машины с полями Следует использовать для рекомендаций при наличии дополнительных признаков, помимо userId, productId и rating (таких как описание или цена продукта). Этот алгоритм также использует метод совместной фильтрации. >Попробуйте

Проблема нового пользователя

Одна из распространенных проблем при использовании совместной фильтрации — это проблема "холодного запуска", когда для нового пользователя еще нет данных, на основе которых можно было бы делать выводы. Для ее решения новому пользователю часто предлагается создать профиль и, например, оценить фильмы, которые он уже видел. Хотя такой подход требует некоторых усилий от пользователя, он позволяет получить начальные данные, на которые можно было бы опираться.

Ресурсы

В этом учебнике используются данные из набора данных MovieLens.

Следующие шаги

В этом руководстве вы узнали, как:

  • выбрать алгоритм машинного обучения;
  • подготовить и загрузить данные;
  • создать и обучить модель;
  • оценить модель;
  • развернуть и использовать модель.

Переходите к следующему руководству: