File size: 6,958 Bytes
1e05592 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | """
模型检查点管理
处理模型的保存、加载和版本管理
"""
import torch
from pathlib import Path
from typing import Dict, Optional, Any
import json
from datetime import datetime
class CheckpointManager:
"""
检查点管理器
自动管理模型保存、加载和最佳模型跟踪
"""
def __init__(
self,
checkpoint_dir: str,
max_keep: int = 5,
metric_mode: str = 'min'
):
"""
Args:
checkpoint_dir: 检查点保存目录
max_keep: 最多保留的检查点数量
metric_mode: 指标模式 ('min' 或 'max')
"""
self.checkpoint_dir = Path(checkpoint_dir)
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
self.max_keep = max_keep
self.metric_mode = metric_mode
self.checkpoints = [] # [(path, metric_value), ...]
self.best_metric = float('inf') if metric_mode == 'min' else float('-inf')
self.best_checkpoint = None
# 加载已有检查点信息
self._load_checkpoint_info()
def save(
self,
model: torch.nn.Module,
optimizer: torch.optim.Optimizer,
epoch: int,
metric_value: float,
extra_info: Optional[Dict] = None
) -> Path:
"""
保存检查点
Args:
model: 模型
optimizer: 优化器
epoch: 当前epoch
metric_value: 验证指标值
extra_info: 额外信息
Returns:
保存的文件路径
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"checkpoint_epoch{epoch}_{timestamp}.pt"
filepath = self.checkpoint_dir / filename
# 准备保存内容
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'metric_value': metric_value,
'timestamp': timestamp
}
if extra_info:
checkpoint.update(extra_info)
# 保存
torch.save(checkpoint, filepath)
# 更新检查点列表
self.checkpoints.append((filepath, metric_value))
# 检查是否是最佳模型
is_best = self._is_best(metric_value)
if is_best:
self.best_metric = metric_value
self.best_checkpoint = filepath
# 保存最佳模型的副本
best_path = self.checkpoint_dir / "best_model.pt"
torch.save(checkpoint, best_path)
print(f"✨ New best model saved! Metric: {metric_value:.4f}")
# 清理旧检查点
self._cleanup()
# 保存检查点信息
self._save_checkpoint_info()
return filepath
def load(
self,
model: torch.nn.Module,
optimizer: Optional[torch.optim.Optimizer] = None,
checkpoint_path: Optional[str] = None,
load_best: bool = False
) -> Dict:
"""
加载检查点
Args:
model: 模型
optimizer: 优化器(可选)
checkpoint_path: 检查点路径(可选,不指定则加载最新)
load_best: 是否加载最佳模型
Returns:
检查点字典
"""
if load_best:
filepath = self.checkpoint_dir / "best_model.pt"
elif checkpoint_path:
filepath = Path(checkpoint_path)
else:
# 加载最新检查点
if not self.checkpoints:
raise ValueError("No checkpoints found!")
filepath = self.checkpoints[-1][0]
if not filepath.exists():
raise FileNotFoundError(f"Checkpoint not found: {filepath}")
print(f"Loading checkpoint from {filepath}")
checkpoint = torch.load(filepath, map_location='cpu')
# 加载模型权重
model.load_state_dict(checkpoint['model_state_dict'])
# 加载优化器状态
if optimizer and 'optimizer_state_dict' in checkpoint:
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
print(f"Loaded checkpoint from epoch {checkpoint.get('epoch', 'unknown')}")
print(f"Metric value: {checkpoint.get('metric_value', 'N/A')}")
return checkpoint
def _is_best(self, metric_value: float) -> bool:
"""判断是否是最佳模型"""
if self.metric_mode == 'min':
return metric_value < self.best_metric
else:
return metric_value > self.best_metric
def _cleanup(self):
"""清理旧检查点,只保留最新的max_keep个"""
if len(self.checkpoints) <= self.max_keep:
return
# 按指标排序
sorted_checkpoints = sorted(
self.checkpoints,
key=lambda x: x[1],
reverse=(self.metric_mode == 'max')
)
# 保留最好的max_keep个
keep_checkpoints = sorted_checkpoints[:self.max_keep]
remove_checkpoints = [
cp for cp in self.checkpoints if cp not in keep_checkpoints
]
# 删除多余的文件(除了best_model.pt)
for filepath, _ in remove_checkpoints:
if filepath.exists() and filepath.name != "best_model.pt":
filepath.unlink()
print(f"Removed old checkpoint: {filepath.name}")
self.checkpoints = keep_checkpoints
def _save_checkpoint_info(self):
"""保存检查点元信息"""
info = {
'checkpoints': [
{'path': str(cp[0]), 'metric': cp[1]}
for cp in self.checkpoints
],
'best_checkpoint': str(self.best_checkpoint) if self.best_checkpoint else None,
'best_metric': self.best_metric,
'metric_mode': self.metric_mode
}
info_file = self.checkpoint_dir / "checkpoint_info.json"
with open(info_file, 'w') as f:
json.dump(info, f, indent=2)
def _load_checkpoint_info(self):
"""加载检查点元信息"""
info_file = self.checkpoint_dir / "checkpoint_info.json"
if not info_file.exists():
return
with open(info_file, 'r') as f:
info = json.load(f)
self.checkpoints = [
(Path(cp['path']), cp['metric'])
for cp in info['checkpoints']
if Path(cp['path']).exists()
]
if info['best_checkpoint'] and Path(info['best_checkpoint']).exists():
self.best_checkpoint = Path(info['best_checkpoint'])
self.best_metric = info['best_metric'] |