MLOps 01: Data testing, tại sao, cái gì và như thế nào

Tại sao cần viết test

Onboarding new member

Dự án đầu tiên tôi join là script analytics cho hệ thống BI nội bộ và không có unit test, tất cả những gì tôi có là vài dòng comment khó hiểu, càng tệ hơn khi được viết bằng Python do các variables chỉ được implicit data type casting nên rất khó trace back lại logic. Cách tôi làm là debug từng dòng xem các variables trông như thế nào, có chức năng gì khi được gán giá trị.

Nếu có unit test mọi việc đã khác đi nhiều, tôi chỉ cần đọc theo cấu trúc PREPARE (data trông như thế nào), ACTION (đoạn code được thực thi ra sao), ASSERT (kết quả mong đợi khi thực thi đoạn code trên). Rất rõ ràng, Input > Transform > Output. New member thay vì dành ra 2-3 tuần để đọc vô số documents nay chỉ việc đọc những unit test trong vài ngày để có thể bắt đầu join dự án.

Refactoring code

Các dự án được đề xướng và thực hiện trong thời gian ngắn thường có tốc độ thay đổi nhanh, có những thời điểm khi yêu cầu sửa đổi, mọi thứ cần được đập đi hết để xây lại từ đầu. Vậy bắt đầu sửa từ đâu để cấu trúc code hiện tại không bị ảnh hưởng nhiều. Bạn không biết nên làm gì, bởi trong đống code hổ đốn này chỉ cần thay đổi một đoạn nào đó đều có thể dẫn đến toàn bộ functionality của hệ thống chạy trật đường ray đã lập ra trước đó.

Bạn nghĩ công việc viết code là của dev và testing là của QA nên hãy để cho mọi người kiểm chứng sau khi refactor code. Vâng QA cho Software thì có nhiều, nhưng QA bao nhiêu cho đủ để test đi test lại những bug lẽ ra đã được viết test tự động, hơn nữa QA cho Data thì cực hiếm và có lẽ không hề có sẵn để mọi người làm công việc này thay bạn. Nên hãy cố gắng viết test từ sớm, để tiết kiệm thời gian cho bản thân, cho đồng đội và dự án chung mỗi khi có những thay đổi dù nhỏ hay lớn.

Trusted and high-quality code

Nếu không có unit test khi bạn tạo một Pull-Reqquest và yêu cầu member của mình review code thì tâm lý reviewer thường diễn biến như sau. Mình không có thời gian, review như thế nào bây giờ? Code gì khó hiểu quá, làm sao để test đây? Thôi lười quá approve luôn cho rồi. Và thế là technical debt ra đời, tệ hơn là không một ai aware được việc này vì mình không có thời gian mà.

Sau một thời gian dài tích tụ đủ lâu, bạn và team của bạn sẽ trả giá: thêm feature mới khó khăn vì mỗi lần thêm gì vào sẽ phá vỡ những quy luật cũ, knowledge sharing trong team không thường xuyên dẫn đến duplicate code, code bị security breach từ credentials của hệ thống cho tới thông tin khách hàng dễ bị leak, performance không tốt do code xử lý cồng kềnh, documentation cho nhiều nhưng cũng bỏ xó không ai ngó ngàng tới.

Trusted data pipeline

Khi xây dựng các hệ thống Data Products liên quan đến Machine Learning models chắc chắn ta sẽ đụng đến MLOps (Machine Learning Operations). Nói ngắn gọn MLOps = DataOps + ModelOps + DevOps, trong đó DevOps chiếm vai trò quan trọng, giúp cho quá trình CI/CD (Continuous Integration/Continuous Deployment) được diễn ra liên tục và trơn tru. Khi hệ thống phình lên, nhờ có CI tức được testing thường xuyên khi có feature mới, ta sẽ yên tâm hơn để phát triển và nâng cấp chương trình.

Vậy viết test như thế nào?

marsner.com

Nắm tinh thần và khái niệm để testing giúp ta viết test có định hướng hơn. Dưới đây, tôi xin chia sẻ cách viết test thường được áp dụng trong quá trình phát triển phần mềm. Các bài viết về cài đặt sẽ được hiện thực hóa ở những lần sau. Đầu tiên, ta cần nắm tinh thần cái đã. Tinh thần chung đó là FIRST viết tắt của Fast, Independent, Repeatable, Self-Validating và Timely.

  • Fast: Các bài test phải nhanh. Khi các bài test càng nhanh, bạn càng có nhiều thời gian để tiến hành nhiều bài test khác. Khi các bài test chạy chậm, nhóm của bạn sẽ không chạy chúng thường xuyên. Do đó, bạn không phát hiện ra các vấn đề đủ sớm để khắc phục chúng một cách dễ dàng.
  • Independent: Các bài test không nên phụ thuộc vào nhau. Một test case không nên phụ thuộc vào các test case khác. Các thành viên trong nhóm của bạn sẽ có thể chạy từng test case một cách độc lập và theo bất kỳ thứ tự nào. Khi các test case phụ thuộc vào nhau, test case đầu tiên bị lỗi sẽ gây ra một loạt các lỗi ở test case khác, khiến cho việc chẩn đoán trở nên khó khăn.
  • Repeatable: Các bài test nên thực hiện được trong bất kỳ môi trường nào. Nghĩa là, các bài test chạy thành công trên laptop của bạn thì khi lên các môi trường khác như QA hay production thì đều phải chạy được.
  • Self-Validating: Các bài test phải có đầu ra là một chân trị nhất quán đạt hay không đạt. Không có chuyện nay chạy pass ngày mai lại chạy failed.
  • Timely: unit tests cần được viết ngay tại thời điểm có feature mới được xây dựng.

