Учебник. Обнаружение аномалий в данных о продажах товаров с помощью ML.NET

Узнайте, как создать приложение для обнаружения аномалий в данных о продажах товаров. В этом руководстве в Visual Studio с помощью C# создается консольное приложение .NET Core.

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

  • Загрузка данных
  • Создание преобразования для обнаружения пиковых аномалий
  • Обнаружение пиковых аномалий с помощью преобразования
  • Создание преобразования для обнаружения аномалий в точке изменений
  • Обнаружение аномалий в точке изменений с помощью преобразования

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

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

Примечание

Формат данных в product-sales.csv основан на наборе данных Shampoo Sales Over a Three Year Period, изначально опубликованном DataMarket и предоставленном Time Series Data Library (TSDL), за авторством Роба Нундмана (Rob Hyndman). Набор данных Shampoo Sales Over a Three Year Period предоставляется по стандартной открытой лицензии DataMarket.

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

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

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

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

  4. Установите пакет NuGet для Microsoft.ML:

    Примечание

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

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

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

    using Microsoft.ML;
    using ProductSalesAnomalyDetection;
    

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

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

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

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

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

В следующей таблице приведен предварительный просмотр данных из файла *.csv:

Месяц ProductSales
1-Jan 271
2-Jan 150,9
..... .....
1-Feb 199,3
..... .....

Создание классов и определение путей

Далее определите структуру данных для класса ввода данных и прогнозирования.

Добавьте в проект новый класс:

  1. В Обозреватель решений щелкните проект правой кнопкой мыши и выберите Добавить > новый элемент.

  2. В диалоговом окне Добавление нового элемента выберите Класс и измените значение поля Имя на ProductSalesData.cs. Теперь нажмите кнопку Добавить.

    Файл ProductSalesData.cs откроется в редакторе кода.

  3. Добавьте следующую инструкцию using в начало файла ProductSalesData.cs:

    using Microsoft.ML.Data;
    
  4. Удалите из файла ProductSalesData.cs существующее определение класса и добавьте следующий код с двумя классами ProductSalesData и ProductSalesPrediction:

    public class ProductSalesData
    {
        [LoadColumn(0)]
        public string? Month;
    
        [LoadColumn(1)]
        public float numSales;
    }
    
    public class ProductSalesPrediction
    {
        //vector to hold alert,score,p-value values
        [VectorType(3)]
        public double[]? Prediction { get; set; }
    }
    

    ProductSalesData — это класс входных данных. Атрибут LoadColumn определяет столбцы в наборе данных, которые следует загрузить (по индексам).

    ProductSalesPrediction указывает класс данных прогноза. Для обнаружения аномалий прогноз состоит из предупреждения, указывающего на наличие аномалии, необработанной оценки и p-значения. Чем ближе p-значение к 0, тем больше вероятность возникновения аномалий.

  5. Создайте два глобальных поля для хранения пути к файлу недавно скачанного набора данных и пути к файлу сохраненной модели:

    • _dataPath содержит путь к набору данных, используемому для обучения модели;
    • _docsize содержит количество записей в файле набора данных. Вы будете использовать _docSize для вычисления pvalueHistoryLength.
  6. Добавьте следующий код в строку прямо под операторами using, чтобы определить эти пути:

    string _dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "product-sales.csv");
    //assign the Number of records in dataset file to constant variable
    const int _docsize = 36;
    

Инициализация переменных

  1. Замените строку Console.WriteLine("Hello World!") следующим кодом, чтобы объявить и инициализировать переменную mlContext:

    MLContext mlContext = new MLContext();
    

    Класс MLContext является отправной точкой для любых операций ML.NET. В результате инициализации класса mlContext создается среда ML.NET, которая может использоваться всеми объектами в рамках процесса создания модели. По существу он аналогичен классу DBContext в Entity Framework.

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

Данные на ML.NET представлены интерфейсом IDataView. IDataView позволяет гибко и полно описывать табличные данные (числовые и текстовые). Данные можно загружать в объект IDataView из текстового файла или других источников (например, из базы данных SQL или файлов журнала).

  1. Добавьте следующий код после создания переменной mlContext:

    IDataView dataView = mlContext.Data.LoadFromTextFile<ProductSalesData>(path: _dataPath, hasHeader: true, separatorChar: ',');
    

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

