完成--完成加载模型预测方法

This commit is contained in:
haotian 2025-02-20 10:37:53 +08:00
parent 7b99da7251
commit b0609e1ad1
91 changed files with 700 additions and 14 deletions

View File

@ -500,26 +500,49 @@ Response:
} }
``` ```
### 2.8 模型预测 ### 2.9 模型预测
```http ```http
POST /api/predict POST /api/model/predict
Content-Type: application/json Content-Type: application/json
Request: Request:
{ {
"model_id": "model_20230820_001", "run_id": "7970364d490f4e0aa0375c2db26215f3",
"data": "dataset/dataset_processed/test.csv", "data": "dataset/dataset_processed/test.csv",
"output_path": "predictions/pred_20230820_001.csv" "output_path": "predictions/pred_20250219_001.csv",
"batch_size": 32,
"device": "cuda",
"return_proba": true,
"metrics": ["accuracy", "f1", "precision", "recall"]
} }
Response: Response:
{ {
"status": "success", "status": "success",
"prediction_id": "pred_20230820_001", "prediction": {
"output_file": "predictions/pred_20230820_001.csv", "id": "pred_20250219_001",
"metrics": { "run_id": "7970364d490f4e0aa0375c2db26215f3",
"accuracy": 0.95, "model_name": "XGBClassifier",
"f1": 0.94 "output_file": "predictions/pred_20250219_001.csv",
"prediction_time": "2025-02-19 15:30:45",
"samples_count": 1000,
"metrics": {
"accuracy": 0.956,
"f1": 0.948,
"precision": 0.962,
"recall": 0.935
},
"execution_time": "5.23s"
}
}
Error Response:
{
"status": "error",
"message": "模型预测失败",
"details": {
"error_type": "ValueError",
"error_message": "输入数据格式不正确"
} }
} }
``` ```

6
example_model_delete.py Normal file
View File

@ -0,0 +1,6 @@
from function.model_manager import ModelManager
# 创建模型管理器实例
manager = ModelManager()
back = manager.delete_model('7970364d490f4e0aa0375c2db26215f3')
print(back)

View File

@ -7,7 +7,7 @@ manager = ModelManager()
result = manager.get_finished_models( result = manager.get_finished_models(
page=1, page=1,
page_size=10, page_size=10,
experiment_name='breast_cancer_classification_2' experiment_name='breast_cancer_classification_3'
) )
# 打印结果 # 打印结果

8
example_model_predict.py Normal file
View File

@ -0,0 +1,8 @@
from function.model_manager import ModelManager
model_manager = ModelManager()
print(model_manager.predict(run_id = "33939ea6d8ce4d43a268f23f7361651e",\
data_path="/home/admin-root/haotian/MLPlatform/dataset/dataset_processed/breast_cancer_20250219_145614/test_breast_cancer_20250219_145614.csv",\
output_path="predictions/pred_breast_cancer_20250219_145614.csv" ,\
metrics= ["accuracy", "f1", "precision", "recall"] ))

View File

