Trong Machine Learning cổ điển, khi muốn kiểm tra độ ổn định của mô hình, chúng ta thường dùng K-Fold Cross-Validation. Chúng ta xáo trộn dữ liệu, chia làm K phần và huấn luyện. Tuy nhiên, nếu bạn áp dụng cách này cho dữ liệu chuỗi thời gian (Time Series) như dự báo thời tiết, lượng điện tiêu thụ hay lưu lượng server, bạn đang mắc một sai lầm nghiêm trọng: Data Leakage (Rò rỉ dữ liệu).
Lý do đơn giản: Bạn không thể dùng dữ liệu của "ngày mai" để dạy cho mô hình dự đoán "hôm nay".
Hôm nay, chúng ta sẽ tìm hiểu về Walk-Forward Validation (WFV) và Walk-Forward Optimization (WFO) – tiêu chuẩn vàng để kiểm thử các mô hình phụ thuộc thời gian, đi kèm ví dụ thực tế sử dụng .NET 10 và ML.NET.
1. Walk-Forward Validation (WFV) là gì?
Hãy tưởng tượng bạn đang đứng trên dòng thời gian. Bạn chỉ biết quá khứ và muốn dự đoán tương lai gần. WFV mô phỏng chính xác quá trình này.
Thay vì xáo trộn dữ liệu ngẫu nhiên, WFV sử dụng một "cửa sổ trượt" (sliding window) hoặc "cửa sổ mở rộng" (expanding window):
- Train: Huấn luyện trên một khoảng thời gian trong quá khứ (Ví dụ: Tháng 1 -> Tháng 3).
- Test: Kiểm thử trên khoảng thời gian ngay sau đó (Ví dụ: Tuần đầu tháng 4).
- Slide: Trượt cả cửa sổ Train và Test về phía trước (Ví dụ: Train từ Tháng 1 -> Tuần đầu T4, Test tuần thứ 2 T4).
Lợi ích cốt lõi:
- Không nhìn trộm tương lai (No Look-ahead Bias): Đảm bảo mô hình chỉ học từ dữ liệu đã xảy ra.
- Thích nghi với sự thay đổi (Adaptability): Kiểm tra xem mô hình có còn đúng khi dữ liệu thay đổi theo mùa hoặc xu hướng mới hay không.
Sliding Window
Sliding Window (cửa sổ trượt) là một kỹ thuật thường được sử dụng để xử lý các dãy liên tiếp trong mảng hoặc chuỗi một cách hiệu quả, bằng cách duy trì một "cửa sổ" chứa một phần tử liên tục của dữ liệu và di chuyển nó qua toàn bộ dãy.Có hai dạng phổ biến của thuật toán Sliding Window:
- Cửa Sổ Có Kích Thước Cố Định (Fixed-Size)
- Cửa Sổ Có Kích Thước Linh Hoạt (Variable-Size)
using System;
class GFG {
static int maxSum(int[] arr, int n, int k){
// n must be greater
if (n <= k) {
Console.WriteLine("Invalid");
return -1;
}
// Compute sum of first window of size k
int max_sum = 0;
for (int i = 0; i < k; i++)
max_sum += arr[i];
// Compute sums of remaining windows by
// removing first element of previous
// window and adding last element of
// current window.
int window_sum = max_sum;
for (int i = k; i < n; i++) {
window_sum += arr[i] - arr[i - k];
max_sum = Math.Max(max_sum, window_sum);
}
return max_sum;
}
public static void Main(){
int[] arr = {5, 2, -1, 0, 3};
int k = 3;
int n = arr.Length;
Console.WriteLine(maxSum(arr, n, k));
}
}
Minh họa:
Sliding Window (k = 3)
[5 2 -1] 0 3 → Sum = 6
5 [2 -1 0] 3 → Sum = 1
5 2 [-1 0 3] → Sum = 2
Expanding Window
Expanding Window (cửa sổ mở rộng) là kỹ thuật mà:- Tập train không giữ kích thước cố định
- Mỗi lần lặp, ta giữ lại toàn bộ dữ liệu cũ
- Và mở rộng thêm dữ liệu mới vào tập train
Expanding Window (Train grows over time)
Walk 1:
[=====3=====][1]
Walk 2:
[=========4=========][1]
Walk 3:
[===============5===============]
2. Walk-Forward Optimization (WFO) là gì?
Nếu WFV là cách chúng ta kiểm tra mô hình, thì WFO là cách chúng ta tinh chỉnh nó.
Trong quá trình trượt cửa sổ thời gian, các tham số tốt nhất (hyperparameters) cho tháng 1 có thể không còn tốt cho tháng 6. WFO thực hiện việc tối ưu hóa lại các tham số sau mỗi lần trượt hoặc sau một khoảng thời gian nhất định.
Quy trình WFO:
- Lấy một cửa sổ dữ liệu.
- Chạy WFV trên cửa sổ đó với nhiều bộ tham số khác nhau.
- Chọn bộ tham số tốt nhất.
- Áp dụng bộ tham số đó để dự đoán cho giai đoạn tiếp theo ("Out of Sample").
- Lặp lại.
3. Ví dụ thực hành: Dự báo tiêu thụ năng lượng (Smart Grid)
Chúng ta sẽ xây dựng một ứng dụng Console đơn giản bằng .NET 10 và ML.NET. Bài toán: Dự đoán lượng điện tiêu thụ dựa trên dữ liệu quá khứ.
using Microsoft.ML;
using Microsoft.ML.Data;
public class EnergyData
{
public float TimeIndex { get; set; }
public float Consumption { get; set; }
}
public class EnergyPrediction
{
[ColumnName("Score")]
public float PredictedConsumption { get; set; }
}
class Program
{
static void Main(string[] args)
{
var mlContext = new MLContext(seed: 1);
var allData = GenerateData(1000);
int trainWindowSize = 200;
int testWindowSize = 50;
int stepSize = 50;
var metricsHistory = new List<double>();
for (int i = 0; (i + trainWindowSize + testWindowSize) < allData.Count; i += stepSize)
{
var trainData = allData.GetRange(i, trainWindowSize);
var testData = allData.GetRange(i + trainWindowSize, testWindowSize);
IDataView trainView = mlContext.Data.LoadFromEnumerable(trainData);
IDataView testView = mlContext.Data.LoadFromEnumerable(testData);
var pipeline = mlContext.Transforms.Concatenate("Features", "TimeIndex")
.Append(mlContext.Regression.Trainers.Sdca(labelColumnName: "Consumption", maximumNumberOfIterations: 100));
var model = pipeline.Fit(trainView);
var predictions = model.Transform(testView);
var metrics = mlContext.Regression.Evaluate(predictions, labelColumnName: "Consumption");
metricsHistory.Add(metrics.RootMeanSquaredError);
Console.WriteLine($"Walk at {i}: RMSE = {metrics.RootMeanSquaredError:F4}");
}
Console.WriteLine($"\nAverage RMSE: {metricsHistory.Average():F4}");
}
static List<EnergyData> GenerateData(int count)
{
var data = new List<EnergyData>();
var rand = new Random(0);
for (int i = 0; i < count; i++)
{
float val = 10 + 5 * (float)Math.Sin(i / 20.0) + (float)(rand.NextDouble() * 2.0 - 1.0);
data.Add(new EnergyData { TimeIndex = i, Consumption = val });
}
return data;
}
}
Sử dụng Walk-Forward Validation giúp bạn tránh được cái bẫy "mô hình tốt trên giấy nhưng tệ khi thực tế". Nếu bạn đang làm việc với dữ liệu cảm biến, năng lượng, hoặc bất kỳ hệ thống IoT nào, hãy ngừng dùng K-Fold ngẫu nhiên và bắt đầu "Walk-Forward" ngay hôm nay.
Nhận xét
Đăng nhận xét