Обнаружение аномалий во временных рядах

Функция обнаружения аномалий регистрирует неожиданные или необычные события или особенности поведения. Кроме того, она подсказывает, где нужно искать проблему, и помогает ответить на вопрос "Является ли это странным?".

Пример обнаружения аномалий с помощью ответа на вопрос

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

Обнаружение аномалий может быть полезным во многих ситуациях. Например:

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

Существуют два вида аномалий временных рядов, которые можно обнаружить.

  • Пиковые значения указывают временные всплески аномального поведения в системе.

  • Точки изменений указывают начало устойчивых изменений с течением времени в системе.

В ML.NET алгоритмы обнаружения пиковых значений IID или обнаружения точек изменений IID подходят для независимых и одинаково распределенных наборов данных. Предполагается, что входные данные представляют собой последовательность точек данных, которые вычисляются независимо из одного стационарного распределения.

В отличие от моделей в других руководствах, преобразования обнаружения аномалий временных рядов работают непосредственно с входными данными. Методу IEstimator.Fit() не требуются обучающие данные для создания преобразования. Для него нужна схема данных, которая предоставляется представлением данных, созданным из пустого списка ProductSalesData.

Вы проанализируете одни и те же данные о продажах продукта для обнаружения пиковых значений и точек изменений. Процесс создания и обучения модели одинаков для обоих видов обнаружения; основное различие заключается в используемом алгоритме обнаружения.

Обнаружение пиковых значений

Целью обнаружения пиковых значений является выявление внезапных, но при этом временных всплесков, которые значительно отличаются от большинства значений данных временных рядов. Важно своевременно обнаружить эти подозрительные редкие элементы, события или наблюдения, чтобы свести их влияние к минимуму. Для обнаружения разнообразных аномалий, таких как простои, кибератаки и вирусное веб-содержимое, можно использовать описанный ниже подход. На следующем рисунке представлен пример пиковых значений в наборе данных временных рядов.

Снимок экрана с данными обнаружения двух пиков.

Добавьте метод CreateEmptyDataView()

Добавьте следующий метод к Program.cs:

IDataView CreateEmptyDataView(MLContext mlContext) {
    // Create empty DataView. We just need the schema to call Fit() for the time series transforms
    IEnumerable<ProductSalesData> enumerableData = new List<ProductSalesData>();
    return mlContext.Data.LoadFromEnumerable(enumerableData);
}

CreateEmptyDataView() создает пустой объект представления данных с правильной схемой для использования в качестве входных данных для метода IEstimator.Fit().

Создание метода DetectSpike()

Метод DetectSpike():

  • Создает преобразование из средства оценки.
  • Обнаруживает пиковые значения на основе исторических данных о продажах.
  • Отображает результаты.
  1. Создайте метод DetectSpike() в конце файла Program.cs, используя следующий код:

    DetectSpike(MLContext mlContext, int docSize, IDataView productSales)
    {
    
    }
    
  2. Используйте IidSpikeEstimator, чтобы обучить модель для обнаружения пиковых значений. Добавьте его в метод DetectSpike() со следующим кодом:

    var iidSpikeEstimator = mlContext.Transforms.DetectIidSpike(outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.numSales), confidence: 95d, pvalueHistoryLength: docSize / 4);
    
  3. Создайте преобразование для обнаружения пиковых значений, добавив в метод DetectSpike() следующую строку кода:

    Совет

    Параметры confidence и pvalueHistoryLength влияют на то, как обнаруживаются пики. confidence определяет, насколько важна модель для пиков. Чем ниже уровень достоверности, тем выше вероятность того, что алгоритм выявляет "небольшие" пики. Параметр pvalueHistoryLength определяет количество точек данных в скользящем окне. Значение этого параметра обычно представляет собой процентную долю от всего набора данных. Чем ниже значение pvalueHistoryLength, тем быстрее модель забывает о предыдущих больших пиках.

    ITransformer iidSpikeTransform = iidSpikeEstimator.Fit(CreateEmptyDataView(mlContext));
    
  4. Добавьте указанный ниже код для преобразования данных productSales в следующую строку метода DetectSpike():

    IDataView transformedData = iidSpikeTransform.Transform(productSales);
    

    Предыдущий код использует метод Transform(), чтобы сделать прогнозы для нескольких входных строк набора данных.

  5. Преобразуйте transformedData в строго типизированный IEnumerable для более удобного отображения с помощью метода CreateEnumerable(), используя следующий код:

    var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData, reuseRowObject: false);
    
  6. Создайте отображаемую строку заголовка с помощью следующего кода Console.WriteLine():

    Console.WriteLine("Alert\tScore\tP-Value");
    

    В результатах обнаружения пиковых значений будут отображены следующие сведения:

    • Alert указывает на оповещение о пиковом значении для заданной точки данных.
    • Score является значением ProductSales для заданной точки данных в наборе данных.
    • P-Value — "P" означает вероятность. Чем ближе p-значение к 0, тем больше вероятность того, что точка данных аномальна.
  7. Используйте следующий код, чтобы выполнить итерацию по predictionsIEnumerable и отобразить результаты:

    foreach (var p in predictions)
    {
        if (p.Prediction is not null)
        {
            var results = $"{p.Prediction[0]}\t{p.Prediction[1]:f2}\t{p.Prediction[2]:F2}";
    
            if (p.Prediction[0] == 1)
            {
                results += " <-- Spike detected";
            }
    
            Console.WriteLine(results);
        }
    }
    Console.WriteLine("");
    
  8. Добавьте вызов метода DetectSpike() под вызовом метода LoadFromTextFile():

    DetectSpike(mlContext, _docsize, dataView);
    

