I. Data Version Control (DVC)

Hôm nay mình đã có một buổi học cực kỳ giá trị về Data Version Control (DVC) - một công cụ mà mình tin rằng sẽ thay đổi hoàn toàn cách chúng ta quản lý dự án Machine Learning. Sau nhiều tháng vật lộn với các phiên bản dữ liệu và model lộn xộn, cuối cùng mình cũng tìm thấy giải pháp!

1. Vấn Đề Muôn Thuở Trong Các Dự Án ML

Buổi học bắt đầu bằng việc thảo luận về những "nỗi đau" chung mà bất kỳ kỹ sư ML nào cũng từng trải qua:

Vấn đề 1: Quản lý phiên bản dữ liệu hỗn loạn

  • File dữ liệu với tên không nhất quán: dataset_v1_final.csvdataset_final_v2_new.csvdata_augmented_mix05.pkl...
  • Không biết dataset nào tương ứng với model nào
  • Không thể tái tạo lại kết quả cũ vì không nhớ đã dùng dataset nào

Vấn đề 2: Làm việc nhóm thiếu hiệu quả

  • Mỗi thành viên thử nghiệm với các dataset và hyperparameter khác nhau
  • Không có cách nào đồng bộ hóa kết quả
  • "Chạy thử code của tôi đi!" - "Nhưng tôi phải dùng dataset nào? Parameter nào?"

Vấn đề 3: Tách rời giữa code và dữ liệu

  • Git quản lý code rất tốt, nhưng bất lực với file dữ liệu lớn
  • Dữ liệu thường được lưu riêng trên Google Drive, NAS, hay các cloud storage
  • Không có sự liên kết rõ ràng giữa phiên bản code và phiên bản dữ liệu

2. Data Version Control (DVC) - "Git Cho Dữ Liệu"

DVC giải quyết tất cả các vấn đề trên bằng một cách tiếp cận cực kỳ thông minh:

Cách DVC hoạt động:

  • DVC không lưu trữ dữ liệu trực tiếp trong Git repository
  • Thay vào đó, nó tạo các file metadata (.dvc) nhỏ, chứa hash của dữ liệu
  • Git quản lý các file metadata này
  • Dữ liệu thực được lưu trữ trên remote storage (S3, Google Drive, Azure Blob, SSH, etc.)
Git Repository → Quản lý: code + file .dvc (metadata)
↓
DVC → Quản lý: data, model, metrics
↓
Remote Storage → Lưu trữ: file dữ liệu lớn

3. Trải Nghiệm Thực Tế: Case Study MNIST với DVC

Phần thú vị nhất là chúng mình được thực hành với một project MNIST cụ thể:

Bước 1: Thiết lập môi trường

# Tạo project và cấu trúc thư mụcmkdir dvc-mnist-demo
cd dvc-mnist-demo
mkdir -p data/raw data/processed models scripts

# Khởi tạo Git và DVCgit init
dvc init

Bước 2: Theo dõi dữ liệu với DVC

# Tải dataset MNIST
python scripts/download_v1.py

# Theo dõi file dữ liệu với DVC
dvc add data/raw/x_train_v1.npy data/raw/y_train_v1.npy

# Commit lên Gitgit add data/raw/.gitignore data/raw/*.dvc
git commit -m "Version 1: Full MNIST dataset"

Bước 3: Training và theo dõi model

# Training model
python scripts/train.py

# Theo dõi model và metrics
dvc add models/model.npy models/metrics.json
git add models/
git commit -m "Model v1: Full dataset accuracy"

Bước 4: Thử nghiệm với dataset khác

# Tạo dataset nhỏ hơn
python scripts/download_v2.py
dvc add data/raw/x_train_v2.npy

# Training với dataset mới
python scripts/train.py
dvc add models/model.npy
git commit -m "Model v2: Small dataset accuracy"

4. Tính Năng "Thần Thánh" Của DVC

Chuyển đổi phiên bản dữ liệu dễ dàng:

# Quay lại phiên bản cũgit checkout <commit-hash-cu>
dvc checkout# DVC sẽ tự động kéo đúng dữ liệu tương ứng!

Remote Storage linh hoạt:

