ML.NET Series: Menganalisa Anomali Data Pasien Diabetes (Time Series)

Hi rekan-rekan Makers,

Bertemu lagi di seri ML.NET, kali ini kita akan mencoba menggunakan ML.NET untuk mendeteksi anomali pada data. Untuk rekan-rekan yang belum mengikuti seri ini dari awal silakan baca dari artikel ini. Adapun beberapa hal yang sebelumnya dibahas antara lain regression, binary classification, multiclass classification atau clustering.

Mendeteksi anomali data sangat bermanfaat untuk berbagai hal seperti prediksi maintenance, deteksi perubahan cuaca, bahkan hal-hal seputar kesehatan seperti deteksi perubahan gula darah pada pasian diabetes dari waktu ke waktu, hal ini sangat bermanfaat untuk mencegah hal-hal yang tidak diinginkan terjadi. Dataset yang akan kita gunakan informasinya dapat baca disini. Catatan pasien diabetes diperoleh dari dua sumber: alat perekam elektronik otomatis dan catatan manual dengan kertas. Perangkat otomatis memiliki jam internal untuk mencatat waktu, sedangkan catatan kertas hanya menyediakan slot “waktu logikal” (sarapan, makan siang, makan malam, waktu tidur). Untuk catatan kertas, waktu yang ditetapkan ditetapkan untuk sarapan (08:00), makan siang (12:00), makan malam (18:00), dan waktu tidur (22:00). Dengan demikian catatan kertas memiliki waktu perekaman seragam, sedangkan catatan elektronik memiliki waktu yang lebih realistis.

Beberapa attribut dari dataset ini adalah:

  1. Date, dalam format MM-DD-YYYY 
  2. Time, dalam format XX:YY
  3. Code, mengindikasikan jenis data, keterangan dibawah.
  4. Value, pengukuran gula darah

Adapun keterangan mengenai Code adalah sebagai berikut:

33 = Dosis regular insulin 
34 = Dosis NPH insulin  
35 = Dosis UltraLente insulin 
48 = Pengukuran glukosa darah (tidak spesifik)
57 = Pengukuran glukosa darah (tidak spesifik) 
58 = Pengukuran gula darah sebelum sarapan  
59 = Pengukuran gula darah setelah sarapan
60 = Pengukuran gula darah sebelum makan siang
61 = Pengukuran gula darah setelah makan siang
62 = Pengukuran gula darah sebelum makan malam
63 = Pengukuran gula darah setelah makan malam
64 = Pengukuran gula darah sebelum makan snack
65 = Symptom Hypoglycemic  
66 = Konsumsi makan tipikal
67 = Konsumsi makan lebih dari biasanya 
68 = Konsumsi makan yang lebih sedikit dari biasanya
69 = Aktivitas olahraga biasa 
70 = Aktivitas olahraga lebih dari biasanya 
71 = Aktivitas olahraga lebih sedikit dari biasanya 
72 = Event spesial tertentu

selanjutnya, rekan-rekan memastikan sudah menginstall .NET Core / .NET Framework versi terakhir, dalam artikel ini saya menggunakan .NET Core versi 2.2. Jika belum silakan download disini. Jika belum memiliki IDE silakan download visual studio and vs code.

Langkah pertama silakan buat project baru dengan tipe console application. Kalau dengan dengan .Net Core bisa menggunakan CLI, silakan buka terminal atau command line (cmd.exe). Lalu ketik:

mkdir DiabetesAnalysisApp

Lalu masuk ke folder yang baru dibuat dengan mengetik:

cd DiabetesAnalysisApp

Selanjutnya buat aplikasi console dengan mengetik:

dotnet new console

Kita perlu menambahkan nuget package ML.NET dan algoritma Time Series dengan mengetik:

dotnet add package Microsoft.ML
dotnet add package Microsoft.ML.TimeSeries

Kemudian buatlah folder dengan nama “Data” dengan mengetik:

mkdir Data

Lalu download file tsv dari link ini, dan masukan ke folder “Data” tersebut.

Kemudian buka dengan visual studio code folder “DiabetesAnalysisApp”

pada Program.cs masukan namespace ML.NET dengan mengetik pada bagian atas:

using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using Microsoft.ML;

Selanjutnya buatlah class dengan nama “DiabetesRecord.cs” isikan kode berikut:

using System;
using System.Collections.Generic;
using System.Text;

namespace DiabetesAnalysisApp
{
public class DiabetesRecord
{
public DateTime TimeStamp { set; get; }
public string Code { get; set; }
public float Data { get; set; }
}
}