Результаты обнаружения пиковых значений

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

Detect temporary changes in pattern
=============== Training the model ===============
=============== End of training process ===============
Alert   Score   P-Value
0       271.00  0.50
0       150.90  0.00
0       188.10  0.41
0       124.30  0.13
0       185.30  0.47
0       173.50  0.47
0       236.80  0.19
0       229.50  0.27
0       197.80  0.48
0       127.90  0.13
1       341.50  0.00 <-- Spike detected
0       190.90  0.48
0       199.30  0.48
0       154.50  0.24
0       215.10  0.42
0       278.30  0.19
0       196.40  0.43
0       292.00  0.17
0       231.00  0.45
0       308.60  0.18
0       294.90  0.19
1       426.60  0.00 <-- Spike detected
0       269.50  0.47
0       347.30  0.21
0       344.70  0.27
0       445.40  0.06
0       320.90  0.49
0       444.30  0.12
0       406.30  0.29
0       442.40  0.21
1       580.50  0.00 <-- Spike detected
0       412.60  0.45
1       687.00  0.01 <-- Spike detected
0       480.30  0.40
0       586.30  0.20
0       651.90  0.14

Обнаружение точек изменений

Change points — это постоянные изменения в распределении значений по потоку событий временных рядов, такие как изменения уровня и тенденции. Эти постоянные изменения длятся гораздо дольше, чем spikes, и могут указывать на катастрофические события. Change points обычно не видны невооруженным глазом, но могут быть обнаружены в данных с помощью таких подходов, которые описаны в следующем методе. На следующем рисунке представлен пример обнаружения точек изменений.

Снимок экрана с данными обнаружения точки изменения.

Создание метода DetectChangepoint()

