Skip to content

Commit fe3464c

Browse files
committed
feat(neural_network): add checkpoint file cleanup to prevent disk space accumulation
1 parent c9bd940 commit fe3464c

1 file changed

Lines changed: 134 additions & 7 deletions

File tree

spx-algorithm/services/reranking/neural_network.py

Lines changed: 134 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
1313
from pathlib import Path
1414
from datetime import datetime
15+
import glob
16+
import os
1517

1618
logger = logging.getLogger(__name__)
1719

@@ -96,24 +98,26 @@ def get_model_info(self) -> Dict[str, Any]:
9698
class NeuralNetworkTrainer:
9799
"""神经网络训练器"""
98100

99-
def __init__(self, model_save_path: str = "models/neural_rerank_model.pth", max_history_size: int = 100):
101+
def __init__(self, model_save_path: str = "models/neural_rerank_model.pth", max_history_size: int = 100, max_checkpoints: int = 3):
100102
"""
101103
初始化训练器
102104
103105
Args:
104106
model_save_path: 模型保存路径
105107
max_history_size: 训练历史记录最大保存数量
108+
max_checkpoints: 最大检查点文件保留数量
106109
"""
107110
self.model_save_path = model_save_path
108111
self.model = None
109112
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
110113
self.training_history = []
111114
self.max_history_size = max_history_size
115+
self.max_checkpoints = max_checkpoints
112116

113117
# 确保模型目录存在
114118
Path(self.model_save_path).parent.mkdir(parents=True, exist_ok=True)
115119

116-
logger.info(f"神经网络训练器初始化完成,设备: {self.device}, 模型保存路径: {model_save_path}")
120+
logger.info(f"神经网络训练器初始化完成,设备: {self.device}, 模型保存路径: {model_save_path}, 最大检查点保留数: {max_checkpoints}")
117121