@ -30,7 +30,7 @@ model_config = {
# 训练模型, 删除训练实验时要删除 mlruns/.trash/ 回收站里的文件 # 训练模型, 删除训练实验时要删除 mlruns/.trash/ 回收站里的文件
# 模型文件 直接在 mlruns/文件夹下 # 模型文件 直接在 mlruns/文件夹下
for i in range(8, 20): for i in range(3, 4):
result = trainer.train_model( result = trainer.train_model(
{ {
'features': X_train, 'features': X_train,

View File

@ -4,9 +4,19 @@ import pandas as pd
from typing import Dict, List, Optional from typing import Dict, List, Optional
import logging import logging
from pathlib import Path from pathlib import Path
import datetime from datetime import datetime
import yaml import yaml
import json import json
import time
import os
import numpy as np
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score, roc_auc_score,
mean_absolute_error, mean_squared_error, r2_score, explained_variance_score,
adjusted_rand_score, homogeneity_score, completeness_score, silhouette_score
)
import torch
from torch.utils.data import DataLoader, TensorDataset
class ModelManager: class ModelManager:
"""模型管理类""" """模型管理类"""
@ -16,6 +26,7 @@ class ModelManager:
self.config = config or {} self.config = config or {}
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self._setup_logging() self._setup_logging()
self._metrics_map()
# 初始化MLflow客户端 # 初始化MLflow客户端
self.mlflow_uri = self.config.get('mlflow_uri', 'http://10.0.0.202:5000') self.mlflow_uri = self.config.get('mlflow_uri', 'http://10.0.0.202:5000')
@ -28,13 +39,30 @@ class ModelManager:
log_dir.mkdir(exist_ok=True) log_dir.mkdir(exist_ok=True)
file_handler = logging.FileHandler( file_handler = logging.FileHandler(
log_dir / f'model_manager_{datetime.datetime.now():%Y%m%d_%H%M%S}.log' log_dir / f'model_manager_{datetime.now():%Y%m%d_%H%M%S}.log'
) )
file_handler.setFormatter( file_handler.setFormatter(
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
) )
self.logger.addHandler(file_handler) self.logger.addHandler(file_handler)
self.logger.setLevel(logging.INFO) self.logger.setLevel(logging.INFO)
def _metrics_map(self):
self.metrics_map={
'accuracy' : accuracy_score,
'precision' : precision_score,
'recall' : recall_score,
'f1' : f1_score,
'mae' : mean_absolute_error,
'mse' : mean_squared_error,
# 'rmse' : np.sqrt(mean_absolute_error), # 这里要特殊处理一下
'r2': r2_score,
'explained_variance' : explained_variance_score,
'adjusted_rand' : adjusted_rand_score,
'homogeneity' : homogeneity_score,
'completeness': completeness_score,
'silhouette' : silhouette_score
}
def get_finished_models( def get_finished_models(
self, self,
@ -269,4 +297,158 @@ class ModelManager:
return { return {
'status': 'error', 'status': 'error',
'message': error_msg 'message': error_msg
} }
def predict(
self,
run_id: str,
data_path: str,
output_path: str,
batch_size: int = 32,
device: str = 'cuda' if torch.cuda.is_available() else 'cpu',
return_proba: bool = True,
metrics: List[str] = None
) -> Dict:
"""
使用指定的模型进行预测
Args:
run_id: MLflow运行ID
data_path: 输入数据路径
output_path: 预测结果保存路径
batch_size: 批处理大小
device: 计算设备 ('cuda' or 'cpu')
return_proba: 是否返回概率预测
metrics: 评估指标列表
Returns:
预测结果信息
"""
# try:
start_time = time.time()
# 获取模型信息
run = self.client.get_run(run_id)
if not run:
return {
'status': 'error',
'message': f'未找到运行ID为 {run_id} 的模型'
}
# 加载模型
model = mlflow.pyfunc.load_model(f"runs:/{run_id}/model")
model_name = run.data.params.get('algorithm', 'Unknown')
# 加载数据
try:
data = pd.read_csv(data_path)
if 'label' in data.columns:
y_true = data.pop('label').values
has_labels = True
elif 'target' in data.columns:
y_true = data.pop('target').values
has_labels = True
else:
has_labels = False
X = data.values
except Exception as e:
return {
'status': 'error',
'message': '数据加载失败',
'details': {
'error_type': type(e).__name__,
'error_message': str(e)
}
}
# 创建预测ID
pred_id = f"pred_{datetime.now():%Y%m%d_%H%M%S}"
# 进行预测
if isinstance(model, torch.nn.Module):
# PyTorch模型预测
model.to(device)
model.eval()
dataset = TensorDataset(torch.FloatTensor(X))
dataloader = DataLoader(dataset, batch_size=batch_size)
predictions = []
probas = []
with torch.no_grad():
for batch in dataloader:
batch = batch[0].to(device)
outputs = model(batch)
if return_proba:
proba = torch.softmax(outputs, dim=1)
probas.append(proba.cpu().numpy())
preds = outputs.argmax(dim=1)
predictions.append(preds.cpu().numpy())
predictions = np.concatenate(predictions)
if return_proba:
probas = np.concatenate(probas)
else:
# 其他模型预测
predictions = model.predict(X)
if return_proba and hasattr(model, 'predict_proba'):
probas = model.predict_proba(X)
else:
probas = []
# 计算评估指标
metrics_results = {}
if has_labels and metrics:
for metric in metrics:
if metric in self.metrics_map.keys():
metrics_results[metric] = float(self.metrics_map[metric](y_true, predictions))
# 保存预测结果
results_df = pd.DataFrame({
'prediction': predictions
})
if return_proba and len(probas) > 0:
for i in range(probas.shape[1]):
results_df[f'probability_{i}'] = probas[:, i]
# 确保输出目录存在
os.makedirs(os.path.dirname(output_path), exist_ok=True)
results_df.to_csv(output_path, index=False)
# 计算执行时间
execution_time = time.time() - start_time
# 记录日志
self.logger.info(
f"预测完成 - Run ID: {run_id}, 模型: {model_name}, "
f"样本数: {len(predictions)}, 耗时: {execution_time:.2f}s"
)
return {
'status': 'success',
'prediction': {
'id': pred_id,
'run_id': run_id,
'model_name': model_name,
'output_file': output_path,
'prediction_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'samples_count': len(predictions),
'metrics': metrics_results,
'execution_time': f"{execution_time:.2f}s"
}
}
# except Exception as e:
# error_msg = f"预测过程发生错误: {str(e)}"
# self.logger.error(error_msg)
# return {
# 'status': 'error',
# 'message': '模型预测失败',
# 'details': {
# 'error_type': type(e).__name__,
# 'error_message': str(e)
# }
# }

View File

@ -0,0 +1,20 @@
artifact_path: model
flavors:
python_function:
env:
conda: conda.yaml
virtualenv: python_env.yaml
loader_module: mlflow.sklearn
model_path: model.pkl
predict_fn: predict
python_version: 3.9.19
sklearn:
code: null
pickled_model: model.pkl
serialization_format: cloudpickle
sklearn_version: 1.5.2
mlflow_version: 2.20.1
model_size_bytes: 96534
model_uuid: 80ec24f7f34643b9be9da8be761430eb
run_id: 725c551dce9c477391856e6ac41c75bf
utc_time_created: '2025-02-20 01:43:39.758338'

View File

@ -0,0 +1,15 @@
channels:
- conda-forge
dependencies:
- python=3.9.19
- pip<=24.0
- pip:
- mlflow==2.20.1
- cloudpickle==3.1.0
- numpy==1.26.4
- pandas==2.2.2
- psutil==6.0.0
- scikit-learn==1.5.2
- scipy==1.13.1
- xgboost==2.1.4
name: mlflow-env

View File

@ -0,0 +1,7 @@
python: 3.9.19
build_dependencies:
- pip==24.0
- setuptools==60.2.0
- wheel==0.43.0
dependencies:
- -r requirements.txt

View File

@ -0,0 +1,8 @@
mlflow==2.20.1
cloudpickle==3.1.0
numpy==1.26.4
pandas==2.2.2
psutil==6.0.0
scikit-learn==1.5.2
scipy==1.13.1
xgboost==2.1.4

View File

@ -0,0 +1,20 @@
artifact_path: model
flavors:
python_function:
env:
conda: conda.yaml
virtualenv: python_env.yaml
loader_module: mlflow.sklearn
model_path: model.pkl
predict_fn: predict
python_version: 3.9.19
sklearn:
code: null
pickled_model: model.pkl
serialization_format: cloudpickle
sklearn_version: 1.5.2
mlflow_version: 2.20.1
model_size_bytes: 96534
model_uuid: 80a9b0fb66324b0f982aebb166759911
run_id: d16c356bb3324ede819f9998d427780a
utc_time_created: '2025-02-20 01:44:24.622216'

View File

@ -0,0 +1,15 @@
channels:
- conda-forge
dependencies:
- python=3.9.19
- pip<=24.0
- pip:
- mlflow==2.20.1
- cloudpickle==3.1.0
- numpy==1.26.4
- pandas==2.2.2
- psutil==6.0.0
- scikit-learn==1.5.2
- scipy==1.13.1
- xgboost==2.1.4
name: mlflow-env

View File

@ -0,0 +1,7 @@
python: 3.9.19
build_dependencies:
- pip==24.0
- setuptools==60.2.0
- wheel==0.43.0
dependencies:
- -r requirements.txt

View File

@ -0,0 +1,8 @@
mlflow==2.20.1
cloudpickle==3.1.0
numpy==1.26.4
pandas==2.2.2
psutil==6.0.0
scikit-learn==1.5.2
scipy==1.13.1
xgboost==2.1.4

View File

@ -0,0 +1,20 @@
artifact_path: model
flavors:
python_function:
env:
conda: conda.yaml
virtualenv: python_env.yaml
loader_module: mlflow.sklearn
model_path: model.pkl
predict_fn: predict
python_version: 3.9.19
sklearn:
code: null
pickled_model: model.pkl
serialization_format: cloudpickle
sklearn_version: 1.5.2
mlflow_version: 2.20.1
model_size_bytes: 96534
model_uuid: 770483f5da38405496fdedeef1cfe93f
run_id: 81b3390084a146bb949fec21c80dba2c
utc_time_created: '2025-02-20 01:43:43.110167'

View File

@ -0,0 +1,15 @@
channels:
- conda-forge
dependencies:
- python=3.9.19
- pip<=24.0
- pip:
- mlflow==2.20.1
- cloudpickle==3.1.0
- numpy==1.26.4
- pandas==2.2.2
- psutil==6.0.0
- scikit-learn==1.5.2
- scipy==1.13.1
- xgboost==2.1.4
name: mlflow-env

View File

@ -0,0 +1,7 @@
python: 3.9.19
build_dependencies:
- pip==24.0
- setuptools==60.2.0
- wheel==0.43.0
dependencies:
- -r requirements.txt

View File

@ -0,0 +1,8 @@
mlflow==2.20.1
cloudpickle==3.1.0
numpy==1.26.4
pandas==2.2.2
psutil==6.0.0
scikit-learn==1.5.2
scipy==1.13.1
xgboost==2.1.4

View File

@ -0,0 +1,15 @@
artifact_uri: mlflow-artifacts:/189649205577051698/725c551dce9c477391856e6ac41c75bf/artifacts
end_time: 1740015822953
entry_point_name: ''
experiment_id: '189649205577051698'
lifecycle_stage: active
run_id: 725c551dce9c477391856e6ac41c75bf
run_name: redolent-slug-558
run_uuid: 725c551dce9c477391856e6ac41c75bf
source_name: ''
source_type: 4
source_version: ''
start_time: 1740015819406
status: 3
tags: []
user_id: admin-root

View File

@ -0,0 +1 @@
1740015819735 0.961038961038961 0

View File

@ -0,0 +1 @@
1740015819747 0.9612318007520749 0

View File

@ -0,0 +1 @@
1740015819739 0.9617833147244911 0

View File

@ -0,0 +1 @@
1740015819743 0.961038961038961 0

View File

@ -0,0 +1 @@
1740015819752 0.9607692307692308 0

View File

@ -0,0 +1 @@
['计算效率高,支持并行计算。', '具有内置的缺失值处理能力。']

View File

@ -0,0 +1 @@
XGBClassifier

View File

@ -0,0 +1 @@
/home/admin-root/haotian/MLPlatform/dataset/dataset_processed/breast_cancer_20250219_144629

View File

@ -0,0 +1 @@
['参数较多,调优较复杂。']

View File

@ -0,0 +1 @@
XGBoostExtreme Gradient Boosting是一种基于梯度提升树GBDT的改进算法具有更强的正则化和并行处理能力。

View File

@ -0,0 +1 @@
classification

View File

@ -0,0 +1 @@
[{"run_id": "725c551dce9c477391856e6ac41c75bf", "artifact_path": "model", "utc_time_created": "2025-02-20 01:43:39.758338", "model_uuid": "80ec24f7f34643b9be9da8be761430eb", "flavors": {"python_function": {"model_path": "model.pkl", "predict_fn": "predict", "loader_module": "mlflow.sklearn", "python_version": "3.9.19", "env": {"conda": "conda.yaml", "virtualenv": "python_env.yaml"}}, "sklearn": {"pickled_model": "model.pkl", "sklearn_version": "1.5.2", "serialization_format": "cloudpickle", "code": null}}}]

View File

@ -0,0 +1 @@
redolent-slug-558

View File

@ -0,0 +1 @@
7b99da7251d23ceb9b3dfbb599e96dc00054e961

View File

@ -0,0 +1 @@
/home/admin-root/haotian/MLPlatform/example_model_trainer.py

View File

@ -0,0 +1 @@
admin-root

View File

@ -0,0 +1,6 @@
artifact_location: mlflow-artifacts:/189649205577051698
creation_time: 1740015819325
experiment_id: '189649205577051698'
last_update_time: 1740015819325
lifecycle_stage: active
name: breast_cancer_classification_1

View File

@ -0,0 +1,15 @@
artifact_uri: mlflow-artifacts:/433321862082712659/d16c356bb3324ede819f9998d427780a/artifacts
end_time: 1740015867827
entry_point_name: ''
experiment_id: '433321862082712659'
lifecycle_stage: active
run_id: d16c356bb3324ede819f9998d427780a
run_name: skillful-swan-139
run_uuid: d16c356bb3324ede819f9998d427780a
source_name: ''
source_type: 4
source_version: ''
start_time: 1740015864275
status: 3
tags: []
user_id: admin-root

View File

@ -0,0 +1 @@
1740015864600 0.961038961038961 0

View File

@ -0,0 +1 @@
1740015864611 0.9612318007520749 0

View File

@ -0,0 +1 @@
1740015864604 0.9617833147244911 0

View File

@ -0,0 +1 @@
1740015864607 0.961038961038961 0

View File

@ -0,0 +1 @@
1740015864616 0.9607692307692308 0

View File

@ -0,0 +1 @@
['计算效率高,支持并行计算。', '具有内置的缺失值处理能力。']

View File

@ -0,0 +1 @@
XGBClassifier

View File

@ -0,0 +1 @@
/home/admin-root/haotian/MLPlatform/dataset/dataset_processed/breast_cancer_20250219_144629

View File

@ -0,0 +1 @@
['参数较多,调优较复杂。']

View File

@ -0,0 +1 @@
XGBoostExtreme Gradient Boosting是一种基于梯度提升树GBDT的改进算法具有更强的正则化和并行处理能力。

View File

@ -0,0 +1 @@
classification

View File

@ -0,0 +1 @@
[{"run_id": "d16c356bb3324ede819f9998d427780a", "artifact_path": "model", "utc_time_created": "2025-02-20 01:44:24.622216", "model_uuid": "80a9b0fb66324b0f982aebb166759911", "flavors": {"python_function": {"model_path": "model.pkl", "predict_fn": "predict", "loader_module": "mlflow.sklearn", "python_version": "3.9.19", "env": {"conda": "conda.yaml", "virtualenv": "python_env.yaml"}}, "sklearn": {"pickled_model": "model.pkl", "sklearn_version": "1.5.2", "serialization_format": "cloudpickle", "code": null}}}]

View File

@ -0,0 +1 @@
skillful-swan-139

View File

@ -0,0 +1 @@
7b99da7251d23ceb9b3dfbb599e96dc00054e961

View File

@ -0,0 +1 @@
/home/admin-root/haotian/MLPlatform/example_model_trainer.py

View File

@ -0,0 +1 @@
admin-root

View File

@ -0,0 +1,15 @@
artifact_uri: mlflow-artifacts:/656341556838275234/81b3390084a146bb949fec21c80dba2c/artifacts
end_time: 1740015825618
entry_point_name: ''
experiment_id: '656341556838275234'
lifecycle_stage: active
run_id: 81b3390084a146bb949fec21c80dba2c
run_name: rebellious-fish-472
run_uuid: 81b3390084a146bb949fec21c80dba2c
source_name: ''
source_type: 4
source_version: ''
start_time: 1740015822972
status: 3
tags: []
user_id: admin-root

View File

@ -0,0 +1 @@
1740015823091 0.961038961038961 0

View File

@ -0,0 +1 @@
1740015823104 0.9612318007520749 0

View File

@ -0,0 +1 @@
1740015823096 0.9617833147244911 0

View File

@ -0,0 +1 @@
1740015823100 0.961038961038961 0

View File

@ -0,0 +1 @@
1740015823106 0.9607692307692308 0

View File

@ -0,0 +1 @@
['计算效率高,支持并行计算。', '具有内置的缺失值处理能力。']

View File

@ -0,0 +1 @@
XGBClassifier

View File

@ -0,0 +1 @@
/home/admin-root/haotian/MLPlatform/dataset/dataset_processed/breast_cancer_20250219_144629

View File

@ -0,0 +1 @@
['参数较多,调优较复杂。']

View File

@ -0,0 +1 @@
XGBoostExtreme Gradient Boosting是一种基于梯度提升树GBDT的改进算法具有更强的正则化和并行处理能力。

View File

@ -0,0 +1 @@
classification

View File

@ -0,0 +1 @@
[{"run_id": "81b3390084a146bb949fec21c80dba2c", "artifact_path": "model", "utc_time_created": "2025-02-20 01:43:43.110167", "model_uuid": "770483f5da38405496fdedeef1cfe93f", "flavors": {"python_function": {"model_path": "model.pkl", "predict_fn": "predict", "loader_module": "mlflow.sklearn", "python_version": "3.9.19", "env": {"conda": "conda.yaml", "virtualenv": "python_env.yaml"}}, "sklearn": {"pickled_model": "model.pkl", "sklearn_version": "1.5.2", "serialization_format": "cloudpickle", "code": null}}}]

View File

@ -0,0 +1 @@
rebellious-fish-472

View File

@ -0,0 +1 @@
7b99da7251d23ceb9b3dfbb599e96dc00054e961

View File

@ -0,0 +1 @@
/home/admin-root/haotian/MLPlatform/example_model_trainer.py

View File

@ -0,0 +1 @@
admin-root

View File

@ -0,0 +1,104 @@
prediction
0
1
0
0
1
0
1
1
0
1
1
0
1
1
0
1
1
0
0
0
1
1
1
0
0
0
1
1
1
1
1
0
1
1
1
0
0
0
0
0
1
0
1
0
0
1
0
1
1
1
1
1
1
0
1
1
0
1
1
0
1
1
0
0
1
1
0
0
0
0
0
1
0
1
1
0
0
1
1
0
1
0
1
1
1
1
0
1
1
1
1
1
1
1
0
1
1
1
1
1
1
1
1
1 prediction
2 0
3 1
4 0
5 0
6 1
7 0
8 1
9 1
10 0
11 1
12 1
13 0
14 1
15 1
16 0
17 1
18 1
19 0
20 0
21 0
22 1
23 1
24 1
25 0
26 0
27 0
28 1
29 1
30 1
31 1
32 1
33 0
34 1
35 1
36 1
37 0
38 0
39 0
40 0
41 0
42 1
43 0
44 1
45 0
46 0
47 1
48 0
49 1
50 1
51 1
52 1
53 1
54 1
55 0
56 1
57 1
58 0
59 1
60 1
61 0
62 1
63 1
64 0
65 0
66 1
67 1
68 0
69 0
70 0
71 0
72 0
73 1
74 0
75 1
76 1
77 0
78 0
79 1
80 1
81 0
82 1
83 0
84 1
85 1
86 1
87 1
88 0
89 1
90 1
91 1
92 1
93 1
94 1
95 1
96 0
97 1
98 1
99 1
100 1
101 1
102 1
103 1
104 1

99
test_model_manager.py Normal file
View File

@ -0,0 +1,99 @@
import pytest
import pandas as pd
import numpy as np
import mlflow
from pathlib import Path
from function.model_manager import ModelManager
class TestModelManager:
@pytest.fixture
def model_manager(self):
return ModelManager()
@pytest.fixture
def sample_data(self):
# 创建测试数据
np.random.seed(42)
n_samples = 100
X = np.random.randn(n_samples, 4)
y = (X[:, 0] + X[:, 1] > 0).astype(int)
# 保存测试数据
data_dir = Path("dataset/dataset_processed/test_data")
data_dir.mkdir(parents=True, exist_ok=True)
df = pd.DataFrame(X, columns=[f'feature_{i}' for i in range(4)])
df['label'] = y
data_path = data_dir / "test_data.csv"
df.to_csv(data_path, index=False)
return str(data_path)
@pytest.fixture
def trained_model(self, sample_data):
# 训练一个简单的模型用于测试
from sklearn.ensemble import RandomForestClassifier
# 加载数据
data = pd.read_csv(sample_data)
X = data.drop('label', axis=1).values
y = data['label'].values
# 训练模型
model = RandomForestClassifier(n_estimators=10, random_state=42)
model.fit(X, y)
# 使用MLflow记录模型
with mlflow.start_run() as run:
mlflow.sklearn.log_model(model, "model")
mlflow.log_param("algorithm", "RandomForestClassifier")
return run.info.run_id
def test_predict(self, model_manager, sample_data, trained_model):
# 设置输出路径
output_dir = Path("predictions/test")
output_dir.mkdir(parents=True, exist_ok=True)
output_path = str(output_dir / "test_predictions.csv")
# 执行预测
result = model_manager.predict(
run_id=trained_model,
data_path=sample_data,
output_path=output_path,
metrics=['accuracy', 'f1']
)
# 验证结果
assert result['status'] == 'success'
assert 'prediction' in result
assert Path(result['prediction']['output_file']).exists()
assert result['prediction']['samples_count'] == 100
assert 'accuracy' in result['prediction']['metrics']
assert 'f1' in result['prediction']['metrics']
# 验证预测结果格式
predictions = pd.read_csv(output_path)
assert 'prediction' in predictions.columns
assert len(predictions) == 100
def test_predict_invalid_run_id(self, model_manager, sample_data):
result = model_manager.predict(
run_id="invalid_run_id",
data_path=sample_data,
output_path="predictions/test/invalid.csv"
)
assert result['status'] == 'error'
assert '未找到运行ID' in result['message']
def test_predict_invalid_data_path(self, model_manager, trained_model):
result = model_manager.predict(
run_id=trained_model,
data_path="invalid/path/data.csv",
output_path="predictions/test/invalid.csv"
)
assert result['status'] == 'error'
assert '数据加载失败' in result['message']