Метод DetectChangepoint() выполняет следующие задачи:

  • Создает преобразование из средства оценки.
  • Обнаруживает точки изменений на основе исторических данных о продажах.
  • Отображает результаты.
  1. Создайте метод DetectChangepoint() сразу после объявления метода DetectSpike(), используя следующий код:

    void DetectChangepoint(MLContext mlContext, int docSize, IDataView productSales)
    {
    
    }
    
  2. Создайте iidChangePointEstimator в методе DetectChangepoint(), используя следующий код:

    var iidChangePointEstimator = mlContext.Transforms.DetectIidChangePoint(outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.numSales), confidence: 95d, changeHistoryLength: docSize / 4);
    
  3. Как и ранее, создайте преобразование из средства оценки, добавив следующую строку кода в метод DetectChangePoint():

    Совет

    Обнаружение точек изменений происходит с небольшой задержкой, поскольку модель должна гарантировать, что текущее отклонение является постоянным изменением, а не только некоторыми случайными пиками перед созданием предупреждения. Значение этой задержки равно значению параметра changeHistoryLength. Увеличив значение этого параметра, можно изменить оповещения обнаружения для более постоянных изменений, но компромисс приведет к более длительной задержке.

    var iidChangePointTransform = iidChangePointEstimator.Fit(CreateEmptyDataView(mlContext));
    
  4. Используйте метод Transform() для преобразования данных, добавив в DetectChangePoint() следующий код:

    IDataView transformedData = iidChangePointTransform.Transform(productSales);
    
  5. Как и ранее, преобразуйте transformedData в строго типизированный IEnumerable для более удобного отображения с помощью метода CreateEnumerable(), используя следующий код:

    var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData, reuseRowObject: false);
    
  6. Создайте отображаемый заголовок, добавив в следующую строку метода DetectChangePoint() приведенный ниже код.

    Console.WriteLine("Alert\tScore\tP-Value\tMartingale value");
    

    В результатах обнаружения точек изменений будут отображены следующие сведения:

    • Alert указывает на оповещение о точке изменений для заданной точки данных.
    • Score является значением ProductSales для заданной точки данных в наборе данных.
    • P-Value — "P" означает вероятность. Чем ближе p-значение к 0, тем больше вероятность того, что точка данных аномальна.
    • Martingale value — используется для определения того, насколько "странной" является точка данных, на основе последовательности P-значений.
  7. Используйте следующий код, чтобы выполнить итерацию по predictionsIEnumerable и отобразить результаты:

    foreach (var p in predictions)
    {
        if (p.Prediction is not null)
        {
            var results = $"{p.Prediction[0]}\t{p.Prediction[1]:f2}\t{p.Prediction[2]:F2}\t{p.Prediction[3]:F2}";
    
            if (p.Prediction[0] == 1)
            {
                results += " <-- alert is on, predicted changepoint";
            }
            Console.WriteLine(results);
        }
    }
    Console.WriteLine("");
    
  8. Добавьте следующий вызов метода DetectChangepoint() под вызовом метода DetectSpike():

    DetectChangepoint(mlContext, _docsize, dataView);
    

Результаты обнаружения точек изменений

Результаты выполнения должны выглядеть примерно так, как показано ниже. Во время обработки отображаются сообщения. Вы можете видеть предупреждения или обработанные сообщения. Для удобства некоторые сообщения удалены из следующих результатов.

Detect Persistent changes in pattern
=============== Training the model Using Change Point Detection Algorithm===============
=============== End of training process ===============
Alert   Score   P-Value Martingale value
0       271.00  0.50    0.00
0       150.90  0.00    2.33
0       188.10  0.41    2.80
0       124.30  0.13    9.16
0       185.30  0.47    9.77
0       173.50  0.47    10.41
0       236.80  0.19    24.46
0       229.50  0.27    42.38
1       197.80  0.48    44.23 <-- alert is on, predicted changepoint
0       127.90  0.13    145.25
0       341.50  0.00    0.01
0       190.90  0.48    0.01
0       199.30  0.48    0.00
0       154.50  0.24    0.00
0       215.10  0.42    0.00
0       278.30  0.19    0.00
0       196.40  0.43    0.00
0       292.00  0.17    0.01
0       231.00  0.45    0.00
0       308.60  0.18    0.00
0       294.90  0.19    0.00
0       426.60  0.00    0.00
0       269.50  0.47    0.00
0       347.30  0.21    0.00
0       344.70  0.27    0.00
0       445.40  0.06    0.02
0       320.90  0.49    0.01
0       444.30  0.12    0.02
0       406.30  0.29    0.01
0       442.40  0.21    0.01
0       580.50  0.00    0.01
0       412.60  0.45    0.01
0       687.00  0.01    0.12
0       480.30  0.40    0.08
0       586.30  0.20    0.03
0       651.90  0.14    0.09

Поздравляем! Вы успешно создали модели машинного обучения для обнаружения аномалий в виде пиковых значений и точек изменений в данных о продажах.

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

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

  • Загрузка данных
  • Обучение модели для обнаружения пиковых аномалий
  • Обнаружение пиковых аномалий с помощью обученной модели
  • Обучение модели для обнаружения аномалий в точке изменений
  • Обнаружение аномалий в точке изменений с помощью обученной модели

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

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