Class diatas merepresentasikan struktur data tsv, lalu buat class dengan nama “DiabetesRecordPrediction.cs”, berikut adalah kodenya

using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.Text;

namespace DiabetesAnalysisApp
{
public class DiabetesRecordPrediction
{
//vector to hold alert,score,p-value values
[VectorType(3)] public double[] Prediction { get; set; }
}
}

Class ini digunakan untuk melakukan deteksi Spike dan Change Point, selanjutnya kembali ke “Program.cs”, buatlah variabel global di dalam class Program:

static MLContext mlContext;

Mlcontext adalah object yang kita gunakan sebagai pipeline untuk melakukan seluruh proses ML. Selanjutnya rekan-rekan bisa memasukan kode berikut untuk memuat data dan melakukan beberapa transformasi data:

//Data prep.
var TrainData = new List<DiabetesRecord>();
var DataFolder = GetAbsolutePath("../../../Data/");
var Files = Directory.GetFiles(DataFolder, "*");
foreach (var filePath in Files)
{
foreach (var line in File.ReadAllLines(filePath))
{
var cols = line.Split('\t');
CultureInfo ci = new CultureInfo("id-ID");
var DateStr = $"{cols[0]} {cols[1]}";//Convert.ToDateTime($"{cols[0]} {cols[1]}", ci );
var len = DateStr.Length;
float dataValue = 0;
float.TryParse(cols[3], out dataValue);
//make sure this line can be processed / contains correct time-series data
if (len >= 15)
{
//parse string of date to datetime
DateTime.TryParse(DateStr, out DateTime dt);
if (dt.Year > DateTime.MinValue.Year)
TrainData.Add(new DiabetesRecord() { TimeStamp = dt, Code = cols[2], Data = dataValue });
}
}
}
HashSet<string> CodeIn = new HashSet<string>();
//only observe data with code 48,57-61
CodeIn.Add("48");CodeIn.Add("57");CodeIn.Add("58");CodeIn.Add("59");CodeIn.Add("60");CodeIn.Add("61");CodeIn.Add("62");CodeIn.Add("63");CodeIn.Add("64");
TrainData = TrainData.Where(x=> CodeIn.Contains(x.Code)).OrderBy(a => a.TimeStamp).ToList();
Console.WriteLine($"Total data : {TrainData.Count}");

Pada kode diatas kita memuat beberapa file tsv, lalu memasukannya pada kelas DiabetesRecord, kita konversi waktu dalam string menjadi datetime agar kita bisa urutkan secara ascending. Algoritma TimeSeries ini hanya membutuhkan input dengan tipe single jadi pastikan data rekan-rekan sudah terurut berdasarkan waktu. Selanjutnya masukan kode berikut:

// Create MLContext
mlContext = new MLContext();

//Load Data
IDataView data = mlContext.Data.LoadFromEnumerable<DiabetesRecord>(TrainData);
//assign the Number of records in dataset file to cosntant variable
var RowCount = data.GetRowCount();
int size = RowCount.HasValue? Convert.ToInt32(RowCount.Value) : 36;
//STEP 1: Create Esimtator
DetectSpike(size, data);
//To detect persistent change in the pattern
DetectChangepoint(10, data); //set 10 datapoints per-sliding window

Kode diatas akan menginisiasi objek mlcontext, lalu kita load data dari collection of object yang sudah kita buat sebelumnya. Lalu kita panggil method DetectSpike, berikut adalah kodenya:

static void DetectSpike(int size,IDataView dataView)
{
Console.WriteLine("===============Detect temporary changes in pattern===============");

//STEP 1: Create Esimtator
var estimator = mlContext.Transforms.DetectIidSpike(outputColumnName: nameof(DiabetesRecordPrediction.Prediction), inputColumnName: nameof(DiabetesRecord.Data),confidence: 95, pvalueHistoryLength: size / 4);

//STEP 2:The Transformed Model.
//In IID Spike detection, we don't need to do training, we just need to do transformation.
//As you are not training the model, there is no need to load IDataView with real data, you just need schema of data.
//So create empty data view and pass to Fit() method.
ITransformer tansformedModel = estimator.Fit(CreateEmptyDataView());

//STEP 3: Use/test model
//Apply data transformation to create predictions.
IDataView transformedData = tansformedModel.Transform(dataView);
var predictions = mlContext.Data.CreateEnumerable<DiabetesRecordPrediction>(transformedData, reuseRowObject: false);

Console.WriteLine("Alert\tScore\tP-Value");
foreach (var p in predictions)
{
if (p.Prediction[0] == 1)
{
Console.BackgroundColor = ConsoleColor.DarkYellow;
Console.ForegroundColor = ConsoleColor.Black;
}
Console.WriteLine("{0}\t{1:0.00}\t{2:0.00}", p.Prediction[0], p.Prediction[1], p.Prediction[2]);
Console.ResetColor();
}

}