Có nhiều phương pháp test hiệu quả, trong đó cách viết test theo TDD (Test Driven Development) là hợp với tôi nhất, nghĩa là viết test trước, cài đặt chức năng sau. Viết test cho hàm mới, đảm bảo rằng test này fail. Chuyển qua refactor code để test có thể pass. Tối ưu hóa code vừa viết sao cho đảm bảo test vẫn pass và cứ thế lặp lại cho các hàm khác.

Để đánh giá project đã được test bao nhiêu phần trăm, ta thường sử dụng code coverage metric.

Vậy viết test bao nhiêu là đủ? Đối với tôi, viết test vừa đủ không viết thừa chỉ để nâng chỉ số coverage lên 99-100% là ok.

Code coverage metrics hữu ích trong việc tracking project đang được testing như thế nào, giúp cho team đánh giá và xác định được khu vực nào của project chưa được viết unit test.

Ta rất dễ mắc bẫy nâng chỉ số code coverage lên mà không để ý đến assertion, vì bạn có thể nâng chỉ số này lên dễ dàng bằng cách mocking, hay chỉ đơn giản execute xong hàm nhưng không hề đánh giá hay kiểm chứng kết quả trả về, điều này chỉ khiến bạn tốn thời gian để chạy code mà không hề testing gì.

Data project cần test những gì?

Test Functionality

Bước test này tương tự như trong quy trình phát triển phần mềm và cũng được sử dụng trong việc test Data. Như có đề cập ở phía trên, để tiến hành test, ta thực hiện 4 bước:

  1. Arrange (Prepare): setup code và sample data.
  2. Act (Action): thực thi hàm cần test.
  3. Assert: logical assertion kết quả trả về
  4. Cleanup: teardown test resources.

Trong python, ta có thể sử dụng các công cụ sau để test:

