Download src/monitoring.py from ruoc188/ML1_project_temperature_forecast: direct link, hf CLI and curl.
- Browser
- Download file 5.52 kB
-
https://huggingface.co/spaces/ruoc188/ML1_project_temperature_forecast/resolve/main/src/monitoring.py
- Command line
-
hf download hf://spaces/ruoc188/ML1_project_temperature_forecast/src/monitoring.py
-
curl -L -o monitoring.py https://huggingface.co/spaces/ruoc188/ML1_project_temperature_forecast/resolve/main/src/monitoring.py
5.52 kB
| import os | |
| import joblib | |
| import sys | |
| import logging | |
| from pathlib import Path | |
| sys.path.append(str(Path(__file__).parent.parent)) | |
| import pandas as pd | |
| from datetime import datetime, timedelta | |
| from zoneinfo import ZoneInfo | |
| from src.model_training import retrain_pipeline | |
| # --- Cấu hình logging --- | |
| logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s') | |
| # --- Đường dẫn sử dụng pathlib --- | |
| BASE_DIR = Path(__file__).parent.parent | |
| LOG_PATH = BASE_DIR / "logs" / "daily_rmse.txt" | |
| RETRAIN_LOG_PATH = BASE_DIR / "logs" / "retrain_log.pkl" | |
| DATA_PATH = BASE_DIR / "data" / "latest_3_year.csv" | |
| # --- Ngưỡng cho monitoring --- | |
| RMSE_THRESHOLD = 2.5 # Ngưỡng cảnh báo drift về hiệu năng | |
| RETRAIN_INTERVAL_DAYS = 90 # Tự động retrain sau 90 ngày | |
| # --- Hàm lấy múi giờ --- | |
| def get_timezone(): | |
| """Lấy múi giờ từ biến môi trường TZ, mặc định là Asia/Ho_Chi_Minh.""" | |
| tz_string = os.getenv("TZ", "Asia/Ho_Chi_Minh") | |
| try: | |
| return ZoneInfo(tz_string) | |
| except Exception: | |
| return ZoneInfo("Asia/Ho_Chi_Minh") | |
| def check_rmse_drift(log_path=LOG_PATH, threshold=RMSE_THRESHOLD): | |
| """Kiểm tra xem RMSE trung bình 5 ngày gần nhất có vượt ngưỡng không.""" | |
| log_path = Path(log_path) | |
| if not log_path.exists(): | |
| logging.warning(f"Không tìm thấy file log metrics tại '{log_path}', bỏ qua kiểm tra drift.") | |
| return False | |
| try: | |
| # 1. Tải dữ liệu và kiểm tra định dạng | |
| log_data = joblib.load(log_path) | |
| if not isinstance(log_data, list) or not log_data: | |
| logging.warning(f"File log '{log_path}' không phải là danh sách hoặc bị rỗng.") | |
| return False | |
| # 2. Chuyển đổi sang DataFrame | |
| df = pd.DataFrame(log_data) | |
| except Exception as e: | |
| logging.error(f"Không thể đọc hoặc xử lý file log tại '{log_path}': {e}") | |
| return False | |
| # 3. Kiểm tra sự tồn tại của cột 'rmse' | |
| if "rmse" not in df.columns: | |
| logging.warning(f"Log tại '{log_path}' không có cột 'rmse', bỏ qua kiểm tra drift.") | |
| return False | |
| # 4. Tính RMSE trung bình 5 ngày gần nhất | |
| recent_rmse_series = df["rmse"].dropna().tail(5) | |
| if len(recent_rmse_series) < 5: | |
| logging.info(f"Không có đủ 5 điểm dữ liệu RMSE hợp lệ gần nhất (chỉ có {len(recent_rmse_series)}). Bỏ qua kiểm tra drift.") | |
| return False | |
| recent_rmse = recent_rmse_series.mean() | |
| logging.info(f"RMSE trung bình 5 ngày gần nhất: {recent_rmse:.4f}") | |
| # 5. So sánh với ngưỡng | |
| if recent_rmse > threshold: | |
| logging.warning(f"CẢNH BÁO: RMSE ({recent_rmse:.4f}) đã vượt ngưỡng {threshold}! Cần huấn luyện lại.") | |
| return True | |
| return False | |
| def check_retrain_interval(log_path=RETRAIN_LOG_PATH, interval_days=RETRAIN_INTERVAL_DAYS): | |
| """Kiểm tra xem đã quá 90 ngày kể từ lần retrain gần nhất chưa""" | |
| tz = get_timezone() | |
| log_path = Path(log_path) | |
| if not log_path.exists(): | |
| logging.info("Chưa có lịch sử huấn luyện lại. Bỏ qua kiểm tra khoảng thời gian.") | |
| return False | |
| try: | |
| records = joblib.load(log_path) | |
| if not records: | |
| logging.info("Lịch sử huấn luyện lại rỗng. Cần huấn luyện lại lần đầu.") | |
| return True | |
| except Exception as e: | |
| logging.error(f"Không thể đọc file lịch sử huấn luyện lại tại {log_path}: {e}") | |
| return False | |
| last_date_str = records[-1]["timestamp"] | |
| last_date = datetime.strptime(last_date_str, "%Y-%m-%d %H:%M:%S") | |
| # Chuyển đổi last_date sang múi giờ hiện tại nếu cần | |
| if last_date.tzinfo is None: | |
| last_date = last_date.replace(tzinfo=tz) | |
| current_time = datetime.now(tz) | |
| days_since = (current_time - last_date).days | |
| logging.info(f"Lần huấn luyện lại gần nhất: {last_date_str} ({days_since} ngày trước).") | |
| if days_since > interval_days: | |
| logging.info(f"Đã quá {interval_days} ngày. Cần huấn luyện lại theo lịch.") | |
| return True | |
| return False | |
| def monitor_and_retrain(): | |
| """Giám sát và tự động retrain khi cần""" | |
| logging.info("="*20 + " BẮT ĐẦU QUY TRÌNH GIÁM SÁT " + "="*20) | |
| need_retrain = False | |
| # 1. Kiểm tra drift | |
| if check_rmse_drift(): | |
| need_retrain = True | |
| # 2. Kiểm tra thời gian từ lần retrain gần nhất | |
| if check_retrain_interval(): | |
| need_retrain = True | |
| # 3. Nếu cần retrain thì gọi pipeline | |
| if need_retrain: | |
| logging.info(">>> Bắt đầu quy trình huấn luyện lại mô hình...") | |
| retrain_pipeline(str(DATA_PATH)) # gọi hàm retrain trong model_training | |
| logging.info(">>> Quy trình huấn luyện lại đã hoàn tất.") | |
| else: | |
| logging.info("Hệ thống ổn định, không cần huấn luyện lại.") | |
| logging.info("="*20 + " KẾT THÚC QUY TRÌNH GIÁM SÁT " + "="*20) | |
| if __name__ == "__main__": | |
| monitor_and_retrain() | |
| # hàm check interval chạy ổn | |
| # hàm check rmse, chạy thử với file daily_rmse có ổn ko | |
| # xem có cần tự động 5 ngày monitorin, ... / hoặc là sau khi gọi bên forcasting => gọi hàm monitor_and_retrain trong monitoring |