Khoá môi trường là ghi lại chính xác phiên bản từng gói mà phân tích dùng, để máy khác hoặc chính bạn năm sau dựng lại được đúng như vậy. Với R, dùng renv: renv::init(), renv::snapshot(), renv::restore(). Với Python, dùng conda: xuất environment.yml rồi tạo lại bằng conda env create.
Câu chuyện quen thuộc: mã chạy tốt trên máy nghiên cứu sinh, sang máy người hướng dẫn thì báo lỗi hàm không tồn tại, hoặc tệ hơn là chạy được nhưng ra hệ số khác ở chữ số thứ ba. Nguyên nhân thường là phiên bản gói khác nhau: một gói đổi giá trị mặc định của tham số, một gói phụ thuộc đổi cách xử lý giá trị thiếu. Bài này giải thích cơ chế và thao tác khoá môi trường cho hai ngôn ngữ phổ biến nhất trong phân tích.
Vì sao cùng mã mà kết quả khác nhau giữa hai máy?
Một phân tích phụ thuộc vào nhiều tầng: phiên bản ngôn ngữ, phiên bản các gói gọi trực tiếp, phiên bản các gói mà chúng gọi tiếp, thư viện hệ thống như BLAS cho đại số tuyến tính, đôi khi cả hệ điều hành. Cài gói vào một thời điểm khác là nhận phiên bản khác. Phần lớn thay đổi không ảnh hưởng gì, nhưng chỉ cần một gói đổi thuật toán tối ưu hoá mặc định là kết quả lệch.
Khoá môi trường giải quyết tầng thứ hai và thứ ba: các gói. Tầng ngôn ngữ thì ghi lại và cài đúng phiên bản bằng tay hoặc qua công cụ quản lý phiên bản. Tầng hệ thống chỉ khoá được bằng container như Docker, chủ đề của một bài khác. Với đa số đề tài, khoá gói đã giải quyết phần lớn vấn đề.
Một ví dụ dễ gặp: gói xử lý dữ liệu bảng đổi cách sắp xếp mặc định của biến phân loại giữa hai phiên bản lớn, khiến nhóm tham chiếu trong hồi quy đổi theo. Mã vẫn chạy, không báo lỗi, chỉ có dấu của một hệ số đảo chiều. Người hướng dẫn chạy trên máy mình và thấy kết quả ngược với bản thảo. Nếu dự án có tệp khoá, hai người dùng cùng phiên bản gói và chuyện này không xảy ra; nếu không, hai bên có thể mất vài ngày mới tìm ra nguyên nhân.
renv cho R: bốn lệnh chính
Mở dự án RStudio hoặc đặt thư mục làm việc ở gốc dự án, rồi:
install.packages('renv')nếu chưa có gói.renv::init()tạo thư viện riêng cho dự án, quét mã để tìm các gói đang dùng, ghi vào tệprenv.lock. Từ đây dự án dùng thư viện riêng, không đụng tới thư viện chung của máy.- Cài hoặc cập nhật gói như bình thường bằng
install.packages()hoặcrenv::install(), rồi chạyrenv::snapshot()để ghi phiên bản mới vàorenv.lock. - Ở máy khác, sao chép dự án về, mở R trong thư mục dự án, chạy
renv::restore()để cài đúng các phiên bản trong tệp khoá. renv::status()báo khi thư viện và tệp khoá lệch nhau, ví dụ bạn đã cài gói mới mà quên snapshot.
Đưa vào Git các tệp renv.lock, .Rprofile, renv/activate.R và renv/settings.json. Thư mục renv/library chứa gói đã cài thì không đưa vào; renv tự tạo tệp bỏ qua cho phần này. Tệp khoá có ghi phiên bản R, nhưng renv không cài R cho bạn: máy khôi phục nên cài cùng phiên bản R, ít nhất cùng phiên bản lớn và nhỏ.
conda cho Python: tạo, xuất, tạo lại
Tạo môi trường riêng cho dự án
conda create -n luanan python=3.11 pandas statsmodels matplotlib -c conda-forge tạo môi trường tên luanan, rồi conda activate luanan trước mỗi phiên làm việc. Mỗi dự án một môi trường, đừng cài mọi thứ vào môi trường base.
Hai cách xuất
conda env export --from-history > environment.yml chỉ ghi các gói bạn yêu cầu cài, gọn và dựng lại được trên hệ điều hành khác, nhưng các gói phụ thuộc sẽ lấy phiên bản mới tại thời điểm dựng lại. Thêm số phiên bản vào từng dòng trong tệp, như pandas=2.2, để chặt hơn. conda env export > environment-day-du.yml ghi mọi gói kèm bản dựng cụ thể, chặt nhất nhưng thường chỉ dựng lại được trên cùng hệ điều hành. Nên lưu cả hai: tệp gọn để chia sẻ, tệp đầy đủ để ghi nhận chính xác những gì đã chạy.
Tạo lại ở máy khác
conda env create -f environment.yml. Khi tệp thay đổi, cập nhật môi trường hiện có bằng conda env update -f environment.yml --prune; tuỳ chọn --prune gỡ các gói không còn trong tệp.
Nếu không dùng conda
Môi trường ảo có sẵn của Python cũng làm được: python -m venv .venv, kích hoạt, cài gói bằng pip, rồi pip freeze > requirements.txt; ở máy khác pip install -r requirements.txt. Cách này không quản lý được phiên bản Python và thư viện hệ thống, nhưng đủ cho dự án chỉ dùng gói Python thuần.
Đối chiếu nhanh hai công cụ
Thao tác cùng mục đích ở hai bên:
| Việc | renv (R) | conda (Python) |
|---|---|---|
| Khởi tạo môi trường | renv::init() | conda create -n ten python=3.11 |
| Ghi lại phiên bản | renv::snapshot() | conda env export --from-history > environment.yml |
| Tệp khoá đưa vào Git | renv.lock | environment.yml |
| Khôi phục | renv::restore() | conda env create -f environment.yml |
| Kiểm lệch | renv::status() | So conda list với tệp xuất |
| Phiên bản ngôn ngữ | Ghi trong tệp khoá, cài tay | Cài theo tệp, nằm trong môi trường |
Lỗi hay gặp khi khôi phục môi trường
Gói R cần biên dịch nhưng máy thiếu công cụ. Trên Windows, phiên bản gói cũ có thể không còn bản nhị phân, phải biên dịch từ mã nguồn và cần Rtools. Trên Linux cần các thư viện hệ thống tương ứng. Đọc thông báo lỗi để biết thiếu thư viện nào.
Gói lấy từ GitHub hoặc Bioconductor. renv ghi được nguồn này, nhưng máy khôi phục phải truy cập được nguồn đó. Gói từ kho GitHub đã bị xoá thì không khôi phục được; với dự án quan trọng, cân nhắc lưu bản gói vào kho dữ liệu.
Trộn pip và conda trong một môi trường. Cài gói bằng pip sau khi đã dùng conda có thể làm conda không còn kiểm soát được phụ thuộc. Ưu tiên cài mọi thứ bằng conda từ kênh conda-forge; chỉ dùng pip cho gói không có trên conda và ghi chúng trong mục pip của environment.yml.
Điều khoản kênh mặc định. Anaconda áp điều khoản dịch vụ riêng cho kênh defaults, có ràng buộc với tổ chức trên một quy mô nhất định. Đọc điều khoản hiện hành; nhiều nhóm chọn Miniforge, bản cài mặc định dùng kênh conda-forge.
Tệp khoá có nhưng không ai cập nhật. Cài thêm gói rồi quên snapshot là lỗi thường xuyên nhất. Thêm việc chạy renv::status() trước mỗi lần ghi nhận Git.
Đưa khoá môi trường vào thói quen của nhóm
Quy ước chung
Thống nhất một công cụ cho mỗi ngôn ngữ trong toàn nhóm, ghi vào README. Nhóm nửa dùng conda, nửa dùng pip với venv thì tệp khoá không ai tin được. Ghi cả lệnh khôi phục môi trường ngay ở đầu README để người mới chạy được trong vài phút.
Cập nhật gói có chủ đích
Không cập nhật gói giữa lúc đang trả lời phản biện, trừ khi buộc phải sửa lỗi. Nếu cần, cập nhật trong một nhánh riêng, chạy lại toàn bộ phân tích, so kết quả với bản cũ, rồi mới gộp. Ghi trong thông điệp ghi nhận rằng kết quả có đổi hay không.
Thử khôi phục định kỳ
Tệp khoá chỉ đáng tin khi đã từng được dùng để dựng lại. Mỗi vài tháng, hoặc trước khi nộp bài, nhờ một thành viên khác sao chép kho về máy mình, khôi phục môi trường và chạy lại từ đầu. Lỗi phát hiện lúc này rẻ hơn nhiều so với khi phản biện hay người đọc báo lỗi.
Mức nào là đủ cho một đề tài?
Với luận văn hay bài báo dùng vài chục gói thông dụng, tệp khoá cộng ghi chú phiên bản ngôn ngữ và hệ điều hành trong README là đủ cho người khác chạy lại trong vài năm. Chỉ khi phụ thuộc thư viện hệ thống phức tạp, như gói không gian địa lý hay học sâu cần GPU, hoặc khi yêu cầu tái lập rất chặt, mới cần tính tới container.
Một mẹo nhỏ: cuối mỗi tệp phân tích R, in sessionInfo(); với Python, in phiên bản các gói chính. Kết quả in ra lưu cùng kết quả phân tích là bằng chứng môi trường thật đã chạy, kể cả khi tệp khoá có sai sót.
Câu hỏi thường gặp
renv có thay được việc ghi phiên bản R không?
Không hoàn toàn. Tệp renv.lock ghi phiên bản R đã dùng nhưng renv không tự cài R. Người khôi phục phải cài đúng hoặc gần đúng phiên bản R đó trước khi chạy renv::restore().
Có nên đưa thư mục renv/library vào Git không?
Không. Thư mục này chứa gói đã cài, nặng và phụ thuộc hệ điều hành. Chỉ cần đưa renv.lock và vài tệp cấu hình nhỏ; renv tự tạo tệp bỏ qua cho thư viện.
conda và venv khác nhau thế nào?
venv chỉ quản lý gói Python cài bằng pip. conda quản lý cả phiên bản Python lẫn thư viện ngoài Python như thư viện C, nên hợp với gói khoa học có phần biên dịch phức tạp.
environment.yml xuất ở Windows dùng trên Linux được không?
Bản xuất với --from-history thường dùng được vì chỉ ghi tên và phiên bản gói. Bản xuất đầy đủ chứa mã bản dựng riêng cho từng hệ điều hành nên hay lỗi khi chuyển sang hệ khác.
Đang làm dở dự án R có thêm renv được không?
Được. Chạy renv::init() trong thư mục dự án, renv sẽ quét mã, cài các gói đang dùng vào thư viện riêng và tạo tệp khoá từ trạng thái hiện tại.