# Thiết lập remote storage
dvc remote add -d myremote s3://my-bucket/dvc-storage
dvc remote add -d myremote gdrive://your-google-drive-folder

# Đẩy dữ liệu lên cloud
dvc push

# Kéo dữ liệu về khi cần
dvc pull

Pipeline tự động hóa (DVC Pipeline):

File dvc.yaml cho phép định nghĩa toàn bộ workflow:

dvc.yaml

stages:
  preprocess:
    cmd: python scripts/preprocess.py
    deps:
      - scripts/preprocess.py
      - data/raw
    outs:
      - data/processed

  train:
    cmd: python scripts/train.py
    deps:
      - scripts/train.py
      - data/processed
    outs:
      - models/model.pkl
    metrics:
      - models/metrics.json:
          cache: false

Chạy toàn bộ pipeline với một lệnh:

dvc repro# DVC thông minh chỉ chạy lại những bước cần thiết!

5. So Sánh Metrics và Parameters

Một tính năng tuyệt vời khác là khả năng so sánh kết quả:

# Xem metrics của các experiment
dvc metrics show

# So sánh sự khác biệt
dvc metrics diff
dvc params diff

6. Bài Học Lớn Về Tư Duy Versioning

Sau buổi học, mình nhận r bài học quan trọng:

Bài học 1: Reproducibility là yếu tố sống còn

  • DVC đảm bảo mọi thí nghiệm đều có thể tái tạo
  • Không còn "máy tôi chạy được, không hiểu sao máy bạn không chạy"

Bài học 2: Collaboration hiệu quả

  • Team có thể làm việc trên các nhánh khác nhau với các dataset khác nhau
  • Dễ dàng merge và so sánh kết quả

Bài học 3: Professional ML Workflow

  • DVC giúp dự án ML trở nên chuyên nghiệp, có cấu trúc rõ ràng
  • Dễ dàng onboard thành viên mới

7. Khi Nào Nên Dùng DVC?

Dựa trên buổi học, mình tổng kết được các trường hợp nên sử dụng DVC

NÊN DÙNG khi:

  • Dự án có nhiều phiên bản dữ liệu
  • Làm việc theo nhóm
  • Cần reproducibility
  • Dữ liệu từ 1GB trở lên

CÓ THỂ KHÔNG CẦN khi:

  • Dự án nhỏ, dữ liệu < 100MB
  • Chỉ có một người làm
  • Dữ liệu ít thay đổi

Kết Luận & Hành Động Tiếp Theo

Buổi học về DVC thực sự là một bước ngoặt trong hành trình MLOps của mình. Mình đã nhận ra rằng: "Code không phải là tất cả trong ML, mà là sự kết nối giữa code, data và model."

Kế hoạch tiếp theo của mình:

  • Áp dụng DVC vào project đang làm tại công ty
  • Thiết lập DVC pipeline cho toàn bộ workflow
  • Hướng dẫn lại cho các thành viên trong team
  • Tích hợp DVC với CI/CD pipeline

DVC không chỉ là một công cụ, mà là một tư duy mới về quản lý dự án Machine Learning. Nếu bạn đang làm ML và chưa dùng DVC, mình thực sự khuyên bạn nên tìm hiểu ngay!

II. Feast

1. Feast là gì?

Feast (Feature Store) là một nền tảng lưu trữ đặc trưng mã nguồn mở giúp các nhóm dễ dàng vận hành các hệ thống học máy (ML) trong môi trường sản xuất ở quy mô lớn. Công cụ này cho phép bạn định nghĩa, quản lý, kiểm tra và phục vụ (serve) các đặc trưng (features) dùng trong các mô hình AI/ML thực tế.

image.png

2. Các components của Feast

Component của Feast chỉ bao gồm:

🧩Store

Offline store

  • Là nơi lưu trữ feature lịch sử (historical features) hoặc dataset training.
  • Dữ liệu thường được lấy từ data warehouse / data lake (BigQuery, Snowflake, S3, Lakehouse...).
  • Cập nhật theo batch (ví dụ mỗi ngày hoặc mỗi giờ).

Ví dụ:

  • Có dữ liệu 6 tháng giao dịch khách hàng.

    Mỗi dòng là tổng chi tiêu, số lần login, v.v.

→ Lưu trong offline store để dùng train model dự đoán churn.