Deteksi spike digunakan untuk menganalisa perubahan nilai yang drastis secara temporer (singkat). Contoh case ini adalah cyber attack, indikasi kerusakan mesin, pasien jatuh atau shock. Selanjutnya kita akan pilih algoritma untuk deteksi Spike yaitu DetectIidSpike. Algoritma ini menerima parameter nama kolom input, kolom output, confidence dan pValueHistoryLength. pValueHistoryLength bisa diisi jumlah data dalam satu sliding window, atau sebanyak record yang kita analisa. Untuk analisa ini kita tidak perlu melakukan training, kita cukup membuat object ITransformer dengan collection of object yang kosong dari class DiabetesRecord. Untuk memulai analisa panggil fungsi Transform. Lalu hasil prediksinya dapat dikonversi kembali ke dalam bentuk list of data sebanyak datapoint yang diinput. Setiap item dari hasil prediksi memiliki attribut Prediction berupa data vector. Data vector ini berisi data : Alert (jika nilai 1 maka terjadi spike), Score (nilai gula darah), P-value (probabilitas, semakin rendah semakin mengindikasikan terjadi Spike).  Jika terjadi Spike kita warnai background tulisan menjadi kuning tua.

Oke selanjutnya masukan method DetectChangePoint dengan kode berikut:

static void DetectChangepoint(int size, IDataView dataView)
{
Console.WriteLine("===============Detect Persistent changes in pattern===============");

//STEP 1: Setup transformations using DetectIidChangePoint
var estimator = mlContext.Transforms.DetectIidChangePoint(outputColumnName: nameof(DiabetesRecordPrediction.Prediction), inputColumnName: nameof(DiabetesRecord.Data), confidence: 95, changeHistoryLength: size);

//STEP 2:The Transformed Model.
//In IID Change point detection, we don't need need to do training, we just need to do transformation.
//As you are not training the model, there is no need to load IDataView with real data, you just need schema of data.
//So create empty data view and pass to Fit() method.
ITransformer tansformedModel = estimator.Fit(CreateEmptyDataView());

//STEP 3: Use/test model
//Apply data transformation to create predictions.
IDataView transformedData = tansformedModel.Transform(dataView);
var predictions = mlContext.Data.CreateEnumerable<DiabetesRecordPrediction>(transformedData, reuseRowObject: false);

Console.WriteLine($"{nameof(DiabetesRecordPrediction.Prediction)} column obtained post-transformation.");
Console.WriteLine("Alert\tScore\tP-Value\tMartingale value");

foreach(var p in predictions)
{
if (p.Prediction[0] == 1)
{
Console.WriteLine("{0}\t{1:0.00}\t{2:0.00}\t{3:0.00} <-- alert is on, predicted changepoint", p.Prediction[0], p.Prediction[1], p.Prediction[2], p.Prediction[3]);
}
else
{
Console.WriteLine("{0}\t{1:0.00}\t{2:0.00}\t{3:0.00}", p.Prediction[0], p.Prediction[1], p.Prediction[2], p.Prediction[3]);
}
}

}

Change Point adalah perubahan trend data yang lebih persisten (lebih lama dari spike). Contoh terjadi kerusakan mesin, panas tinggi terus menerus, kenaikan/penurunan gula darah yang cukup lama, bisa diindikasikan sebagai waktu kejadian bencana. Dalam kode diatas penggunaannya mirip dengan deteksi spike, hanya hati-hati dalam menentukan changeHistoryLength. Masukan sejumlah data point dalam 1 sliding window cukup.  Sedangkan output dari change point adalah vektor yang berisi : Alert (jika nilai 1 maka terjadi change point), Score (nilai gula darah), P-value, dan Martingale Value (ini menjadi indikator terjadi change point, ketika nilainya naik, hal ini didasarkan munculnya nilai p-values kecil secara sekuensial). 

Oke, sekarang rekan-rekan sudah bisa mendeteksi Spike dan Change Point pada data time-series. Silakan berlatih dengan data-data sendiri, misal weather station yang secara berkala melakukan log data kondisi lingkungan. Source code lengkapnya bisa diunduh dari sini.

Semoga bermanfaat, silakan ikut kelanjutan artikel selanjutnya dalam seri yang sama.

Salam Makers ;D

Loading

You May Also Like