118122
def train_model(self,
119123
features: List[List[float]],
@@ -245,6 +249,9 @@ def train_model(self,
245249
# 加载最佳模型
246250
self._load_checkpoint()
247251

252+
# 训练完成后清理检查点文件
253+
self._cleanup_checkpoints()
254+
248255
# 评估模型
249256
train_metrics = self._evaluate_model(X_train, y_train, "训练集")
250257
val_metrics = self._evaluate_model(X_val, y_val, "验证集")
@@ -318,15 +325,21 @@ def _calculate_auc(self, y_true: np.ndarray, y_scores: np.ndarray) -> float:
318325
def _save_checkpoint(self):
319326
"""保存检查点"""
320327
if self.model is not None:
321-
checkpoint_path = self.model_save_path.replace('.pth', '_checkpoint.pth')
328+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
329+
checkpoint_path = self.model_save_path.replace('.pth', f'_checkpoint_{timestamp}.pth')
322330
torch.save(self.model.state_dict(), checkpoint_path)
331+
332+
# 清理旧的检查点文件
333+
self._cleanup_checkpoints()
323334

324335
def _load_checkpoint(self):
325-
"""加载检查点"""
336+
"""加载最新的检查点"""
326337
if self.model is not None:
327-
checkpoint_path = self.model_save_path.replace('.pth', '_checkpoint.pth')
328-
if Path(checkpoint_path).exists():
329-
self.model.load_state_dict(torch.load(checkpoint_path, map_location=self.device))
338+
# 查找最新的检查点文件
339+
latest_checkpoint = self._find_latest_checkpoint()
340+
if latest_checkpoint and Path(latest_checkpoint).exists():
341+
self.model.load_state_dict(torch.load(latest_checkpoint, map_location=self.device))
342+
logger.debug(f"已加载检查点: {latest_checkpoint}")
330343

331344
def _save_model(self):
332345
"""保存模型"""
@@ -384,6 +397,9 @@ def load_model(self, model_path: Optional[str] = None) -> bool:
384397
self.training_history = model_data.get('training_history', [])
385398
self._cleanup_training_history()
386399

400+
# 加载模型后清理旧的检查点文件
401+
self._cleanup_checkpoints()
402+
387403
logger.info(f"模型加载成功: {path}")
388404
return True
389405

@@ -531,4 +547,115 @@ def retrain_with_new_data(self, features: List[List[float]], labels: List[int],
531547
except Exception as e:
532548
logger.error(f"增量训练失败: {e}")
533549
raise
550+
551+
def _find_latest_checkpoint(self) -> Optional[str]:
552+
"""
553+
查找最新的检查点文件
554+
555+
Returns:
556+
最新检查点文件的路径,如果没有则返回None
557+
"""
558+
try:
559+
model_dir = Path(self.model_save_path).parent
560+
model_name = Path(self.model_save_path).stem
561+
562+
# 查找所有检查点文件
563+
checkpoint_pattern = str(model_dir / f"{model_name}_checkpoint_*.pth")
564+
checkpoint_files = glob.glob(checkpoint_pattern)
565+
566+
if not checkpoint_files:
567+
return None
568+
569+
# 按时间戳排序,返回最新的
570+
checkpoint_files.sort()
571+
return checkpoint_files[-1]
572+
573+
except Exception as e:
574+
logger.error(f"查找最新检查点失败: {e}")
575+
return None
576+
577+
def _cleanup_checkpoints(self):
578+
"""
579+
清理旧的检查点文件,只保留最近的N个检查点
580+
"""
581+
try:
582+
model_dir = Path(self.model_save_path).parent
583+
model_name = Path(self.model_save_path).stem
584+
585+
# 查找所有检查点文件
586+
checkpoint_pattern = str(model_dir / f"{model_name}_checkpoint_*.pth")
587+
checkpoint_files = glob.glob(checkpoint_pattern)
588+
589+
if len(checkpoint_files) <= self.max_checkpoints:
590+
return
591+
592+
# 按修改时间排序
593+
checkpoint_files.sort(key=lambda x: os.path.getmtime(x))
594+
595+
# 删除多余的旧检查点文件
596+
files_to_delete = checkpoint_files[:-self.max_checkpoints]
597+
deleted_count = 0
598+
599+
for file_path in files_to_delete:
600+
try:
601+
os.remove(file_path)
602+
deleted_count += 1
603+
logger.debug(f"删除旧检查点文件: {file_path}")
604+
except Exception as e:
605+
logger.warning(f"删除检查点文件失败: {file_path}, 错误: {e}")
606+
607+
if deleted_count > 0:
608+
logger.info(f"检查点清理完成:删除了 {deleted_count} 个旧文件,保留最近 {len(checkpoint_files) - deleted_count} 个检查点")
609+
610+
except Exception as e:
611+
logger.error(f"检查点清理失败: {e}")
612+
613+
def get_checkpoint_info(self) -> Dict[str, Any]:
614+
"""
615+
获取检查点文件信息
616+
617+
Returns:
618+
检查点信息字典
619+
"""
620+
try:
621+
model_dir = Path(self.model_save_path).parent
622+
model_name = Path(self.model_save_path).stem
623+
624+
# 查找所有检查点文件
625+
checkpoint_pattern = str(model_dir / f"{model_name}_checkpoint_*.pth")
626+
checkpoint_files = glob.glob(checkpoint_pattern)
627+
628+
checkpoint_info = []
629+
total_size = 0
630+
631+
for file_path in checkpoint_files:
632+
file_stats = os.stat(file_path)
633+
checkpoint_info.append({
634+
'path': file_path,
635+
'size_mb': file_stats.st_size / (1024 * 1024),
636+
'modified_time': datetime.fromtimestamp(file_stats.st_mtime).isoformat()
637+
})
638+
total_size += file_stats.st_size
639+
640+
# 按修改时间排序
641+
checkpoint_info.sort(key=lambda x: x['modified_time'])
642+
643+
return {
644+
'checkpoint_count': len(checkpoint_files),
645+
'max_checkpoints': self.max_checkpoints,
646+
'total_size_mb': total_size / (1024 * 1024),
647+
'checkpoints': checkpoint_info,
648+
'latest_checkpoint': checkpoint_info[-1]['path'] if checkpoint_info else None
649+
}
650+
651+
except Exception as e:
652+
logger.error(f"获取检查点信息失败: {e}")
653+
return {
654+
'checkpoint_count': 0,
655+
'max_checkpoints': self.max_checkpoints,
656+
'total_size_mb': 0,
657+
'checkpoints': [],
658+
'latest_checkpoint': None,
659+
'error': str(e)
660+
}
534661

0 commit comments

Comments
 (0)