Online store

  • Là nơi lưu các feature mới nhất, được cập nhật liên tục từ hệ thống thực tế (event stream, Kafka, Redis stream...).
  • Thiết kế để đọc nhanh, latency thấp (<10ms).
  • Dùng để lấy feature cho online inference.

Ví dụ:

  • Khi user truy cập app, bạn cần biết ngay:
    • user_last_login_time
    • num_transactions_last_10min
    • average_spent_last_24h

→ Các feature này được cập nhật liên tục và lưu ở online store.

🧩 Serve

Offline serving

Data dùng cho việc retrain model theo lịch trình (ngày/tháng) hoặc khi data drift/shift được phát hiện

Online Serving

Là phần hệ thống trả kết quả dự đoán ngay lập tức (hoặc trong thời gian rất ngắn — low latency) cho mỗi request tới mô hình đã deploy

🧩 Register

Feast Feature Registrykho lưu trữ trung tâm chứa toàn bộ định nghĩa và metadata của các feature. Nó cho phép các nhà khoa học dữ liệu tìm kiếm, khám phá và cộng tác trên các feature mới.

Feast hỗ trợ hai kiểu registry chính:

  • File-based registry (mặc định): lưu thông tin dưới dạng file protobuf trong hệ thống cục bộ.
  • SQL-based registry: lưu trong cơ sở dữ liệu SQL.

Người dùng có thể thêm, liệt kê, lấy hoặc xóa các đối tượng (như feature views, entities, v.v.) thông qua registry này.

3. Feast cho bài toán Linear Regression

Dữ liệu và mục tiêu

Dữ liệu

TV Radio Newspaper Sales
230.1 37.8 69.2 22.1
44.5 39.3 45.1 10.4
17.2 45.9 69.3 9.3
151.5 41.3 58.5 18.5
180.8 10.8 58.4 12.9

Mục tiêu

  1. Triển khai một Feature Store cục bộ với:
    • Offline store sử dụng file Parquet, và
    • Online store sử dụng SQLite.
  2. Xây dựng tập dữ liệu huấn luyện (training dataset) bằng cách sử dụng các đặc trưng chuỗi thời gian (time series features) từ các file Parquet.
  3. Nạp (ingest) các đặc trưng hàng loạt (batch features) – còn gọi là quá trình materialization, và đặc trưng dạng luồng (streaming features) thông qua Push API vào online store.
  4. Đọc các đặc trưng mới nhất từ offline store để phục vụ cho batch scoring (dự đoán hàng loạt).
  5. Đọc các đặc trưng mới nhất từ online store để phục vụ cho real-time inference (dự đoán thời gian thực).
  6. Khám phá giao diện Feast UI (bản thử nghiệm) để trực quan hóa và quản lý các feature.

Source code: https://github.com/dauvannam1804/feast-linear-regression

Cấu trúc thư mục dự án