Một vài kỹ thuật lập trình khi test thường được dùng:

  • fixture: dùng trong bước Arrange, thường là configuration, khởi động app context, hay SparkSession nếu bạn test tính toán với Spark. (https://docs.pytest.org/en/6.2.x/fixture.html)
  • conftest.py: gom các fixture lại với nhau để tiện quản lý, các module test khác đều có thể truy xuất và tái sử dụng các fixture ở đây.
  • parametrize: một cách Arrange khác tương tự như fixture, giúp chuẩn bị config cho arguments của hàm cần test. (https://docs.pytest.org/en/6.2.x/parametrize.html)
  • mock: overwrite behavior của hàm hay giá trị của variable, dùng trong trường hợp giả lập behavior trả về khi muốn integration test. (https://docs.python.org/3/library/unittest.mock.html)

Test Data

Test code đã khó, test data thì càng khó hơn do Data rất khó lường. Khó lường ở chỗ, schema có thể bị thay đổi (thêm column mới, đổi tên column cũ, hay column bị xóa đi, data types bị thay đổi), transaction bị missing ở một ngày nào đó, hay giá trị lưu bị lỗi và bị noise. Thường một DataFrame ta sẽ tiến hành test ở các cấp độ sau:

  • Testing source ​raw data.
  • Testing intermediate và core data models​.
  • Test ở table level​:
    • Số lượng dòng và cột, thứ tự colum trả về có đúng expectation.
    • Schema có match expectation.
    • Freshness​.
  • Test ở column level​:
    • Uniqueness của primary keys​, có bị duplicate dòng hoặc giá trị key nào không
    • NULL values​.
    • Accepted values​, giá trị biến thiên trong khoảng đoạn được dự đoán hay không, reference key có tồn tại trong primary key ở table cha không.
    • Constraints (logic ràng buộc đặc thù cho từng hàm xử lý ví dụ như list các giá trị đã được filter thì không nên có mặt trong kết quả trả về, trường price thì cần nằm trong khoảng lower/upper bound đã định sẵn, etc), các ràng buộc toàn vẹn khác thì sao?

Wait! Hold on! Có vẻ như chúng ta đang đi lại quá trình xây dựng RDBMS thì phải. Tại sao không ai làm điều này từ trước, đảm bảo tính chất ACID của dữ liệu, chỉ cần cài đặt một vài constraint là được?

Lý do là vì Big Data! Mục tiêu của các hệ thống Big Data cần giải quyết là fast storage và fast computing, nếu phải trải qua rất nhiều scanning validation thì sẽ rất lâu và không đạt được yêu cầu đặt ra. Rất may hiện nay đã có những RDBMS-like dành cho Big Data như Apache Hudi, Apache Iceberge hay Delta Lake vừa đảm bảo khả năng lưu trữ và tính toán của BigData vừa kiểm soát được ACID của dữ liệu.

Thêm vào đó, trong vận hành, ta sẽ tiếp cận với các thuật ngữ như Data Contract, Schema Ops cốt là để quản lý những thay đổi bất chợt này để đảm bảo dữ liệu down stream không bị ảnh hưởng và bị ảnh hưởng rất ít.

Ngày nay, hầu như tất cả các thư viện DataOps đều tích hợp sẵn công cụ testing dữ liệu điển hình là:

Test Machine Learning Model

Thành phần cấu tạo nên model là dữ liệu và thuật toán. Mặc dù, trong quá trình training model ta đã phân chia dữ liệu thành các tập train, validation, test để giúp model generalize nhất có thể nhằm mong đợi khi deploy lên production model có thể tiếp tục duy trì được độ chính xác của mình đối với unseen data. Nhưng dữ liệu thực tế so với dữ liệu training sẽ thay đổi theo thời gian, điều này dẫn đến 2 vấn đề mà ta sẽ gặp phải là Data Drift khi phân phối của P(X) thay đổi và Concept Drift khi phân phối P(Y|X) thay đổi.

Để quyết định có nên retrain model hay không, ta cần monitoring 2 chỉ số này thường xuyên. Ta sẽ dùng các chỉ số kiểm định thống kê (statistical testing) để theo dõi, so sánh các phân phối này theo thời gian. Khi chỉ số theo dõi nằm dưới một ngưỡng cho phép nào đó ta sẽ nhờ MLOps tự động hóa quá trình thu thập dữ liệu mới, retrain model, evaluate performance, register model và deploy lên production. Dưới đây là một vài chỉ số kiểm định thống kê thường dùng:

Ta có thể tự tay cài đặt hệ thống monitoring này từ đầu hoặc tham khảo Evidently AI – open-source machine learning monitoring giúp tự động tính toán các chỉ số test, visualize reports cũng như tích hợp được vào các chương trình ML pipeline như Airflow, MLFlow, Metaflow, Grafana.

Kết

Technical debt là một khái niệm minh họa chi phí phải trả cho tương lai (làm lại việc, làm thêm việc) vì ở hiện tại ta quyết định thực hiện một giải pháp đơn giản (mì ăn liền) thay vì đưa ra cách tiếp cận tốt hơn. Ta cho rằng như vậy sẽ giúp dự án tiết kiệm thời gian để hoàn thành mà không biết rằng về sau ta sẽ trả giá cho những quyết định này. Thường technical debt phát sinh từ đâu?

Từ áp lực deadline cận kề, team thường quyết định bỏ qua các nguyên tắc design code, bỏ qua viết test khiến cho việc đọc code sau này khó hiểu về chức năng, các component phụ thuộc vào nhau dẫn đến khó chỉnh sửa và nâng cấp. Ngoài ra do thay đổi yêu cầu từ business thường xuyên khiến toàn bộ dự án phải tái cấu trúc 180 độ, lúc này ta sẽ khó quyết định được nên trade-off giữa release timeline hay giành thời gian để viết test cũng như review code quality do nợ đã chồng thêm nợ. Vì vậy unit test sinh ra là một trong những giải pháp giúp tối thiểu hóa technical debt về lâu về dài.

Viết unit test là kỹ thuật đã được áp dụng từ lâu trong phát triển phần mềm, ngày nay đã được vận dụng trong phát triển Data Products vì những lợi ích được đề cập trong bài viết. Việc deploy PoC model lên production là một công việc thử thách không như throw-away-code như lúc đi học khi làm các toy project hay khi demo để hoàn thành luận văn nghiên cứu. Công việc sẽ chưa kết thúc sau khi ta đã deploy model thành công, các bước monitoring model performance cũng quan trọng không kém. Việc đưa ra được quyết định có nên retrain model hay không phụ thuộc vào các chỉ số về Data Drift và Concept Drift.

Rất may, tinh thần viết test đã được chú ý nhiều hơn trong cộng đồng hiện tại. Từ đó, các team chuyên viết phần mềm phục vụ cho Data-er ngày càng đông đảo hơn (Dagster, DBT, great_expectations, etc). Các công cụ hỗ trợ testing data này được phát triển để giúp cho chúng ta dễ dàng hơn trong việc ứng dụng unit test vào dự án của mình.

Các bạn có thường viết test không, và unit test đã payback xứng đáng cho bạn trong những tình huống nào, hãy để lại comment bên dưới bài viết. Hẹn gặp lại các bạn ở các bài viết tiếp theo.

Tham khảo thêm

Góc nhìn khác về viết test

Những cuốn sách tham khảo:

Data Drift, Concept Drift:

Trả lời

Điền thông tin vào ô dưới đây hoặc nhấn vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s