From 28348df1c34890acc384a60a7e38b97816f71285 Mon Sep 17 00:00:00 2001 From: haotian <2421912570@qq.com> Date: Mon, 24 Feb 2025 16:23:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9--=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/main.cpython-39.pyc | Bin 4565 -> 4565 bytes api/__pycache__/data_api.cpython-39.pyc | Bin 2750 -> 2767 bytes api/__pycache__/model_api.cpython-39.pyc | Bin 3713 -> 3713 bytes api/data_api.py | 12 +- api/model_api.py | 2 +- ...record__breast_cancer_20250224_162247.json | 81 ++++ .../train_breast_cancer_20250224_162247.csv | 359 ++++++++++++++++++ .../val_breast_cancer_20250224_162247.csv | 155 ++++++++ date_feature/parameter.yaml | 60 +-- doc/接口文档code.md | 10 +- doc/时间计划.md | 8 +- .../__pycache__/data_manager.cpython-39.pyc | Bin 14393 -> 14738 bytes .../__pycache__/model_manager.cpython-39.pyc | Bin 18940 -> 19306 bytes function/data_manager.py | 195 +++++----- function/model_manager.py | 264 +++++++------ .../artifacts/model/MLmodel | 20 + .../artifacts/model/conda.yaml | 15 + .../artifacts/model/model.pkl | Bin 0 -> 96534 bytes .../artifacts/model/python_env.yaml | 7 + .../artifacts/model/requirements.txt | 8 + .../meta.yaml | 15 + .../metrics/accuracy | 1 + .../metrics/f1 | 1 + .../metrics/precision | 1 + .../metrics/recall | 1 + .../metrics/roc_auc | 1 + .../params/advantages | 1 + .../params/algorithm | 1 + .../params/dataset | 1 + .../params/disadvantages | 1 + .../params/learning_rate | 1 + .../params/max_depth | 1 + .../params/n_estimators | 1 + .../params/principle | 1 + .../params/random_state | 1 + .../params/task_type | 1 + .../tags/mlflow.log-model.history | 1 + .../tags/mlflow.runName | 1 + .../tags/mlflow.source.git.commit | 1 + .../tags/mlflow.source.name | 1 + .../tags/mlflow.source.type | 1 + .../tags/mlflow.user | 1 + 42 files changed, 972 insertions(+), 260 deletions(-) create mode 100644 dataset/dataset_processed/breast_cancer_20250224_162247/process_record__breast_cancer_20250224_162247.json create mode 100644 dataset/dataset_processed/breast_cancer_20250224_162247/train_breast_cancer_20250224_162247.csv create mode 100644 dataset/dataset_processed/breast_cancer_20250224_162247/val_breast_cancer_20250224_162247.csv create mode 100644 mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/MLmodel create mode 100644 mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/conda.yaml create mode 100644 mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/model.pkl create mode 100644 mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/python_env.yaml create mode 100644 mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/requirements.txt create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/meta.yaml create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/metrics/accuracy create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/metrics/f1 create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/metrics/precision create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/metrics/recall create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/metrics/roc_auc create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/advantages create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/algorithm create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/dataset create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/disadvantages create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/learning_rate create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/max_depth create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/n_estimators create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/principle create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/random_state create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/params/task_type create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/tags/mlflow.log-model.history create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/tags/mlflow.runName create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/tags/mlflow.source.git.commit create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/tags/mlflow.source.name create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/tags/mlflow.source.type create mode 100644 mlruns/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/tags/mlflow.user diff --git a/__pycache__/main.cpython-39.pyc b/__pycache__/main.cpython-39.pyc index dc0be9f48256d9d227915ac83bf4cd6ca6d84747..9247c5bd979eac0486c0ddbcb0458e42050d80e5 100644 GIT binary patch delta 32 kcmcbrd{vn@k(ZZ?0SG2Y?n!^Tk$1l!ClePV2r~Tu0FDv|=l}o! delta 32 jcmcbrd{vn@k(ZZ?0SK%l_oVOK$h%*VlaUJynSKHQg%k&7 diff --git a/api/__pycache__/data_api.cpython-39.pyc b/api/__pycache__/data_api.cpython-39.pyc index 88acd5a5dd146b24cf8c271b91ef81f8401c86ea..dab3b928a97363ec062e9c91a706b6ddeaa22ad1 100644 GIT binary patch delta 392 zcmYLEF;2rk5cHny*m3L_1VN%82%$8IgrI^72?`p1;DYFcFV2O;KCLgJAXL;8$OZ2} zX%SzbpffMv4M-7dk-)8HZgo31t9fg^w*npno(~NG?A$gWRygW-z>uKCA(D=XY)kpjSNA_jk`K@n$798MurATewye=mvj*2gOJIxmKA{tGr7y Gbm0eJS!xad delta 358 zcmX>vx=)lhk(ZZ?0SJCS+MO=Ck@q&EQ(9(D>Mid4(vpJGlK6tel8jrt1x2X^Mfu68 z#l@L<>9>T_QWHx`i&EoL^U^c(Qd5gSg51RgIhiH#MTsSu`J2_4WLXXDfTor(WHHt- zWHA9TGZ3=?F)I+Wr7-rg)-u*GrZ7n|q%dbQ6mJNXIcbT*KYKxQMCH#08>BLHUdVCw(? diff --git a/api/__pycache__/model_api.cpython-39.pyc b/api/__pycache__/model_api.cpython-39.pyc index 5186e8a2b6db6a4bb4adb5e166693c0020f194b4..2187b99c5e9781c87f0b2643061998869c2b7722 100644 GIT binary patch delta 62 zcmZpaZItCrd@D$tQSp*>16b PBqp!pk=iW6o686QCjApm delta 58 zcmZpaZItCruVfU6u)d}tHeg+hy85-C0)2Jf;aYMahX zt-DNGFtPQ6t&7J-F|BN%h6-9+S1m1VEh<8_Sg3*!gnlub_(c#c_`t_GlXYGH1ZSDw z{O&pTo_l`h+`H$eUN-&XK3@~V=VtO^{yA~hp9n@{I3(Lve9dbTVS z4e)Ov8`^tY$<`)Ac0jBbtw$uzY@bN%<0WpKg6pA{;VZTuy`j2St9fr&VLLFxcT1wJ zzM_h@V9n;Q&HD|K;W0i6hr=63cd-#BGb>h-3ZHQ{9pS8~i%wh=nc?&7gftzs*`!)- zv7-x|Gtp&7MfY)O7gN|nOeCfw2O=Z2ug-L2BP^A!RKi}lr z=i9}&HE!mt!FFR9<)Rs@KN;Bi%i>Y9`iFKlj5C6`qZwmgoi5cbpAa^0FiMBq_<1CPRv z&I7Ht6cn&}^xX1GbGJ04)w%Q4%CXgV7ppVR!_`g!@9A;Kb`9L&{kI6lizK>>ps@z8 zbv>i3plWk7wD)#MQUo@2=ae~|R@{z#(f`{M8GGWc=dTt@n7yl^Ckpn&)zA~w@`5{k zeM1y4k9ijcW!QXKj&T`(cnrAohlgtaWBS4WL+x$7E&2gf@I!(rf<=M}IIVXiE)l&< zK$pUJo8TjYD-epWgP-)IlA$^@RQh&!toJ)U4%y6hegTeV-U+|JCVgeU@KEnV$PmmU6yajuV8_1oNY_rs^2yni(xK%yFRi>jTRnEB zdgS!?^M_Ia_@(awwRQ|(YkwdA6887+Z2jV&U`x&UoHbVa2Y7;VPQcCngv2%YwSO`C rG0ppg;8Oz1&G;JLzi(7c2h;wxU{58m=>~7UM)nSZUZ((m4GjJb{n7bzK!rQ;DFUqOy>vDB+srgV^pQDN_>m z&<_La2tjGA5I*%2F_V6f_0SJO!3ZLJ2q~hM9(w2{qVCOgvHzgC+z;n`@0@$j{cJz>BcHJoln`t*-a;Hrvliat7W3G^kmKnxophGZ z{1+(@M`L-%VHlz}9F6NnQr8puBS+`DnbbWAea`ke>4VTsSwV7-C9W9>ZKeJP*UV*& z6^^>F&bt$K;014~)Jm*%lS)R4M6GOW%9*BCCQ{#Zyz48LdwKuFhl@UYa)0hmO)QWQvsGWCHlQD zFfH&vU|DTzc zDhslDgfSBWM+KIB#pjhpMvKUA3Op8gg1;;4<$$>5ig|^2I{XRt;^C^za2xMb-QrJ# zzh4!*BfGViyvU-_E28!V&PQ?~ieDr3)Y5QM^>!Sq4#69otKM4kO8gP#_>k7wVIO4e zc6+SDJ|qtEag~5fJyZpQsxFE diff --git a/function/__pycache__/model_manager.cpython-39.pyc b/function/__pycache__/model_manager.cpython-39.pyc index 30dc1d68bdf2f014d943de8e671adccd3697fc34..c96ae0d2313e6a9e68cf9e2bf6aa807bbbccbb1b 100644 GIT binary patch delta 5442 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 delta 4889 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 KWQ36w;r{^}!8dyV diff --git a/function/data_manager.py b/function/data_manager.py index c858d08..dd1e094 100644 --- a/function/data_manager.py +++ b/function/data_manager.py @@ -170,107 +170,107 @@ class DataManager: Returns: 处理结果字典 """ - # try: + try: - # 数据集名 - file_name = input_path.split("/")[-1].split(".")[0] + # 数据集名 + file_name = input_path.split("/")[-1].split(".")[0] - # 生成时间戳 - timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + # 生成时间戳 + timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') - # 创建输出目录 - output_path = Path(output_dir+'/'+file_name+'_'+timestamp) - output_path.mkdir(parents=True, exist_ok=True) - - # 记录处理过程 - process_record = { - 'input_file': input_path, - 'timestamp': datetime.datetime.now().isoformat(), - 'process_methods': process_methods, - 'feature_methods': feature_methods, - 'split_params': split_params, - 'steps': [] - } - - # 读取数据 - self.logger.info(f"Loading data from {input_path}") - df = pd.read_csv(input_path) - process_record['steps'].append({ - 'step': 'load_data', - 'shape': df.shape - }) - - - - # 数据预处理 - for method in process_methods: - df = self._apply_process_methods(df, method) + # 创建输出目录 + output_path = Path(output_dir+'/'+file_name+'_'+timestamp) + output_path.mkdir(parents=True, exist_ok=True) + + # 记录处理过程 + process_record = { + 'input_file': input_path, + 'timestamp': datetime.datetime.now().isoformat(), + 'process_methods': process_methods, + 'feature_methods': feature_methods, + 'split_params': split_params, + 'steps': [] + } + + # 读取数据 + self.logger.info(f"Loading data from {input_path}") + df = pd.read_csv(input_path) process_record['steps'].append({ - 'step': 'cleaning', - 'method': method['method_name'], - 'params': method['params'], + 'step': 'load_data', 'shape': df.shape }) - - # 特征工程 - for method in feature_methods: - df = self._apply_feature_method(df, method) - process_record['steps'].append({ - 'step': 'feature_engineering', - 'method': method['method_name'], - 'params': method['params'], - 'shape': df.shape - }) - - # 数据集划分 - train_data, val_data, test_data = self._split_dataset( - df, - test_size=split_params.get('test_size', 0), - val_size=split_params.get('val_size', 0) - ) - - - - # 保存处理后的数据集 - train_path = output_path / f'train_{file_name}_{timestamp}.csv' - val_path = output_path / f'val_{file_name}_{timestamp}.csv' - test_path = output_path / f'test_{file_name}_{timestamp}.csv' - - if train_data is not None: - train_data.to_csv(train_path, index=False) - if val_data is not None: - val_data.to_csv(val_path, index=False) - if test_data is not None: - test_data.to_csv(test_path, index=False) - - # 记录输出文件路径 - process_record['output_files'] = { - 'train': str(train_path) if train_data is not None else "", - 'validation': str(val_path) if val_data is not None else "", - 'test': str(test_path) if test_data is not None else "" - } - - # 保存处理记录 - record_path = output_path / f'process_record__{file_name}_{timestamp}.json' - with open(record_path, 'w', encoding='utf-8') as f: - json.dump(process_record, f, indent=2, ensure_ascii=False) - self.logger.info(f"Data processing completed. Results saved to {output_path}") - - return { - 'status': 'success', - 'message': 'Data processing completed successfully', - 'process_record': process_record - } - - # except Exception as e: - # error_msg = f"Error processing dataset: {str(e)}" - # self.logger.error(error_msg) - # return { - # 'status': 'error', - # 'message': error_msg - # } + + # 数据预处理 + for method in process_methods: + df = self._apply_process_methods(df, method) + process_record['steps'].append({ + 'step': 'cleaning', + 'method': method['method_name'], + 'params': method['params'], + 'shape': df.shape + }) + + # 特征工程 + for method in feature_methods: + df = self._apply_feature_method(df, method) + process_record['steps'].append({ + 'step': 'feature_engineering', + 'method': method['method_name'], + 'params': method['params'], + 'shape': df.shape + }) + + # 数据集划分 + train_data, val_data, test_data = self._split_dataset( + df, + test_size=split_params.get('test_size', 0), + val_size=split_params.get('val_size', 0) + ) + + + + # 保存处理后的数据集 + train_path = output_path / f'train_{file_name}_{timestamp}.csv' + val_path = output_path / f'val_{file_name}_{timestamp}.csv' + test_path = output_path / f'test_{file_name}_{timestamp}.csv' + + if train_data is not None: + train_data.to_csv(train_path, index=False) + + if val_data is not None: + val_data.to_csv(val_path, index=False) + if test_data is not None: + test_data.to_csv(test_path, index=False) + + # 记录输出文件路径 + process_record['output_files'] = { + 'train': str(train_path) if train_data is not None else "", + 'validation': str(val_path) if val_data is not None else "", + 'test': str(test_path) if test_data is not None else "" + } + + # 保存处理记录 + record_path = output_path / f'process_record__{file_name}_{timestamp}.json' + with open(record_path, 'w', encoding='utf-8') as f: + json.dump(process_record, f, indent=2, ensure_ascii=False) + + self.logger.info(f"Data processing completed. Results saved to {output_path}") + + return { + 'status': 'success', + 'message': 'Data processing completed successfully', + 'process_record': process_record + } + + except Exception as e: + error_msg = f"Error processing dataset: {str(e)}" + self.logger.error(error_msg) + return { + 'status': 'error', + 'message': error_msg + } def _apply_process_methods(self, df: pd.DataFrame, method: Dict) -> pd.DataFrame: """应用数据预处理方法""" @@ -470,6 +470,8 @@ class DataManager: "method": outlier_methods }) + self.logger.info("获取预处理方法列表") + return { "status": "success", "methods": methods @@ -504,6 +506,8 @@ class DataManager: if parameter_info is None: raise ValueError(f"Method {method_name} not found in parameter config") + + self.logger.info(f"获取{method_name}方法详情") # 组合返回信息 return { @@ -540,6 +544,8 @@ class DataManager: "description": "特征工程方法", "method": feature_engineering_methods }) + + self.logger.info("获取特征工程方法列表") return { "status": "success", @@ -575,6 +581,8 @@ class DataManager: if parameter_info is None: raise ValueError(f"Method {method_name} not found in parameter config") + + self.logger.info(f'获取{method_name}方法详情') # 组合返回信息 return { @@ -628,6 +636,7 @@ class DataManager: # json_data = json.load(f ,allow_nan=True) back.append(json_data) + self.logger.info("获取处理好的数据集") # print("可用数据集", back) return back \ No newline at end of file diff --git a/function/model_manager.py b/function/model_manager.py index 9137c98..0b4fa58 100644 --- a/function/model_manager.py +++ b/function/model_manager.py @@ -32,7 +32,7 @@ class ModelManager: self.logger = logging.getLogger(__name__) self._setup_logging() self._metrics_map() - self.method_config = self._load_metrics() + # self.method_config = self._load_metrics() self.method_config = self._load_model_config() self.parameter_config = self._load_parameter_config() @@ -43,20 +43,20 @@ class ModelManager: self.client = MlflowClient() def _load_metrics(self) -> Dict: - """加载方法配置文件""" + """加载模型评价指标配置文件""" try: config_path = Path('model/metrics.yaml') if not config_path.exists(): - raise FileNotFoundError(f"Method config file not found at {config_path}") + raise FileNotFoundError(f"Metrics config file not found at {config_path}") with open(config_path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) - self.logger.info("Successfully loaded method config") + self.logger.info("Successfully loaded metrics config") return config except Exception as e: - self.logger.error(f"Error loading method config: {str(e)}") + self.logger.error(f"Error loading metrics config: {str(e)}") raise def _setup_logging(self): @@ -78,7 +78,7 @@ class ModelManager: try: config_path = Path('model/model.yaml') if not config_path.exists(): - raise FileNotFoundError(f"Method config file not found at {config_path}") + raise FileNotFoundError(f"Model config file not found at {config_path}") with open(config_path, 'r', encoding='utf-8') as f: config_model = yaml.safe_load(f) @@ -315,7 +315,9 @@ class ModelManager: 'model_path': f"models/{run['run_id']}" } models.append(model_info) - + + self.logger.info("获取已训练完成的模型列表") + return { 'status': 'success', 'models': models, @@ -392,7 +394,8 @@ class ModelManager: 'runs_count': len(runs) } experiment_list.append(experiment_info) - + + self.logger.info("获取保存的实验列表") return { 'status': 'success', 'experiments': experiment_list, @@ -490,125 +493,133 @@ class ModelManager: 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 + 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: - has_labels = False - X = data.values - except Exception as e: + # 其他模型预测 + 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': 'error', - 'message': '数据加载失败', - 'details': { - 'error_type': type(e).__name__, - 'error_message': str(e) + '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" } } - - # 创建预测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: + self.logger.error(f"Error model predict: {str(e)}") + return { + "status": "error", + "error": str(e) } - } + + def get_models(self) -> Dict: - """获取预处理方法列表""" + """获取机器/深度学习方法列表""" try: models = [] @@ -639,20 +650,23 @@ class ModelManager: "method": clustering_algorithms }) + + self.logger.info("获取机器/深度学习方法列表") + return { "status": "success", "models": models } except Exception as e: - self.logger.error(f"Error getting preprocessing methods: {str(e)}") + self.logger.error(f"Error get models: {str(e)}") return { "status": "error", "error": str(e) } def get_model_details(self, method_name: str) -> Dict: - """获取指定方法的详细信息""" + """获取指定算法的详细信息""" try: # 在各个方法类别中查找方法原理和优缺点 method_info = None @@ -673,6 +687,8 @@ class ModelManager: if parameter_info is None: raise ValueError(f"Method {method_name} not found in parameter config") + + self.logger.info(f"获取{method_name}算法的详细信息") # 组合返回信息 return { @@ -709,7 +725,7 @@ class ModelManager: # } def get_metrics(self) -> Dict: - """获取预处理方法列表""" + """获取评价指标列表""" try: metrics = [] @@ -739,6 +755,8 @@ class ModelManager: "description": "聚类方法评价指标", "metric": clustering_metrics }) + + self.logger.info("获取评价指标列表") return { "status": "success", @@ -746,7 +764,7 @@ class ModelManager: } except Exception as e: - self.logger.error(f"Error getting preprocessing methods: {str(e)}") + self.logger.error(f"Error get metrics: {str(e)}") return { "status": "error", "error": str(e) diff --git a/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/MLmodel b/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/MLmodel new file mode 100644 index 0000000..f5fbfb6 --- /dev/null +++ b/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/MLmodel @@ -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: 23e95e8b8e914cca8891e8fdad912a41 +run_id: cb753c74fbd54866a62e26bc8e17927e +utc_time_created: '2025-02-24 06:16:39.483600' diff --git a/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/conda.yaml b/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/conda.yaml new file mode 100644 index 0000000..306a2fe --- /dev/null +++ b/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/conda.yaml @@ -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 diff --git a/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/model.pkl b/mlartifacts/433321862082712659/cb753c74fbd54866a62e26bc8e17927e/artifacts/model/model.pkl new file mode 100644 index 0000000000000000000000000000000000000000..77a11d53cccdc98a598e8f34458de8dc0f1c2c42 GIT binary patch literal 96534 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