.
├── download_data.py
├── engaged_polliwog #tên repo Feast khi khởi tạo
│   ├── feature_repo
│   │   ├── data
│   │   │   ├── Advertising.csv
│   │   │   ├── Advertising.parquet
│   │   │   ├── online_store.db
│   │   │   └── registry.db
│   │   ├── feature_store.yaml
│   │   ├── feature_view.py
│   │   ├── **init**.py
│   │   └── test_workflow.py
│   ├── **init**.py
│   └── [README.md](http://readme.md/)
├── linear_model.pkl
├── predict_online.py
├── prepare_data.py
├── pyproject.toml
├── [README.md](http://readme.md/)
├── requirements.txt
├── train_model.py
└── uv.lock

Bước 0: Khởi tạo dự án và chuẩn bị Dữ liệu

Tạo môi trường ảo và cài đặt thư viện cần thiết:

uv add -r requirements.txt
source .venv/bin/activate

Khởi tạo cấu trúc dự án Feast:

feast init

Tải dữ liệu

Chạy script download_data.py để *tải bộ dữ liệu và lưu vào folder *feature_repo/data

import os
import requests

# URL nguồn dữ liệu
url = "https://raw.githubusercontent.com/nguyen-toan/ISLR/master/dataset/Advertising.csv"

# Đường dẫn lưu file
target_dir = os.path.join("engaged_polliwog", "feature_repo", "data")
target_path = os.path.join(target_dir, "Advertising.csv")

def download_csv():
    # Tạo thư mục nếu chưa có
    os.makedirs(target_dir, exist_ok=True)

    print(f"📥 Đang tải dữ liệu từ: {url}")
    response = requests.get(url)
    response.raise_for_status()  # báo lỗi nếu tải không thành công

    # Ghi file
    with open(target_path, "wb") as f:
        f.write(response.content)

    print(f"✅ Đã lưu file vào: {target_path}")

if __name__ == "__main__":
    download_csv()

Tiền xử lí dữ liệu

File prepare_data.py chuyển file CSV (Advertising.csv) sang định dạng Parquet và sau đó:

  • Tạo khóa định danh duy nhất (ad_id) cho từng bản ghi — đây là entity key mà Feast dùng để liên kết giữa dữ liệu đặc trưng (features) và nhãn hoặc các nguồn dữ liệu khác.
  • Sinh cột thời gian sự kiện (event_timestamp) để Feast biết thời điểm mà đặc trưng này có hiệu lực, giúp quản lý dữ liệu theo thời gian (time-travel join).
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import os

df = pd.read_csv("engaged_polliwog/feature_repo/data/Advertising.csv").drop(columns=["Unnamed: 0"], errors="ignore")

df = df.reset_index().rename(columns={"index":"ad_id"})

start = datetime.now()
df["event_timestamp"] = [start + timedelta(minutes=int(i)) for i in df["ad_id"]]

os.makedirs("engaged_polliwog/feature_repo/data", exist_ok=True)
df.to_parquet("engaged_polliwog/feature_repo/data/Advertising.parquet", index=False)

print("Saved engaged_polliwog/feature_repo/data/Advertising.parquet with columns:", df.columns.tolist())

Bước 1: Định nghĩa các thành phần trong Feature Repo

feature_store.yaml

project: engaged_polliwog
# By default, the registry is a file (but can be turned into a more scalable SQL-backed registry)
registry: data/registry.db
# The provider primarily specifies default offline / online stores & storing the registry in a given cloud
provider: local
online_store:
    type: sqlite
    path: data/online_store.db
entity_key_serialization_version: 3
# By default, no_auth for authentication and authorization, other possible values kubernetes and oidc. Refer the documentation for more details.
auth:
    type: no_auth
  • project: engaged_polliwog — tên dự án Feast, dùng để nhóm các feature, entity và job lại với nhau.
  • registry: data/registry.db — file lưu metadata về các feature, entity, source... giúp Feast biết cấu trúc và lịch sử của feature store.
  • provider: local — chỉ định môi trường chạy Feast là cục bộ (local) thay vì cloud như GCP, AWS,...
  • online_store — định nghĩa nơi lưu trữ dữ liệu online để phục vụ real-time inference.
    • type: sqlite: dùng SQLite làm cơ sở dữ liệu online.
    • path: data/online_store.db: đường dẫn đến file SQLite lưu dữ liệu.
  • entity_key_serialization_version: 3 — phiên bản định dạng mã hóa khóa entity (để tương thích giữa các bản Feast khác nhau).
  • auth: type: no_auth — không sử dụng cơ chế xác thực, phù hợp cho môi trường phát triển và thử nghiệm cục bộ.

feature_view.py

  • Định nghĩa nguồn dữ liệu (FileSource), entity (ad_id)FeatureView (ad_features) — ba thành phần cốt lõi để Feast hiểu và quản lý đặc trưng.
  • Nó giúp Feast biết lấy dữ liệu từ đâu (Advertising.parquet), đặc trưng nào cần dùng (TV, Radio, Newspaper, Sales), và liên kết chúng theo entity nào (ad_id).
  • File này đóng vai trò như cấu hình “feature schema”, để Feast có thể trích xuất, lưu trữ, và phục vụ các feature cho huấn luyện hoặc dự đoán thời gian thực.
from feast import Entity, FeatureView, Field, FileSource, ValueType
from datetime import timedelta
from feast.types import Float32

# Nguồn dữ liệu (parquet) đã tạo ở bước trên
advertising_source = FileSource(
    path="data/Advertising.parquet",
    event_timestamp_column="event_timestamp",
)

# Entity: ad_id
ad_entity = Entity(
    name="ad_id",
    value_type=ValueType.INT64,
    description="ID of the advertising sample"
)

# FeatureView: chứa các feature TV, Radio, Newspaper
ad_feature_view = FeatureView(
    name="ad_features",
    entities=[ad_entity],
    ttl=timedelta(days=3650),  # không quan trọng ở ví dụ này
    schema=[
        Field(name="TV", dtype=Float32),
        Field(name="Radio", dtype=Float32),
        Field(name="Newspaper", dtype=Float32),
        Field(name="Sales", dtype=Float32),  # ta có Sales luôn trong historical
    ],
    source=advertising_source,
    online=True,
    description="Feature view for advertising data",
    tags= {"owner": "TEAM CONQ26", "version": "1.0"}
)

Bước 2: Apply và nạp dữ liệu vào Feature Store

Áp dụng định nghĩa Feature Store

Đăng ký (apply) các định nghĩa feature, entity, và source trong repo vào registry để Feast nhận biết và quản lý.

Di chuyển vào thư mục Feature Store:

cd engaged_polliwog/feature_repo

Áp dụng các định nghĩa trong repo:

feast apply

Nạp dữ liệu vào Online Store

Nạp dữ liệu đặc trưng lịch sử từ offline store (Parquet) vào online store (SQLite), giúp mô hình có thể truy xuất nhanh khi dự đoán thời gian thực.

feast materialize $(date -d '1 year ago' +%Y-%m-%d) $(date +%Y-%m-%d)

Bước 3: Lấy dữ liệu offline và huấn luyện mô hình

train_model.py

  • kết nối với Feature Store của Feast để lấy dữ liệu huấn luyện lịch sử (offline features) từ file Parquet.
  • Sau đó, tách dữ liệu thành train/test, huấn luyện mô hình Linear Regression dự đoán Sales dựa trên TV, Radio, Newspaper.
  • Cuối cùng, mô hình được đánh giá (MSE, R²)lưu lại dưới dạng linear_model.pkl để dùng cho bước dự đoán sau.
from feast import FeatureStore
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import joblib

# load feature store (tự động dùng feature_repo/feature_store.yaml)
store = FeatureStore(repo_path="engaged_polliwog/feature_repo")

# đọc file parquet để tạo entity dataframe (ad_id + event_timestamp)
entity_df = pd.read_parquet("engaged_polliwog/feature_repo/data/Advertising.parquet")[["ad_id", "event_timestamp"]]

# get_historical_features: list features dưới dạng "feature_view:feature_name"
feature_refs = [
    "ad_features:TV",
    "ad_features:Radio",
    "ad_features:Newspaper",
    "ad_features:Sales",
]

training_df = store.get_historical_features(
    entity_df=entity_df,
    features=feature_refs
).to_df()

# kết quả thường có ad_id, event_timestamp, và các cột feature
print("Lấy dữ liệu training từ Offline Store...")
print("Historical data shape:", training_df.shape)
print(training_df.head())

# chuẩn bị X, y
X = training_df[["TV", "Radio", "Newspaper"]]
y = training_df["Sales"]

# chia train/test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# train linear regression
print("\nBắt đầu huấn luyện mô hình...")
model = LinearRegression()
model.fit(X_train, y_train)

# preds
y_pred = model.predict(X_test)

print("\n--- Kết quả đánh giá mô hình ---")
print("MSE:", mean_squared_error(y_test, y_pred))
print("R2:", r2_score(y_test, y_pred))

# lưu model
joblib.dump(model, "linear_model.pkl")
print("Saved linear_model.pkl")

Bước 4: Lấy feature online và dự đoán

predict_online.py

  • Tải mô hình đã huấn luyện (linear_model.pkl)kết nối lại với Feature Store để truy xuất dữ liệu đặc trưng mới nhất.
  • Lấy các feature hiện tại từ Online Store (đã được materialize trước đó) cho một ad_id cụ thể.
  • Cuối cùng, các feature được đưa vào mô hình để dự đoán giá trị Sales theo thời gian thực.
from feast import FeatureStore
import pandas as pd
import joblib

# 1. Load model & feature store
model = joblib.load("linear_model.pkl")
store = FeatureStore(repo_path="engaged_polliwog/feature_repo")

# 2. Giả lập dự đoán cho một ad_id mới (ví dụ: ad_id = 0)
ad_id = 0

# 3. Lấy feature trực tiếp từ Online Store
feature_refs = [
    "ad_features:TV",
    "ad_features:Radio",
    "ad_features:Newspaper",
]

# entity_rows là danh sách các entity cần lấy feature
entity_rows = [{"ad_id": ad_id}]

# get_online_features — lấy feature mới nhất từ Redis (hoặc online backend khác)
online_features = store.get_online_features(
    features=feature_refs,
    entity_rows=entity_rows
).to_dict()

# 4. Chuyển sang DataFrame để feed vào model
features_df = pd.DataFrame.from_dict(online_features)
x = pd.DataFrame(
    [features_df[["TV", "Radio", "Newspaper"]].iloc[0].values],
    columns=["TV", "Radio", "Newspaper"]
)
# 5. Dự đoán
pred = model.predict(x)
print(f"Predicted Sales for ad_id={ad_id}: {pred[0]:.2f}")

Bước 5: Khám phá giao diện Feast UI (bản thử nghiệm)

🌐 Khởi chạy giao diện web để trực quan hóa Feature Store:

cd feature_repo
feast ui

image.png

Hình 1 – Trang chính của dự án (Home)

  • Đây là trang tổng quan của dự án Feast tên engaged_polliwog.
  • Hiển thị số lượng:
    • Feature Services: 0 (chưa tạo service nào để phục vụ mô hình).
    • Feature Views: 1 (đang có 1 view tên ad_features).
    • Entities: 2 (ví dụ ad_id__dummy).
    • Data Sources: 1 (nguồn dữ liệu Advertising.parquet).
  • Bên phải có phần Explore this Project giúp lọc Feature Views theo version hoặc owner.

⇒ Trang tổng quan giúp bạn thấy cấu trúc cơ bản của Feature Store — gồm các entity, feature, và nguồn dữ liệu hiện đã đăng ký.

image.png

Hình 2 – Lineage (quan hệ giữa các đối tượng)

  • Hiển thị mối quan hệ trực quan giữa các thành phần trong Feature Store:
    • 🔴 data/Advertising.parquet: nguồn dữ liệu gốc (Data Source).
    • 🟧 ad_id: entity dùng để định danh từng mẫu dữ liệu.
    • 🟩 ad_features: Feature View chứa các đặc trưng (TV, Radio, Newspaper, Sales).
  • Mũi tên biểu thị luồng dữ liệu: từ Data Source → Entity → Feature View.

image.png

Hình 3 – Data Sources

📍 Vị trí: http://0.0.0.0:8888/p/engaged_polliwog/data-source

Giải thích:

  • Danh sách nguồn dữ liệu được Feast sử dụng.
  • Ở đây có 1 file: data/Advertising.parquet, kiểu BATCH_FILE.

    → Nghĩa là Feast đọc dữ liệu lịch sử từ file tĩnh này.

image.png

Hình 4 – Entities

  • Liệt kê các entity (định danh chính) mà Feast dùng để join dữ liệu.
  • ad_id: kiểu INT64, có 1 Feature View sử dụng.
  • __dummy: entity mặc định (Feast tạo ra khi cần placeholder).

⇒ Entity là khóa chính dùng để liên kết dữ liệu giữa bảng gốc và feature view — giống như “primary key” trong cơ sở dữ liệu.

image.png

Hình 5 – Features

  • Gồm 4 feature: Newspaper, Radio, Sales, TV.
  • Tất cả đều thuộc Feature View: ad_features.
  • Kiểu dữ liệu: FLOAT.

Đây là các đặc trưng thực tế mô hình sẽ dùng để huấn luyện hoặc dự đoán.


image.png

Hình 6 – Feature Views

Giải thích:

  • Hiển thị danh sách các Feature Views.
  • Hiện có 1 view: ad_features (chứa 4 feature).
  • Chưa có Feature Service nào được định nghĩa.

Feature View là “gói” chứa logic lấy dữ liệu từ nguồn, xử lý, và ánh xạ thành feature phục vụ cho mô hình ML.