CURRENT_BONUS(p)

本文介绍了一个用于计算进程红利的公式:CURRENT_BONUS(p),该公式基于进程p的平均睡眠时间(sleep_avg)进行计算。具体计算方式为将进程的平均睡眠时间转换为jiffies单位后乘以最大红利再除以最大平均睡眠时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CURRENT_BONUS(p)根据sleep_avg计算进程p的当前红利(bonus)

#define CURRENT_BONUS(p)
    ( NS_TO_JIFFIES((p)->sleep_avg) * MAX_BONUS / MAX_SLEEP_AVG )

例如:
p->sleep_avg = 600ms
CURRENT_BONUS(p) = 6
----------------------------------
CURRENT_BONUS(p) --> NS_TO_JIFFIES(600 000 000ns) * 10 / 1000
--> 600 * 10 /1000 --> 6



# ============= 训练参数 ============= num_epochs_rl: int = 20 # RL训练总轮数 lr: float = 1e-3 # 初始学习率 batch_size: int = 12 # 批次大小 seed = 2025 num_epochs = 20 num_workers = 12 freeze_layers = ['vfe', 'map_to_bev', 'backbone_2d'] sync_bn = True warm_up = 2 # epoch # ============= PPO参数 ============= clip_param: float = 0.3 # PPO裁剪参数 ppo_epochs: int = 5 # 每次经验收集后的PPO更新轮数,不宜过大 gamma: float = 0.95 # 折扣因子 tau: float = 0.90 # GAE参数 value_coef: float = 0.7 # 值函数损失权重 entropy_coef: float = 0.05 # 熵正则化权重 max_grad_norm: float = 1.0 # 梯度裁剪阈值 import torch import torch.nn as nn from torch.utils.tensorboard import SummaryWriter import torch.optim as optim from torch.distributions import Normal import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP import copy import random import numpy as np from tqdm import tqdm from collections import deque from rl_seg.utils.compute_miou import get_miou, fast_hist, fast_hist_crop from rl_seg.datasets import load_data_to_gpu # 经验回放缓冲区 # 经验回放缓冲区 class ReplayBuffer: def __init__(self, capacity=100): self.buffer = deque(maxlen=capacity) def add(self, experience): """添加经验到缓冲区""" self.buffer.append(experience) def sample(self, batch_size): """从缓冲区随机采样一批经验""" return random.sample(self.buffer, min(batch_size, len(self.buffer))) def clear(self): """清空缓冲区""" self.buffer.clear() def __len__(self): return len(self.buffer) # PPO 代理(Actor-Critic 网络) class PPOAgent(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim=512): super(PPOAgent, self).__init__() self.state_dim = state_dim self.action_dim = action_dim # 共享特征提取层 self.shared_layers = nn.Sequential( nn.Linear(state_dim, hidden_dim), # nn.ReLU(), nn.LayerNorm(hidden_dim), nn.GELU(), nn.Linear(hidden_dim, hidden_dim), # nn.ReLU() nn.LayerNorm(hidden_dim), nn.GELU(), ) # Actor 网络 (策略) self.actor = nn.Sequential( # nn.Linear(hidden_dim, hidden_dim), # # nn.ReLU(), # nn.GELU(), nn.Linear(hidden_dim, action_dim), nn.Tanh() # 输出在[-1,1]范围内 ) # Critic 网络 (值函数) self.critic = nn.Sequential( nn.Linear(hidden_dim, hidden_dim), # nn.ReLU(), nn.GELU(), nn.Linear(hidden_dim, 1) ) # 动作标准差 (可学习参数) self.log_std = nn.Parameter(torch.zeros(1, action_dim)) # 初始化权重 self.apply(self._init_weights) def _init_weights(self, module): """初始化网络权重""" if isinstance(module, nn.Linear): nn.init.orthogonal_(module.weight, gain=0.01) nn.init.constant_(module.bias, 0.0) def forward(self, state): features = self.shared_layers(state) action_mean = self.actor(features) value = self.critic(features) return action_mean, value.squeeze(-1) def act(self, state): """与环境交互时选择动作""" # state = torch.FloatTensor(state).unsqueeze(0).to(device) # 确保是 [1, state_dim] with torch.no_grad(): action_mean, value = self.forward(state) # 创建动作分布 (添加最小标准差确保稳定性) action_std = torch.clamp(self.log_std.exp(), min=0.01, max=0.5) dist = Normal(action_mean, action_std) # 采样动作 action = dist.sample() # [B, action_dim] log_prob = dist.log_prob(action).sum(-1) return action, log_prob, value def evaluate(self, state, action): """评估动作的概率和值""" action_mean, value = self.forward(state) # 创建动作分布 action_std = torch.clamp(self.log_std.exp(), min=0.01, max=0.5) dist = Normal(action_mean, action_std) # 计算对数概率和熵 log_prob = dist.log_prob(action).sum(-1) entropy = dist.entropy().sum(-1) return log_prob, entropy, value # 强化学习优化器 IbMggIlv class PPOTrainer: """PPO训练器,整合了策略优化和模型微调""" def __init__(self, seg_net, agent, cfg): """ Args: seg_net: 预训练的分割网络 agent: PPO智能体 cfg: 配置对象,包含以下属性: - lr: 学习率 - clip_param: PPO裁剪参数 - ppo_epochs: PPO更新轮数 - gamma: 折扣因子 - tau: GAE参数 - value_coef: 值函数损失权重 - entropy_coef: 熵正则化权重 - max_grad_norm: 梯度裁剪阈值 """ self.seg_net = seg_net self._base_seg_net = seg_net.module if isinstance(seg_net, DDP) else seg_net self._base_seg_net.device = self.seg_net.device self.agent = agent self.cfg = cfg self.writer = SummaryWriter(log_dir=f'{cfg.exp_dir}/runs/ppo_trainer') if cfg.local_rank == 0 else None # 使用分离的优化器 self.optimizer_seg = optim.AdamW( self.seg_net.parameters(), lr=cfg.lr, weight_decay=1e-4 ) self.optimizer_agent = optim.AdamW( self.agent.parameters(), lr=cfg.lr*0.1, weight_decay=1e-4 ) # 训练记录 self.best_miou = 0.0 self.step_count = 0.0 self.metrics = { 'loss': [], 'reward': [], 'miou': [], 'class_ious': [], 'lr': [] } # 梯度缩放因子 self.neck_grad_scale = 1.0 self.head_grad_scale = 1.0 self.current_lr = cfg.lr # 当前学习率 self.class_weights = torch.ones(21).to(seg_net.device) # 初始类别权重 # 经验回放缓冲区 self.replay_buffer = ReplayBuffer(capacity=50) def compute_state(self, features, pred, gt_seg, epoch_progress): """ 计算强化学习状态向量 Args: features: 从extract_features获取的字典包含: - spatial_features: [B, C1, H, W] - bev_features: [B, C2, H, W] - neck_features: [B, C3, H, W] pred: 网络预测的分割结果 [B, num_classes, H, W] gt_seg: 真实分割标签 [B, H, W] Returns: state: 状态向量 [state_dim] """ # 主要使用neck_features作为代表特征 torch.Size([4, 64, 496, 432]) feats = features["neck_features"] # [B, C, H, W] B, C, H, W = feats.shape # 初始化状态列表 states = [] # 为批次中每个样本单独计算状态 for i in range(B): # 特征统计 feat_mean = feats[i].mean(dim=(1, 2)) # [C] feat_std = feats[i].std(dim=(1, 2)) # [C] feat_max = feats[i].max(dim=1)[0].mean(dim=1)# [C] feat_min = feats[i].min(dim=1)[0].mean(dim=1)# [C] # 预测类别分布 pred_classes = pred[i].argmax(dim=0) # [H, W] class_dist = torch.bincount( pred_classes.flatten(), minlength=21 ).float() / (H * W) # 21 # 预测置信度统计 pred_probs = torch.softmax(pred[i], dim=0) confidence = pred_probs.max(dim=0)[0] # 最大类别概率 conf_mean = confidence.mean() conf_std = confidence.std() conf_stats = torch.tensor([ confidence.mean(), confidence.std(), (confidence < 0.5).float().mean() # 低置信度像素比例 ], device=feats.device) # 3 gt_grid_ind = gt_seg["grid_ind"][i] gt_labels_ori = gt_seg["labels_ori"][i] # 各类IoU (需实现单样本IoU计算) sample_miou, sample_cls_iou = self.compute_sample_iou(pred[i], gt_grid_ind, gt_labels_ori, list(range(21))) sample_cls_iou = torch.FloatTensor(sample_cls_iou).to(feats.device) # 21 # 添加额外状态信息 additional_state = torch.tensor([ self.current_lr / self.cfg.lr, # 归一化学习率 epoch_progress, # 训练进度 *self.class_weights.cpu().numpy() # 当前类别权重 ], device=feats.device) # 组合状态 state = torch.cat([ feat_mean, feat_std, class_dist, sample_cls_iou, conf_mean.unsqueeze(0), conf_std.unsqueeze(0), additional_state ]) states.append(state) return torch.stack(states).to(feats.device) def compute_sample_iou(self, pred, gt_grid_ind, gt_labels_ori, classes=list(range(21))): """计算单个样本的IoU""" pred_labels = torch.argmax(pred, dim=0).cpu().detach().numpy() gt_grid_idx = pred_labels[ gt_grid_ind[:, 1], gt_grid_ind[:, 0], gt_grid_ind[:, 2] ] hist = fast_hist_crop( gt_grid_idx, gt_labels_ori, classes ) iou = np.diag(hist) / ((hist.sum(1) + hist.sum(0) - np.diag(hist)) + 1e-8) miou = np.nanmean(iou) return miou, iou def compute_reward(self, miou, prev_miou, class_ious, prev_class_ious): """ 计算复合奖励函数 Args: miou: 当前mIoU prev_miou: 前一次mIoU class_ious: 当前各类IoU [num_classes] prev_class_ious: 前一次各类IoU [num_classes] Returns: reward: 综合奖励值 """ # 基础奖励: mIoU提升 # miou_reward = 5.0 * (miou - prev_miou) * (1 + miou) # 高性能时奖励更大 miou_reward = 15.0 * np.sign(miou - prev_miou) * np.exp(3 * abs(miou - prev_miou)) # 1. 基础mIoU奖励(指数放大改进) # 类别平衡奖励: 鼓励所有类别均衡提升 class_reward = 0.0 for cls, (iou, prev_iou) in enumerate(zip(class_ious, prev_class_ious)): if iou > prev_iou: # 对稀有类别给予更高奖励 weight = 1.0 + (1.0 - prev_iou) # 性能越差的类权重越高 improvement = max(0, iou - prev_iou) class_reward += weight * improvement # 惩罚项: 防止某些类别性能严重下降 penalty = 0.0 for cls, (iou, prev_iou) in enumerate(zip(class_ious, prev_class_ious)): if iou < prev_iou * 0.7: # 性能下降超过10% penalty += 3.0 * (prev_iou - iou) * (1 - prev_iou) # 4. 探索奖励 entropy_bonus = 0.2 * self.agent.log_std.mean().exp().item() # 平衡奖励 (鼓励所有类别均衡提升) balance_reward = 0.5 * (1.0 - torch.std(torch.tensor(class_ious))) total_reward = miou_reward + class_reward - penalty + entropy_bonus + balance_reward return np.clip(total_reward, -2.0, 5.0) # 限制奖励范围 def compute_advantages(self, rewards, values): """计算GAE优势""" if isinstance(rewards, list): rewards = torch.tensor(rewards).to(values.device) advantages = torch.zeros_like(rewards) last_advantage = 0 # 反向计算GAE for t in reversed(range(len(rewards))): if t == len(rewards) - 1: next_value = 0 else: next_value = values[t+1] delta = rewards[t] + self.cfg.gamma * next_value - values[t] advantages[t] = delta + self.cfg.gamma * self.cfg.tau * last_advantage last_advantage = advantages[t] # 标准化优势 advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8) return advantages def apply_action(self, action): """ 应用智能体动作调整模型参数 Args: action: [6] 连续动作向量,范围[-1, 1] """ action = action.squeeze(0) # 动作0-1: 调整学习率 lr_scale = 0.8 + 0.4 * (action[0] + 1) / 2 # 映射到[0.5, 1.0] new_lr = self.cfg.lr * lr_scale # 更新学习率 if abs(new_lr - self.current_lr) > 1e-6: for param_group in self.optimizer_seg.param_groups: param_group['lr'] = new_lr self.current_lr = new_lr # # 动作2-3: 调整特征提取层权重 (范围[0.9, 1.1]) # neck_scale = 0.9 + 0.1 * (action[2] + 1) / 2 # with torch.no_grad(): # for param in self.seg_net.module.at_seg_neck.parameters(): # param.data *= neck_scale # (0.9 + 0.1 * action[2]) # 调整范围[0.9,1.1]at_seg_neck # # 动作4-5: 调整分类头权重 # head_scale = 0.8 + 0.2 * (action[4] + 1) / 2 # with torch.no_grad(): # for param in self.seg_net.module.at_seg_head.parameters(): # param.data *= head_scale # (0.9 + 0.1 * action[4]) # 调整范围[0.9,1.1] # 动作1: 设置特征提取层梯度缩放因子 (范围[0.5, 1.5]) self.neck_grad_scale = 0.5 + (action[1] + 1) # [-1,1] -> [0.5, 1.5] # 动作2: 设置分类头梯度缩放因子 (范围[0.5, 1.5]) self.head_grad_scale = 0.5 + (action[2] + 1) # [-1,1] -> [0.5, 1.5] # 4. 损失函数权重调整 # if hasattr(self.seg_net.module.at_seg_head, 'loss_weights'): # new_weights = F.softmax(torch.tensor([ # 1.0 + action[4], # 1.0 + action[5] # ]), dim=0) # self.seg_net.module.at_seg_head.loss_weights = new_weights # 动作1: 调整最差类别的权重 (范围[0.8, 1.2]) cls_weight_scale = 0.8 + 0.4 * (action[3] + 1.0) / 2.0 # 动作2: 选择要调整的类别 (范围[0, 20]) cls_idx = int((action[4] + 1.0) * 10) # 映射到[0,20] cls_idx = max(0, min(20, cls_idx)) # 更新类别权重 self.class_weights[cls_idx] = torch.clamp( self.class_weights[cls_idx] * cls_weight_scale, min=0.5, max=2.0 ) def train_epoch(self, train_loader, epoch): """执行一个训练周期""" epoch_metrics = { 'seg_loss': 0.0, 'reward': 0.0, 'miou': 0.0, 'class_ious': np.zeros(21), 'policy_loss': 0.0, 'value_loss': 0.0, 'entropy_loss': 0.0, 'batch_count': 0 } self.seg_net.train() self.agent.train() adjusted_hist_all = [] # 自适应探索参数 current_std = max(0.1, 0.5 * (1 - epoch/self.cfg.num_epochs_rl)) total_batches = len(train_loader) for batch_idx, data_dicts in enumerate(tqdm(train_loader, desc=f"RL Epoch {epoch+1}/{self.cfg.num_epochs_rl}")): load_data_to_gpu(data_dicts) # 计算当前进度 epoch_progress = batch_idx / total_batches # 1. 保存原始网络参数 original_state = copy.deepcopy(self.seg_net.state_dict()) # 2. 初始预测和特征 首先用分割网络计算初始预测(不计算梯度) with torch.no_grad(): initial_pred = self.seg_net(data_dicts) initial_miou_batch, initial_class_ious_batch, _ = get_miou( initial_pred, data_dicts, classes=list(range(21)) ) features = self.seg_net.module.extract_features(data_dicts) # DDP包装了 # features = self._base_seg_net.extract_features(data_dicts) # 3. 计算初始状态并选择动作 states = self.compute_state(features, initial_pred, data_dicts, epoch_progress) # [B, state_dim] # print(states.shape) # 设置自适应探索 with torch.no_grad(): self.agent.log_std.data = torch.clamp(self.agent.log_std, max=current_std) # 为每个状态选择动作(循环中调用`agent.act`) actions, log_probs, values = self.agent.act(states) # torch.Size([4, 6]) torch.Size([4]) torch.Size([4]) # 分布式训练中同步动作 if self.cfg.distributed: mean_action = actions.mean(dim=0) mean_action = mean_action.to(self.cfg.local_rank) dist.all_reduce(mean_action, op=dist.ReduceOp.SUM) mean_action /= dist.get_world_size() else: mean_action = actions.mean(dim=0) # 4. 应用动作(调整网络参数和优化器学习率) self.apply_action(mean_action) # # 5. 调整后预测(不计算梯度) # with torch.no_grad(): adjusted_pred = self.seg_net(data_dicts) adjusted_miou_batch, adjusted_class_ious_batch, adjusted_hist_batch = get_miou( adjusted_pred, data_dicts, classes=list(range(21)) ) adjusted_hist_all += adjusted_hist_batch # # === 步骤6: 恢复原始参数 === # self.seg_net.load_state_dict(original_state) # 7. 计算奖励 (使用整个批次的平均改进) reward = self.compute_reward( adjusted_miou_batch, initial_miou_batch, adjusted_class_ious_batch, initial_class_ious_batch ) # === 步骤9: PPO优化 === # 存储经验 experience = { 'states': states, 'actions': actions, 'rewards': [reward] * len(actions), 'old_log_probs': log_probs, 'old_values': values, # 'advantages': advantages, } self.replay_buffer.add(experience) # PPO优化 if len(self.replay_buffer) >= 10: # 缓冲区有足够样本 policy_loss, value_loss, entropy_loss = self.ppo_update(experience) epoch_metrics['policy_loss'] += policy_loss epoch_metrics['value_loss'] += value_loss epoch_metrics['entropy_loss'] += entropy_loss # === 步骤8: 正常监督学习 === # 前向传播 # pred = self.seg_net(data_dicts) seg_loss = self.seg_net.module.at_seg_head.get_loss( adjusted_pred, data_dicts["gt_seg"].to(adjusted_pred.device), class_weights=self.class_weights ) # 反向传播 self.optimizer_seg.zero_grad() seg_loss.backward() # 应用梯度缩放 for name, param in self.seg_net.named_parameters(): if 'at_seg_neck' in name and param.grad is not None: param.grad *= self.neck_grad_scale elif 'at_seg_head' in name and param.grad is not None: param.grad *= self.head_grad_scale # 梯度裁剪和更新 torch.nn.utils.clip_grad_norm_( self.seg_net.parameters(), self.cfg.max_grad_norm ) self.optimizer_seg.step() # === 步骤10: 记录指标 === epoch_metrics['seg_loss'] += seg_loss.item() epoch_metrics['reward'] += reward epoch_metrics['miou'] += adjusted_miou_batch epoch_metrics['class_ious'] += adjusted_class_ious_batch # epoch_metrics['policy_loss'] += policy_loss # epoch_metrics['value_loss'] += value_loss # epoch_metrics['entropy_loss'] += entropy_loss epoch_metrics['batch_count'] += 1 self.step_count += 1 # 记录到TensorBoard if self.step_count % 10 == 0: if self.writer: self.writer.add_scalar('Loss/seg_loss', seg_loss.item(), self.step_count) self.writer.add_scalar('Reward/total', reward, self.step_count) self.writer.add_scalar('mIoU/train', adjusted_miou_batch, self.step_count) self.writer.add_scalar('Loss/policy', policy_loss, self.step_count) self.writer.add_scalar('Loss/value', value_loss, self.step_count) self.writer.add_scalar('Loss/entropy', entropy_loss, self.step_count) self.writer.add_scalar('Params/lr_scale', self.optimizer_seg.param_groups[0]['lr'] / self.cfg.lr, self.step_count) self.writer.add_scalar('Params/neck_grad_scale', self.neck_grad_scale, self.step_count) self.writer.add_scalar('Params/head_grad_scale', self.head_grad_scale, self.step_count) self.writer.add_scalar('Params/exploration_std', current_std, self.step_count) # 计算平均指标 avg_metrics = {} for k in epoch_metrics: if k != 'batch_count': avg_metrics[k] = epoch_metrics[k] / epoch_metrics['batch_count'] hist = sum(adjusted_hist_all) #(21, 21) all_iou_overall = np.diag(hist) / ((hist.sum(1) + hist.sum(0) - np.diag(hist)) + 1e-8) # (21,) miou_epoch = np.nanmean(all_iou_overall) # # 记录到TensorBoard # self.writer.add_scalar('Loss/seg_loss', avg_metrics['seg_loss'], epoch) # self.writer.add_scalar('Reward/total', avg_metrics['reward'], epoch) # self.writer.add_scalar('mIoU/train', avg_metrics['miou'], epoch) # self.writer.add_scalar('Loss/policy', avg_metrics['policy_loss'], epoch) # self.writer.add_scalar('Loss/value', avg_metrics['value_loss'], epoch) # self.writer.add_scalar('Loss/entropy', avg_metrics['entropy_loss'], epoch) return avg_metrics def ppo_update(self, experience): """ PPO策略优化步骤 Args: batch: 包含以下键的字典: - states: [batch_size, state_dim] - actions: [batch_size, action_dim] - old_log_probs: [batch_size] - old_values: [batch_size] - rewards: [batch_size] - advantages: [batch_size] Returns: policy_loss: 策略损失值 value_loss: 值函数损失值 entropy_loss: 熵损失值 """ # 从缓冲区采样经验 experiences = self.replay_buffer.sample(batch_size=8) policy_losses, value_losses, entropy_losses = [], [], [] for exp in experiences: states = exp['states'] actions = exp['actions'] old_log_probs = exp['old_log_probs'] old_values = exp['old_values'] rewards = exp['rewards'] # 计算GAE优势 advantages = self.compute_advantages(rewards, old_values) returns = advantages + old_values for _ in range(self.cfg.ppo_epochs): # 评估当前策略 log_probs, entropy, values = self.agent.evaluate(states, actions) # 比率 ratios = torch.exp(log_probs - old_log_probs.detach()) # 裁剪目标 surr1 = ratios * advantages.detach() surr2 = torch.clamp(ratios, 1.0 - self.cfg.clip_param, 1.0 + self.cfg.clip_param) * advantages.detach() # 策略损失 policy_loss = -torch.min(surr1, surr2).mean() # 值函数损失 value_loss = 0.5 * (returns.detach() - values).pow(2).mean() # 熵损失 entropy_loss = -entropy.mean() # 总损失 loss = policy_loss + self.cfg.value_coef * value_loss + self.cfg.entropy_coef * entropy_loss # 智能体参数更新 self.optimizer_agent.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_( self.agent.parameters(), self.cfg.max_grad_norm ) self.optimizer_agent.step() policy_losses.append(policy_loss.item()) value_losses.append(value_loss.item()) entropy_losses.append(entropy_loss.item()) # 清空缓冲区 self.replay_buffer.clear() return ( np.mean(policy_losses) if policy_losses else 0.0, np.mean(value_losses) if value_losses else 0.0, np.mean(entropy_losses) if entropy_losses else 0.0, ) def close(self): """关闭资源""" if self.writer: self.writer.close() import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torch.utils.tensorboard import SummaryWriter import numpy as np import os import argparse from collections import deque from torch.utils.data import Dataset, DataLoader from torch.distributions import Normal, Categorical import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP import matplotlib.pyplot as plt from tqdm import tqdm import time from mmengine.registry import MODELS, DATASETS from mmengine.config import Config from rl_seg.datasets.build_dataloader import init_dist_pytorch, build_dataloader from rl_seg.datasets import load_data_to_gpu from rl_seg.agents.ppo_agent import PPOAgent, PPOTrainer from rl_seg.utils.compute_miou import get_miou, fast_hist, fast_hist_crop from rl_seg.utils.logger import logger, get_root_logger # 监督学习预训练 def supervised_pretrain(cfg): seg_net = MODELS.build(cfg.model).to('cuda') seg_head = MODELS.build(cfg.model.at_seg_head).to('cuda') if cfg.pretrained_path: ckpt = torch.load(cfg.pretrained_path) seg_net.load_state_dict(ckpt['state_dict'], strict=True) logger.info(f'Load pretrained ckpt: {cfg.pretrained_path}') freeze_pre_backbone_layers(seg_net, freeze_layers = ['vfe', 'map_to_bev', 'backbone_2d']) if cfg.sync_bn: seg_net = nn.SyncBatchNorm.convert_sync_batchnorm(seg_net) n_parameters = sum(p.numel() for p in seg_net.parameters() if p.requires_grad) # logger.info(f"Model: \n{self.model}") logger.info(f"Num params: {n_parameters}") seg_net = DDP(seg_net, device_ids=[cfg.local_rank]) if cfg.local_rank == 0: logger.info(seg_net) optimizer = optim.Adam(seg_net.parameters(), lr=cfg.lr) writer = SummaryWriter(log_dir=f'{cfg.exp_dir}/runs/pretrain') if cfg.local_rank == 0 else None train_losses = [] train_mious = [] train_class_ious = [] # 存储每个epoch的各类IoU best_miou = 0 for epoch in range(cfg.num_epochs): cfg.sampler.set_epoch(epoch) epoch_loss = 0.0 epoch_miou = 0.0 epoch_class_ious = np.zeros(21) # 初始化各类IoU累加器 seg_net.train() all_miou = [] all_hist = [] batch_count = 0 for data_dicts in tqdm(cfg.train_loader, desc=f"Pretrain Epoch {epoch+1}/{cfg.num_epochs}"): optimizer.zero_grad() pred = seg_net(data_dicts) device = pred.device seg_head = seg_head.to(device) loss = seg_head.get_loss(pred, data_dicts["gt_seg"].to(device)) loss.backward() optimizer.step() epoch_loss += loss.item() # import pdb;pdb.set_trace() # 计算mIoU class_ious = [] batch_miou, cls_iou, hist_batch = get_miou(pred, data_dicts, classes=list(range(21))) all_miou.append(batch_miou) all_hist += hist_batch batch_count += 1 if batch_count % 100 == 0 and cfg.local_rank == 0: logger.debug(f"Epoch {epoch+1}/{cfg.num_epochs}, Batch {batch_count}, \ Loss: {loss.item():.4f}, miou: {batch_miou}") # 计算epoch平均指标 avg_loss = epoch_loss / batch_count if batch_count > 0 else 0.0 hist = sum(all_hist) class_ious = np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist)) miou = np.nanmean(class_ious) train_losses.append(avg_loss) train_mious.append(miou) train_class_ious.append(class_ious) # 存储各类IoU # 记录到TensorBoard if writer: writer.add_scalar('Loss/train', avg_loss, epoch) writer.add_scalar('mIoU/train', miou, epoch) for cls, iou in enumerate(class_ious): writer.add_scalar(f'IoU/{cfg.class_names[cls]}', iou, epoch) if cfg.local_rank == 0: logger.info(f"Epoch {epoch+1}/{cfg.num_epochs} - Loss: {avg_loss:.3f}, mIoU: {miou*100:.3f}") logger.info("Class IoUs:") for cls, iou in enumerate(class_ious): logger.info(f" {cfg.class_names[cls]}: {iou*100:.3f}") if best_miou < miou: best_miou = miou torch.save({'state_dict': seg_net.module.state_dict()}, f"{cfg.exp_dir}/seg_pretrained_best_{best_miou*100:.3f}.pth") # 同步所有进程 dist.barrier() # # 保存预训练模型 if cfg.local_rank == 0: torch.save({'state_dict': seg_net.module.state_dict()}, f"{cfg.exp_dir}/seg_pretrained_latest.pth") if writer: writer.close() return seg_net # 强化学习微调 def rl_finetune(model, cfg): state_dim = 64*2 + 21 + 21 + 2 + 23 action_dim = 5 freeze_pre_backbone_layers(model, freeze_layers = ['vfe', 'map_to_bev', 'backbone_2d']) # 初始化PPO智能体 agent = PPOAgent(state_dim, action_dim).to(device) if cfg.agent_path: ckpt = torch.load(cfg.agent_path) agent.load_state_dict(ckpt['state_dict']) logger.info(f'Load agent ckpt: {cfg.agent_path}') trainer = PPOTrainer(model, agent, cfg) train_losses = [] train_rewards = [] train_mious = [] # 训练循环 for epoch in range(cfg.num_epochs_rl): avg_metrics = trainer.train_epoch(cfg.train_loader, epoch) # 记录指标 train_losses.append(avg_metrics['seg_loss']) train_rewards.append(avg_metrics['reward']) train_mious.append(avg_metrics['miou']) # 保存最佳模型 if avg_metrics['miou'] > trainer.best_miou: trainer.best_miou = avg_metrics['miou'] torch.save({'state_dict': model.module.state_dict()}, f"{cfg.exp_dir}/seg_rl_best_{trainer.best_miou*100:.3f}.pth") torch.save({'state_dict': agent.state_dict()}, f"{cfg.exp_dir}/ppo_agent_best.pth") # 打印日志 if cfg.local_rank == 0: logger.info(f"\nRL Epoch {epoch+1}/{cfg.num_epochs_rl} Results:") logger.info(f" Seg Loss: {avg_metrics['seg_loss']:.4f}") logger.info(f" Reward: {avg_metrics['reward']:.4f}") logger.info(f" mIoU: {avg_metrics['miou']*100:.3f} (Best: {trainer.best_miou*100:.3f})") logger.info(f" Policy Loss: {avg_metrics['policy_loss']:.4f}") logger.info(f" Value Loss: {avg_metrics['value_loss']:.4f}") logger.info(f" Entropy Loss: {avg_metrics['entropy_loss']:.4f}") logger.info(f" Learning Rate: {trainer.optimizer_seg.param_groups[0]['lr']:.2e}") logger.info(" Class IoUs:") for cls, iou in enumerate(avg_metrics['class_ious']): logger.info(f" {cfg.class_names[cls]}: {iou*100:.3f}") # 保存最终模型和训练记录 if cfg.local_rank == 0: torch.save({'state_dict': model.module.state_dict()}, f"{cfg.exp_dir}/seg_rl_final.pth") torch.save({'state_dict': agent.state_dict()}, f"{cfg.exp_dir}/ppo_agent_final.pth") logger.info(f"\nTraining completed. Best mIoU: {trainer.best_miou:.4f}") trainer.close() return model, agent # 模型评估 def evaluate_model(model, cfg): model.eval() avg_miou = 0 class_ious = np.zeros(21) hist_list = [] all_miou = [] with torch.no_grad(): for data_dicts in tqdm(cfg.val_loader, desc="Evaluating"): load_data_to_gpu(data_dicts) pred = model(data_dicts) batch_miou, cls_iou, hist_batch = get_miou(pred, data_dicts, classes=list(range(21))) class_ious += cls_iou hist_list += hist_batch all_miou.append(batch_miou) hist = sum(hist_list) # True Positives (TP) / (TP + False Negatives (FN) + TP + False Positives (FP)) class_ious = np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist)) miou = np.nanmean(class_ious) if cfg.local_rank == 0: logger.info("\nEvaluation Results:") logger.info(f"Overall mIoU: {miou*100:.3f}") for cls, iou in enumerate(class_ious): logger.info(f" {cfg.class_names[cls]}: {iou*100:.3f}") return miou, class_ious # 主函数 def main(args): cfg = Config.fromfile(args.cfg_file) os.environ['CUBLAS_WORKSPACE_CONFIG']=':16:8' if int(os.environ['GPU_NUM']) > 1: args.MASTER_ADDR = os.environ["MASTER_ADDR"] args.MASTER_PORT = os.environ["MASTER_PORT"] # 自动获取 torchrun 传入的全局变量 cfg.rank = int(os.environ["RANK"]) cfg.local_rank = int(os.environ["LOCAL_RANK"]) cfg.world_size = int(os.environ["WORLD_SIZE"]) else: cfg.rank = 0 cfg.local_rank = 0 cfg.world_size = 1 os.environ['MASTER_ADDR'] = 'localhost' os.environ["MASTER_PORT"] = '23456' total_gpus, LOCAL_RANK = init_dist_pytorch(cfg) cfg.distributed = True # 第一阶段:监督学习预训练 logger.info("="*50) logger.info("Starting Supervised Pretraining...") logger.info("="*50) if args.batch_size: cfg.batch_size = args.batch_size cfg.work_dir = os.environ["userPath"] cfg.jinn_dir = os.environ["JinnTrainResult"] if args.exp: cfg.exp_dir = os.path.join(cfg.jinn_dir, args.exp) else: cfg.exp_dir = cfg.jinn_dir if cfg.local_rank == 0 and not os.path.exists(cfg.exp_dir): os.makedirs(cfg.exp_dir) logger.info(f'exp dir: {cfg.exp_dir}') cfg.num_gpus = cfg.world_size logger.info(f'configs: \n{cfg.pretty_text}') dist_train = True train_dataset, train_dataloader, sampler = build_dataloader(dataset_cfg=cfg, data_path=cfg.train_data_path, workers=cfg.num_workers, samples_per_gpu=cfg.batch_size, num_gpus=total_gpus, dist=dist_train, pipeline=cfg.train_pipeline, training=True) cfg.train_loader = train_dataloader cfg.sampler = sampler seg_net = supervised_pretrain(cfg) val_dataset, val_dataloader, sampler = build_dataloader(dataset_cfg=cfg, data_path=cfg.val_data_path, workers=cfg.num_workers, samples_per_gpu=cfg.batch_size, num_gpus=total_gpus, dist=True, pipeline=cfg.val_pipeline, training=False) cfg.val_loader = val_dataloader cfg.sampler = sampler # 评估预训练模型 logger.info("\nEvaluating Pretrained Model...") pretrain_miou, pretrain_class_ious = evaluate_model(seg_net, cfg) # return # 第二阶段:强化学习微调 logger.info("\n" + "="*50) logger.info("Starting RL Finetuning...") logger.info("="*50) rl_seg_net, ppo_agent = rl_finetune(seg_net, cfg) # 评估强化学习优化后的模型 logger.info("\nEvaluating RL Optimized Model...") rl_miou, rl_class_ious = evaluate_model(rl_seg_net, cfg) # 结果对比 if cfg.local_rank == 0: logger.info("\nPerformance Comparison:") logger.info(f"Pretrained mIoU: {pretrain_miou*100:.3f}") logger.info(f"RL Optimized mIoU: {rl_miou*100:.3f}") logger.info(f"Improvement: {(rl_miou - pretrain_miou)*100:.3f} ({((rl_miou - pretrain_miou)/pretrain_miou+1e-8)*100:.2f}%)") if pretrain_miou > rl_miou: torch.save({'state_dict': seg_net.module.state_dict()}, f"{cfg.exp_dir}/bese_model_{pretrain_miou}.pth") else: torch.save({'state_dict': rl_seg_net.state_dict()}, f"{cfg.exp_dir}/bese_model_{rl_miou}.pth") logger.info("\nTraining completed successfully!") def freeze_pre_backbone_layers(model, freeze_layers=['vfe', 'map_to_bev', 'backbone_2d']): """冻结主干网络前的所有层""" # 常见预主干层名称(根据实际模型结构调整) if hasattr(model, 'module'): # 处理 DDP 封装 model = model.module for name, module in model.named_children(): # 冻结所有指定名称的模块 if name in freeze_layers: logger.info(f"Freezing layer: {name}") for param in module.parameters(): param.requires_grad = False # 额外冻结非主干/分割头的模块 elif name not in ['at_seg_neck', 'at_seg_head']: logger.info(f"Freezing non-core layer: {name}") for param in module.parameters(): param.requires_grad = False if __name__ == "__main__": def args_config(): parser = argparse.ArgumentParser(description='arg parser') parser.add_argument('--cfg_file', type=str, default="rl_seg/configs/rl_seg_leap.py", help='specify the config for training') parser.add_argument('--batch_size', type=int, default=None, required=False, help='batch size for training') parser.add_argument('--ckpt', type=str, default=None, help='checkpoint to start from') parser.add_argument('--pretrained_model', type=str, default=None, help='pretrained_model') parser.add_argument('--exp', type=str, default=None, help='export dir.') return parser.parse_args() args = args_config() main(args) 分析代码并解析代码方案,(Seg Loss: 0.0587, Reward: 0.2000, Policy Loss: -0.0000, Value Loss: 0.4583, Entropy Loss: -3.6290, Learning Rate: 9.95e-04)loss没有什么浮动,以及mIoU没有明显提升的现象,最后结果 Pretrained mIoU: 75.171 - RL Optimized mIoU: 74.355 - Improvement: -0.816 (-1.09%)排查配置参数及代码实现是否逻辑合理,然后提出你的优化方案
最新发布
08-05
import os import math import time import pygame import pymunk as pm from characters import Bird from level import Level current_path = os.getcwd() pygame.init() screen = pygame.display.set_mode((1200, 650)) redbird = pygame.image.load( "D:/python_code/resources/images/red-bird3.png").convert_alpha() yellowbird = pygame.image.load( "D:/python_code/resources/images/yellowbird1.png").convert_alpha() yellowbird2 = pygame.image.load( "D:/python_code/resources/images/yellowbird2.png").convert_alpha() bluebird = pygame.image.load( "D:/python_code/resources/images/bluebird1.png").convert_alpha() bluebird2 = pygame.image.load( "D:/python_code/resources/images/bluebird2.png").convert_alpha() background1 = pygame.image.load( "D:/python_code/resources/images/background1.png").convert_alpha() background2 = pygame.image.load( "D:/python_code/resources/images/background3.png").convert_alpha() sling_image = pygame.image.load( "D:/python_code/resources/images/sling-3.png").convert_alpha() full_sprite = pygame.image.load( "D:/python_code/resources/images/full-sprite.png").convert_alpha() rect = pygame.Rect(181, 1050, 50, 50) cropped = full_sprite.subsurface(rect).copy() pig_image = pygame.transform.scale(cropped, (30, 30)) buttons = pygame.image.load( "D:/python_code/resources/images/selected-buttons.png").convert_alpha() pig_happy = pygame.image.load( "D:/python_code/resources/images/pig_failed.png").convert_alpha() pig_1 = pygame.image.load( "D:/python_code/resources/images/pig_1.png").convert_alpha() pig_2 = pygame.image.load( "D:/python_code/resources/images/pig_2.png").convert_alpha() stars = pygame.image.load( "D:/python_code/resources/images/stars-edited.png").convert_alpha() rect = pygame.Rect(0, 0, 200, 200) star1 = stars.subsurface(rect).copy() rect = pygame.Rect(204, 0, 200, 200) star2 = stars.subsurface(rect).copy() rect = pygame.Rect(426, 0, 200, 200) star3 = stars.subsurface(rect).copy() rect = pygame.Rect(164, 10, 60, 60) pause_button = buttons.subsurface(rect).copy() rect = pygame.Rect(24, 4, 100, 100) replay_button = buttons.subsurface(rect).copy() rect = pygame.Rect(142, 365, 130, 100) next_button = buttons.subsurface(rect).copy() clock = pygame.time.Clock() rect = pygame.Rect(18, 212, 100, 100) play_button = buttons.subsurface(rect).copy() # 创建一个简单的火焰图像(圆形渐变) flame_surface = pygame.Surface((30, 30), pygame.SRCALPHA) pygame.draw.circle(flame_surface, (255, 140, 0, 180), (15, 15), 15) pygame.draw.circle(flame_surface, (255, 255, 0, 120), (15, 15), 10) pygame.draw.circle(flame_surface, (255, 255, 255, 60), (15, 15), 5) running = True # the base of the physics space = pm.Space() space.gravity = (0.0, -700.0) pigs = [] birds = [] balls = [] polys = [] beams = [] columns = [] poly_points = [] ball_number = 0 polys_dict = {} mouse_distance = 0 rope_lenght = 90 angle = 0 x_mouse = 0 y_mouse = 0 count = 0 mouse_pressed = False t1 = 0 tick_to_next_circle = 10 RED = (255, 0, 0) BLUE = (0, 0, 255) BLACK = (0, 0, 0) WHITE = (255, 255, 255) sling_x, sling_y = 135, 450 sling2_x, sling2_y = 160, 450 score = 0 game_state = 0 bird_path = [] counter = 0 restart_counter = False bonus_score_once = True bold_font = pygame.font.SysFont("arial", 30, bold=True) bold_font2 = pygame.font.SysFont("arial", 40, bold=True) bold_font3 = pygame.font.SysFont("arial", 50, bold=True) wall = False # Static floor static_body = pm.Body(body_type=pm.Body.STATIC) static_lines = [pm.Segment(static_body, (0.0, 060.0), (1200.0, 060.0), 0.0)] static_lines1 = [pm.Segment(static_body, (1200.0, 060.0), (1200.0, 800.0), 0.0)] for line in static_lines: line.elasticity = 0.95 line.friction = 1 line.collision_type = 3 for line in static_lines1: line.elasticity = 0.95 line.friction = 1 line.collision_type = 3 space.add(static_body) for line in static_lines: space.add(line) def to_pygame(p): """Convert pymunk to pygame coordinates""" return int(p.x), int(-p.y + 600) def vector(p0, p1): """Return the vector of the points p0 = (xo,yo), p1 = (x1,y1)""" a = p1[0] - p0[0] b = p1[1] - p0[1] return a, b def unit_vector(v): """Return the unit vector of the points v = (a,b)""" h = ((v[0] ** 2) + (v[1] ** 2)) ** 0.5 if h == 0: h = 0.000000000000001 ua = v[0] / h ub = v[1] / h return ua, ub def distance(xo, yo, x, y): """distance between points""" dx = x - xo dy = y - yo d = ((dx ** 2) + (dy ** 2)) ** 0.5 return d def load_music(): """Load the music""" song1 = '../resources/sounds/angry-birds.ogg' pygame.mixer.music.load(song1) pygame.mixer.music.play(-1) def sling_action(bird_type): """Set up sling behavior""" global mouse_distance global rope_lenght global angle global x_mouse global y_mouse # Fixing bird to the sling rope v = vector((sling_x, sling_y), (x_mouse, y_mouse)) uv = unit_vector(v) uv1 = uv[0] uv2 = uv[1] mouse_distance = distance(sling_x, sling_y, x_mouse, y_mouse) pu = (uv1 * rope_lenght + sling_x, uv2 * rope_lenght + sling_y) bigger_rope = 102 x_redbird = x_mouse - 20 y_redbird = y_mouse - 20 if mouse_distance > rope_lenght: pux, puy = pu pux -= 20 puy -= 20 pul = pux, puy if bird_type == 'yellow': screen.blit(yellowbird, pul) pu2 = (uv1 * bigger_rope + sling_x, uv2 * bigger_rope + sling_y) pygame.draw.line(screen, (0, 0, 0), (sling2_x, sling2_y), pu2, 5) screen.blit(yellowbird, pul) pygame.draw.line(screen, (0, 0, 0), (sling_x, sling_y), pu2, 5) elif bird_type == 'red': screen.blit(redbird, pul) pu2 = (uv1 * bigger_rope + sling_x, uv2 * bigger_rope + sling_y) pygame.draw.line(screen, (0, 0, 0), (sling2_x, sling2_y), pu2, 5) screen.blit(redbird, pul) pygame.draw.line(screen, (0, 0, 0), (sling_x, sling_y), pu2, 5) elif bird_type == 'blue': screen.blit(bluebird, pul) pu2 = (uv1 * bigger_rope + sling_x, uv2 * bigger_rope + sling_y) pygame.draw.line(screen, (0, 0, 0), (sling2_x, sling2_y), pu2, 5) screen.blit(bluebird, pul) pygame.draw.line(screen, (0, 0, 0), (sling_x, sling_y), pu2, 5) else: mouse_distance += 10 pu3 = (uv1 * mouse_distance + sling_x, uv2 * mouse_distance + sling_y) pygame.draw.line(screen, (0, 0, 0), (sling2_x, sling2_y), pu3, 5) if bird_type == 'yellow': screen.blit(yellowbird, (x_redbird, y_redbird)) elif bird_type == 'red': screen.blit(redbird, (x_redbird, y_redbird)) elif bird_type == 'blue': screen.blit(bluebird, (x_redbird, y_redbird)) pygame.draw.line(screen, (0, 0, 0), (sling_x, sling_y), pu3, 5) # Angle of impulse dy = y_mouse - sling_y dx = x_mouse - sling_x if dx == 0: dx = 0.00000000000001 angle = math.atan((float(dy)) / dx) def draw_level_cleared(): """Draw level cleared""" global game_state global bonus_score_once global score level_cleared = bold_font3.render("Level Cleared!", 1, WHITE) score_level_cleared = bold_font2.render(str(score), 1, WHITE) if level.number_of_birds >= 0 and len(pigs) == 0: if bonus_score_once: score += (level.number_of_birds - 1) * 10000 bonus_score_once = False game_state = 4 rect = pygame.Rect(300, 0, 600, 800) pygame.draw.rect(screen, BLACK, rect) screen.blit(level_cleared, (450, 90)) if level.one_star <= score <= level.two_star: screen.blit(star1, (310, 190)) if level.two_star <= score <= level.three_star: screen.blit(star1, (310, 190)) screen.blit(star2, (500, 170)) if score >= level.three_star: screen.blit(star1, (310, 190)) screen.blit(star2, (500, 170)) screen.blit(star3, (700, 200)) screen.blit(score_level_cleared, (550, 400)) screen.blit(replay_button, (510, 480)) screen.blit(next_button, (620, 480)) def draw_level_failed(): """Draw level failed""" global game_state failed = bold_font3.render("Level Failed", 1, WHITE) if level.number_of_birds <= 0 < len(pigs) and time.time() - t2 > 5: game_state = 3 rect = pygame.Rect(300, 0, 600, 800) pygame.draw.rect(screen, BLACK, rect) screen.blit(failed, (450, 90)) screen.blit(pig_happy, (380, 120)) screen.blit(replay_button, (520, 460)) def restart(): """Delete all objects of the level""" pigs_to_remove = [] birds_to_remove = [] columns_to_remove = [] beams_to_remove = [] for pig in pigs: pigs_to_remove.append(pig) for pig in pigs_to_remove: space.remove(pig.shape, pig.shape.body) pigs.remove(pig) for bird in birds: birds_to_remove.append(bird) for bird in birds_to_remove: space.remove(bird.shape, bird.shape.body) birds.remove(bird) for column in columns: columns_to_remove.append(column) for column in columns_to_remove: space.remove(column.shape, column.shape.body) columns.remove(column) for beam in beams: beams_to_remove.append(beam) for beam in beams_to_remove: space.remove(beam.shape, beam.shape.body) beams.remove(beam) def post_solve_bird_pig(arbiter, space, _): """Collision between bird and pig""" surface = screen a, b = arbiter.shapes bird_body = a.body pig_body = b.body p = to_pygame(bird_body.position) p2 = to_pygame(pig_body.position) r = 30 pygame.draw.circle(surface, BLACK, p, r, 4) pygame.draw.circle(surface, RED, p2, r, 4) pigs_to_remove = [] for pig in pigs: if pig_body == pig.body: pig.life -= 20 pigs_to_remove.append(pig) global score score += 10000 for pig in pigs_to_remove: space.remove(pig.shape, pig.shape.body) pigs.remove(pig) def post_solve_bird_wood(arbiter, space, _): """Collision between bird and wood""" poly_to_remove = [] if arbiter.total_impulse.length > 1100: a, b = arbiter.shapes for column in columns: if b == column.shape: poly_to_remove.append(column) for beam in beams: if b == beam.shape: poly_to_remove.append(beam) for poly in poly_to_remove: if poly in columns: columns.remove(poly) if poly in beams: beams.remove(poly) space.remove(b, b.body) global score score += 5000 def post_solve_pig_wood(arbiter, space, _): """Collision between pig and wood""" pigs_to_remove = [] if arbiter.total_impulse.length > 700: pig_shape, wood_shape = arbiter.shapes for pig in pigs: if pig_shape == pig.shape: pig.life -= 20 global score score += 10000 if pig.life <= 0: pigs_to_remove.append(pig) for pig in pigs_to_remove: space.remove(pig.shape, pig.shape.body) pigs.remove(pig) # bird and pigs space.add_collision_handler(0, 1).post_solve = post_solve_bird_pig # bird and wood space.add_collision_handler(0, 2).post_solve = post_solve_bird_wood # pig and wood space.add_collision_handler(1, 2).post_solve = post_solve_pig_wood load_music() level = Level(pigs, columns, beams, space) level.number = 0 level.load_level() bird_type_list = ['red', 'yellow', 'blue', 'yellow'] while running: # Input handling for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: running = False elif event.type == pygame.KEYDOWN and event.key == pygame.K_w: # Toggle wall if wall: for line in static_lines1: space.remove(line) wall = False else: for line in static_lines1: space.add(line) wall = True elif event.type == pygame.KEYDOWN and event.key == pygame.K_s: space.gravity = (0.0, -10.0) level.bool_space = True # restart() # level.load_level() elif event.type == pygame.KEYDOWN and event.key == pygame.K_n: space.gravity = (0.0, -700.0) level.bool_space = False # restart() # level.load_level() elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: for bird in birds: # 确保是黄色小鸟、已经发射、还没加速过 if hasattr(bird, 'bird_type') and bird.bird_type == 'yellow' and not bird.has_boosted: vel = bird.shape.body.velocity # 计算单位向量方向 direction = vel.normalized() # 设置加速倍数(可调) boost_factor = 1.8 new_velocity = direction * vel.length * boost_factor bird.shape.body.velocity = new_velocity bird.has_boosted = True elif hasattr(bird, 'bird_type') and bird.bird_type == 'blue' and not bird.has_split: if bird.shape.body.velocity.length > 5: bird.has_split = True pos = bird.shape.body.position vel = bird.shape.body.velocity speed = vel.length direction = vel.normalized() angle = math.atan2(direction.y, direction.x) delta_angle = math.radians(15) # 偏移角度(上下15°) angle_up = angle + delta_angle vel_up = pm.Vec2d(math.cos(angle_up), math.sin(angle_up)) * speed angle_down = angle - delta_angle vel_down = pm.Vec2d(math.cos(angle_down), math.sin(angle_down)) * speed offset = 30 bird1 = Bird(0, 0, 0, 0, space, bird_type='blue') bird1.shape.body.position = pos + pm.Vec2d(0, offset) bird1.shape.body.velocity = vel_up bird1.has_split = True bird2 = Bird(0, 0, 0, 0, space, bird_type='blue') bird2.shape.body.position = pos + pm.Vec2d(0, -offset) bird2.shape.body.velocity = vel_down bird2.has_split = True birds.append(bird1) birds.append(bird2) if pygame.mouse.get_pressed()[0] and 100 < x_mouse < 250 and 370 < y_mouse < 550: mouse_pressed = True if (event.type == pygame.MOUSEBUTTONUP and event.button == 1 and mouse_pressed): # Release new bird mouse_pressed = False if level.number_of_birds > 0: level.number_of_birds -= 1 t1 = time.time() * 1000 xo = 154 yo = 156 if mouse_distance > rope_lenght: mouse_distance = rope_lenght bird_index = 4 - level.number_of_birds - 1 # 当前是第几只鸟(从 0 开始) bird_type = bird_type_list[bird_index] if x_mouse < sling_x + 5: bird = Bird(mouse_distance, angle, xo, yo, space, bird_type=bird_type) birds.append(bird) else: bird = Bird(-mouse_distance, angle, xo, yo, space, bird_type=bird_type) birds.append(bird) if level.number_of_birds == 0: t2 = time.time() if event.type == pygame.MOUSEBUTTONUP and event.button == 1: if x_mouse < 60 and 155 > y_mouse > 90: game_state = 1 if game_state == 1: if x_mouse > 500 and 200 < y_mouse < 300: # Resume in the paused screen game_state = 0 if x_mouse > 500 and y_mouse > 300: # Restart in the paused screen restart() level.load_level() bird_type_list = ['red', 'yellow', 'blue', 'yellow'] game_state = 0 bird_path = [] if game_state == 3: # Restart in the failed level screen if x_mouse > 500 and x_mouse < 620 and y_mouse > 450: restart() level.load_level() bird_type_list = ['red', 'yellow', 'blue', 'yellow'] game_state = 0 bird_path = [] score = 0 if game_state == 4: # Build next level if x_mouse > 610 and y_mouse > 450: restart() level.number += 1 game_state = 0 level.load_level() bird_type_list = ['red', 'yellow', 'blue', 'yellow'] score = 0 bird_path = [] bonus_score_once = True if 610 > x_mouse > 500 and y_mouse > 450: # Restart in the level cleared screen restart() level.load_level() bird_type_list = ['red', 'yellow', 'blue', 'yellow'] game_state = 0 bird_path = [] score = 0 x_mouse, y_mouse = pygame.mouse.get_pos() # Draw background screen.fill((130, 200, 100)) # screen.blit(background2, (0, -50)) # 显示“no-gravity”标识 if space.gravity == (0.0, -10.0): screen.blit(background1, (0, -50)) ng_text = bold_font.render("NO-GRAVITY", True, (255, 0, 0)) screen.blit(ng_text, (20, 20)) else: screen.blit(background2, (0, -50)) # 显示“WALL ON”或“WALL OFF”标识 if wall: wall_text = bold_font.render("WALL: ON", True, (0, 200, 255)) else: wall_text = bold_font.render("WALL: OFF", True, (200, 0, 0)) if wall: pygame.draw.rect(screen, (255, 0, 0), (1195, 60, 5, 540)) # 亮红色墙体 screen.blit(wall_text, (20, 50)) # Draw first part of the sling rect = pygame.Rect(50, 0, 70, 220) screen.blit(sling_image, (138, 420), rect) # Draw the trail left behind for point in bird_path: pygame.draw.circle(screen, WHITE, point, 5, 0) # Draw the birds in the wait line if level.number_of_birds > 0: for i in range(level.number_of_birds - 1): x = 30 + (i * 35) bird_index = 4 - i - 1 # 当前是第几只鸟(从 0 开始) bird_type_l = ['yellow', 'blue', 'yellow', 'red'] bird_type = bird_type_list[bird_index] if bird_type == 'yellow': screen.blit(yellowbird, (x, 508)) elif bird_type == 'red': screen.blit(redbird, (x, 508)) elif bird_type == 'blue': screen.blit(bluebird, (x, 508)) # Draw sling behavior if mouse_pressed and level.number_of_birds > 0: # 在弹弓上的鸟 bird_index = 4 - level.number_of_birds # 当前是第几只鸟(从 0 开始) bird_type_list = ['red', 'yellow', 'blue', 'yellow'] bird_type = bird_type_list[bird_index] sling_action(bird_type) else: if time.time() * 1000 - t1 > 300 and level.number_of_birds > 0: bird_index = 4 - level.number_of_birds # 当前是第几只鸟(从 0 开始) bird_type_list = ['red', 'yellow', 'blue', 'yellow'] bird_type = bird_type_list[bird_index] if bird_type == 'yellow': screen.blit(yellowbird, (130, 426)) elif bird_type == 'red': screen.blit(redbird, (130, 426)) elif bird_type == 'blue': screen.blit(bluebird, (130, 426)) else: pygame.draw.line(screen, (0, 0, 0), (sling_x, sling_y - 8), (sling2_x, sling2_y - 7), 5) birds_to_remove = [] pigs_to_remove = [] counter += 1 # Draw birds for bird in birds: if bird.shape.body.position.y < 0: birds_to_remove.append(bird) p = to_pygame(bird.shape.body.position) x, y = p x -= 22 y -= 20 # if hasattr(bird, 'bird_type') and bird.bird_type == 'yellow': # 飞出去的鸟 # screen.blit(yellowbird, (x, y)) if hasattr(bird, 'bird_type') and bird.bird_type == 'yellow': if bird.has_boosted: vel = bird.shape.body.velocity direction = vel.normalized() offset_x = int(-direction.x * 25) offset_y = int(direction.y * 25) flame_pos = (x + offset_x, y + offset_y) screen.blit(flame_surface, flame_pos) screen.blit(yellowbird2, (x, y)) # 加速状态图 else: screen.blit(yellowbird, (x, y)) # 普通图 elif bird.bird_type == 'red': screen.blit(redbird, (x, y)) elif bird.bird_type == 'blue': if bird.has_split: screen.blit(bluebird2, (x, y)) # 分裂后的蓝鸟图像 else: screen.blit(bluebird, (x, y)) # 普通蓝鸟图像 # pygame.draw.circle(screen, BLUE, # p, int(bird.shape.radius), 2) if counter >= 3 and time.time() - t1 < 5: bird_path.append(p) restart_counter = True if restart_counter: counter = 0 restart_counter = False # Remove birds and pigs for bird in birds_to_remove: space.remove(bird.shape, bird.shape.body) birds.remove(bird) for pig in pigs_to_remove: space.remove(pig.shape, pig.shape.body) pigs.remove(pig) # Draw static lines for line in static_lines: body = line.body pv1 = body.position + line.a.rotated(body.angle) pv2 = body.position + line.b.rotated(body.angle) p1 = to_pygame(pv1) p2 = to_pygame(pv2) pygame.draw.lines(screen, (150, 150, 150), False, [p1, p2]) i = 0 # Draw pigs for pig in pigs: i += 1 # print (i,pig.life) pig = pig.shape if pig.body.position.y < 0: pigs_to_remove.append(pig) p = to_pygame(pig.body.position) x, y = p angle_degrees = math.degrees(pig.body.angle) img = pygame.transform.rotate(pig_image, angle_degrees) w, h = img.get_size() x -= w * 0.5 y -= h * 0.5 screen.blit(img, (x, y)) pygame.draw.circle(screen, BLUE, p, int(pig.radius), 2) # Draw columns and Beams for column in columns: column.draw_poly('columns', screen) for beam in beams: beam.draw_poly('beams', screen) # Update physics dt = 1.0 / 50.0 / 2. for x in range(2): space.step(dt) # make two updates per frame for better stability # Drawing second part of the sling rect = pygame.Rect(0, 0, 60, 200) screen.blit(sling_image, (120, 420), rect) # Draw score score_font = bold_font.render("SCORE", 1, WHITE) number_font = bold_font.render(str(score), 1, WHITE) screen.blit(score_font, (1060, 90)) if score == 0: screen.blit(number_font, (1100, 130)) else: screen.blit(number_font, (1060, 130)) screen.blit(pause_button, (10, 90)) # Pause option if game_state == 1: screen.blit(play_button, (500, 200)) screen.blit(replay_button, (500, 300)) draw_level_cleared() draw_level_failed() pygame.display.flip() clock.tick(50) pygame.display.set_caption("fps: " + str(clock.get_fps())) 分析代码并找出space ATTRIBUTE错误的原因
06-28
# -*- coding: utf-8 -*- """ 京东高价值客户识别与全链路行为预测系统 - 修复版 作者:李梓翀 李富生 数据来源:京东公开数据(模拟生成) """ import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from datetime import datetime, timedelta import random import time import os from faker import Faker from sklearn.preprocessing import LabelEncoder, StandardScaler from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.cluster import KMeans from sklearn.metrics import (roc_auc_score, precision_score, recall_score, f1_score, mean_absolute_error, roc_curve) from sklearn.decomposition import PCA # -------------------------- # 数据爬取与模拟生成 # -------------------------- def generate_jd_simulation_data(num_users=5000, num_records=50000): """ 模拟生成京东用户行为数据 """ print("开始生成模拟京东数据...") fake = Faker('zh_CN') np.random.seed(42) # 创建用户基础数据 users = pd.DataFrame({ 'user_id': [f'U{str(i).zfill(6)}' for i in range(1, num_users+1)], 'age': np.random.randint(18, 65, num_users), 'gender': np.random.choice(['男', '女'], num_users, p=[0.55, 0.45]), 'city': [fake.city() for _ in range(num_users)], 'is_plus_member': np.random.choice([0, 1], num_users, p=[0.7, 0.3]), 'join_date': [fake.date_between(start_date='-3y', end_date='today') for _ in range(num_users)] }) # 创建行为数据 behavior_types = ['浏览', '加购', '购买', '评价', '收藏'] categories = { '家电': ['冰箱', '洗衣机', '空调', '电视', '微波炉'], '手机': ['智能手机', '配件', '平板', '智能手表'], '电脑': ['笔记本', '台式机', '显示器', '外设'], '数码': ['相机', '耳机', '音箱', '存储设备'], '家居': ['家具', '家纺', '厨具', '灯具'] } records = [] for _ in range(num_records): user_id = f'U{str(np.random.randint(1, num_users+1)).zfill(6)}' behavior_time = fake.date_time_between(start_date='-90d', end_date='now') # 随机选择品类和子类 main_cat = random.choice(list(categories.keys())) sub_cat = random.choice(categories[main_cat]) # 行为类型概率分布 behavior_prob = [0.5, 0.2, 0.15, 0.1, 0.05] behavior_type = np.random.choice(behavior_types, p=behavior_prob) # 订单相关数据 order_amount = 0 if behavior_type == '购买': # 高价商品概率 if main_cat == '家电' and np.random.random() < 0.3: order_amount = np.random.uniform(3000, 20000) else: order_amount = np.random.uniform(100, 3000) # 促销活动参与 is_promotion = 1 if np.random.random() < 0.4 else 0 # 物流评分 delivery_rating = np.random.randint(3, 6) if behavior_type == '购买' else 0 records.append({ 'user_id': user_id, 'behavior_time': behavior_time, 'behavior_type': behavior_type, 'main_category': main_cat, 'sub_category': sub_cat, 'order_amount': order_amount, 'is_promotion': is_promotion, 'delivery_rating': delivery_rating }) # 创建DataFrame df = pd.DataFrame(records) # 添加未来行为标签(模拟未来3个月行为) print("添加未来行为标签...") user_purchase_future = df[df['behavior_type'] == '购买'].groupby('user_id')['order_amount'].sum().reset_index() # 修正语法错误:括号匹配 user_purchase_future['will_buy_high_end'] = np.where( (user_purchase_future['order_amount'] > 5000) & (np.random.random(len(user_purchase_future)) > 0.3), 1, 0).astype(int) # PLUS会员续费倾向 - 修正语法错误 plus_users = users[users['is_plus_member'] == 1]['user_id'].tolist() user_purchase_future['will_renew_plus'] = np.where( user_purchase_future['user_id'].isin(plus_users), np.random.choice([0, 1], len(user_purchase_future)), 0).astype(int) # 合并数据 df = pd.merge(df, users, on='user_id', how='left') df = pd.merge(df, user_purchase_future[['user_id', 'will_buy_high_end', 'will_renew_plus']], on='user_id', how='left').fillna(0) # 保存数据 os.makedirs('data', exist_ok=True) df.to_csv('data/jd_simulated_data.csv', index=False) print(f"模拟数据生成完成,共 {len(df)} 条记录,保存至 data/jd_simulated_data.csv") return df # -------------------------- # 数据预处理 - 修复版本 # -------------------------- def preprocess_data(df): """数据预处理与特征工程""" print("\n开始数据预处理与特征工程...") # 1. 数据清洗 # 过滤异常订单(金额异常) df = df[df['order_amount'] <= 50000] # 修复时间戳错误(修复未来时间戳) current_date = pd.Timestamp.now() df['behavior_time'] = pd.to_datetime(df['behavior_time']) df = df[df['behavior_time'] <= current_date] # 确保日期列转换 df['join_date'] = pd.to_datetime(df['join_date']) # 2. 特征工程 - 基础特征 # 计算用户活跃天数(最近90天) active_days = df.groupby('user_id')['behavior_time'].apply( lambda x: x.dt.date.nunique()).reset_index(name='active_days') # 促销敏感度(参与促销活动比例) promo_sensitivity = df[df['is_promotion'] == 1].groupby('user_id').size().reset_index(name='promo_count') total_actions = df.groupby('user_id').size().reset_index(name='total_actions') promo_sensitivity = pd.merge(promo_sensitivity, total_actions, on='user_id') promo_sensitivity['promo_sensitivity'] = promo_sensitivity['promo_count'] / promo_sensitivity['total_actions'] # 品类浏览集中度 category_concentration = df.groupby(['user_id', 'main_category']).size().reset_index(name='category_count') category_concentration = category_concentration.groupby('user_id')['category_count'].apply( lambda x: (x.max() / x.sum())).reset_index(name='category_concentration') # 3. 高价值客户标签定义 high_value_criteria = df.groupby('user_id').agg( total_spend=('order_amount', 'sum'), purchase_count=('behavior_type', lambda x: (x == '购买').sum()), category_count=('main_category', 'nunique'), # 修复:保留目标列 will_buy_high_end=('will_buy_high_end', 'first'), will_renew_plus=('will_renew_plus', 'first') ).reset_index() high_value_criteria['is_high_value'] = np.where( (high_value_criteria['total_spend'] > 5000) | (high_value_criteria['purchase_count'] > 8) | (high_value_criteria['category_count'] >= 3), 1, 0) # 4. 合并特征(修复:保留目标列) features = pd.merge(active_days, promo_sensitivity[['user_id', 'promo_sensitivity']], on='user_id') features = pd.merge(features, category_concentration, on='user_id') features = pd.merge(features, high_value_criteria[['user_id', 'is_high_value', 'will_buy_high_end', 'will_renew_plus']], on='user_id') # 5. 添加用户基本信息 user_base = df[['user_id', 'age', 'gender', 'city', 'is_plus_member', 'join_date']].drop_duplicates() features = pd.merge(features, user_base, on='user_id') # 6. 修复:添加时间相关特征 last_activity = df.groupby('user_id')['behavior_time'].max().reset_index(name='last_activity') features = pd.merge(features, last_activity, on='user_id') current_time = pd.Timestamp.now() features['last_activity_gap'] = (current_time - features['last_activity']).dt.days features['membership_duration'] = (current_time - features['join_date']).dt.days # 7. 添加行为统计特征 behavior_counts = pd.crosstab(df['user_id'], df['behavior_type']).reset_index() features = pd.merge(features, behavior_counts, on='user_id') # 8. 品类偏好特征 for cat in ['家电', '手机', '电脑', '数码', '家居']: cat_users = df[df['main_category'] == cat]['user_id'].unique() features[f'prefers_{cat}'] = np.where(features['user_id'].isin(cat_users), 1, 0) print(f"特征工程完成,共生成 {len(features.columns)} 个特征") # 添加数据验证 if validate_features(features): return features else: raise ValueError("特征工程验证失败!") def validate_features(features): """验证特征数据完整性""" required_columns = ['user_id', 'is_high_value', 'will_buy_high_end', 'will_renew_plus'] missing = [col for col in required_columns if col not in features.columns] if missing: print(f"错误: 缺失必要列: {missing}") return False print("特征工程验证通过,所有必要列均存在") return True # -------------------------- # 探索性数据分析 (EDA) # -------------------------- def perform_eda(df, features): """执行探索性数据分析""" print("\n开始探索性数据分析...") # 设置绘图风格 sns.set_style("whitegrid") plt.figure(figsize=(18, 12)) # 1. 用户行为类型分布 plt.subplot(2, 2, 1) behavior_counts = df['behavior_type'].value_counts() sns.barplot(x=behavior_counts.index, y=behavior_counts.values, palette="viridis") plt.title('用户行为类型分布') plt.ylabel('数量') # 2. PLUS会员与非会员客单价对比 plt.subplot(2, 2, 2) purchase_df = df[df['behavior_type'] == '购买'] sns.boxplot(x='is_plus_member', y='order_amount', data=purchase_df, palette="Set2") plt.title('PLUS会员 vs 非会员客单价对比') plt.xlabel('PLUS会员') plt.ylabel('订单金额') # 3. 物流评分与复购率关系 plt.subplot(2, 2, 3) # 计算复购率 repurchase_users = purchase_df.groupby('user_id').filter(lambda x: len(x) > 1)['user_id'].unique() purchase_df['is_repurchase'] = purchase_df['user_id'].isin(repurchase_users).astype(int) # 按物流评分分组计算复购率 delivery_repurchase = purchase_df.groupby('delivery_rating')['is_repurchase'].mean().reset_index() sns.lineplot(x='delivery_rating', y='is_repurchase', data=delivery_repurchase, marker='o', linewidth=2.5, color='darkorange') plt.title('物流评分对复购率的影响') plt.xlabel('物流评分') plt.ylabel('复购率') plt.ylim(0, 1) # 4. 高价值客户特征热力图 plt.subplot(2, 2, 4) corr_matrix = features[['active_days', 'promo_sensitivity', 'category_concentration', '购买', '加购', 'is_high_value']].corr() sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt=".2f") plt.title('行为特征相关性') plt.tight_layout() plt.savefig('results/eda_results.png', dpi=300) plt.show() # 5. 高价值客户人口统计特征 plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) sns.countplot(x='gender', hue='is_high_value', data=features, palette="Set1") plt.title('高价值客户性别分布') plt.xlabel('性别') plt.ylabel('数量') plt.subplot(1, 2, 2) sns.boxplot(x='is_high_value', y='age', data=features, palette="Set2") plt.title('高价值客户年龄分布') plt.xlabel('是否高价值客户') plt.ylabel('年龄') plt.tight_layout() plt.savefig('results/high_value_demographics.png', dpi=300) plt.show() print("EDA分析完成,结果保存至 results/ 目录") return True # -------------------------- # 预测模型构建 - 修复版本 # -------------------------- # ... 前面的代码保持不变 ... # -------------------------- # 预测模型构建 - 修复版本 # -------------------------- def build_prediction_models(features, target_column): """构建预测模型""" print(f"\n构建预测模型: {target_column}") # 1. 数据准备 # 选择特征 model_features = features.drop(['user_id', 'join_date', 'last_activity', 'will_buy_high_end', 'will_renew_plus'], axis=1, errors='ignore') # 处理分类变量 categorical_cols = ['gender', 'city'] model_features = pd.get_dummies(model_features, columns=categorical_cols, drop_first=True) # 定义目标变量 y = features[target_column] # 2. 划分训练集/测试集 X_train, X_test, y_train, y_test = train_test_split( model_features, y, test_size=0.25, random_state=42, stratify=y) # 3. 模型初始化 models = { '随机森林': RandomForestClassifier( n_estimators=150, max_depth=8, min_samples_split=10, class_weight='balanced', random_state=42 ), '逻辑回归': LogisticRegression( max_iter=1000, class_weight='balanced', penalty='l2', C=0.1, random_state=42, solver='liblinear' ) } # 4. 模型训练与评估 results = {} feature_importances = {} for name, model in models.items(): print(f"训练 {name} 模型...") start_time = time.time() model.fit(X_train, y_train) train_time = time.time() - start_time # 预测 y_pred = model.predict(X_test) # 关键指标计算 precision = precision_score(y_test, y_pred, zero_division=0) recall = recall_score(y_test, y_pred, zero_division=0) f1 = f1_score(y_test, y_pred, zero_division=0) # 尝试计算AUC,处理不支持的场景 try: y_proba = model.predict_proba(X_test)[:, 1] if hasattr(model, 'predict_proba') else [0]*len(y_test) auc = roc_auc_score(y_test, y_proba) if len(np.unique(y_test)) > 1 else 0.5 except Exception as e: print(f"警告: {name} 模型无法计算AUC: {e}") auc = 0.5 y_proba = np.zeros(len(y_test)) # 自定义加权MAE(高价商品权重更高) # 添加索引验证,确保不越界 valid_index = y_test.index.intersection(features.index) sample_weights = np.where(features.loc[valid_index, 'is_high_value'] == 1, 2.0, 1.0) # 确保长度匹配 y_proba = y_proba[:len(y_test)] mae = mean_absolute_error(y_test, y_proba, sample_weight=sample_weights) results[name] = { 'AUC': auc, '精确率': precision, '召回率': recall, 'F1分数': f1, '加权MAE': mae, '训练时间()': train_time } # 保存重要特征 - 修复缺失代码 if hasattr(model, 'feature_importances_'): feat_imp = pd.Series(model.feature_importances_, index=X_train.columns) feature_importances[name] = feat_imp # 可视化特征重要性 plt.figure(figsize=(10, 6)) feat_imp.nlargest(15).plot(kind='barh') plt.title(f'{name}特征重要性排名 (TOP 15)') plt.tight_layout() plt.savefig(f'results/{name}_{target_column}_feature_importance.png', dpi=200) plt.close() # 绘制ROC曲线 if auc > 0.5: fpr, tpr, thresholds = roc_curve(y_test, y_proba) plt.figure(figsize=(8, 6)) plt.plot(fpr, tpr, label=f'AUC = {auc:.2f}') plt.plot([0, 1], [0, 1], 'r--') plt.xlabel('假阳性率') plt.ylabel('真阳性率') plt.title(f'{name} ROC曲线') plt.legend(loc='lower right') plt.tight_layout() plt.savefig(f'results/{name}_{target_column}_roc_curve.png', dpi=200) plt.close() # 5. 打印并保存结果 print("\n模型评估结果:") results_df = pd.DataFrame(results).T print(results_df) results_df.to_csv(f'results/{target_column}_model_results.csv') return results, feature_importances # -------------------------- # 用户聚类分析 # -------------------------- def perform_clustering(features): """对用户进行聚类分析""" print("\n开始用户聚类分析...") # 选择特征 clustering_features = features.drop([ 'user_id', 'join_date', 'last_activity', 'will_buy_high_end', 'will_renew_plus' ], axis=1, errors='ignore') # 处理分类变量 categorical_cols = ['gender', 'city'] clustering_features = pd.get_dummies(clustering_features, columns=categorical_cols, drop_first=True) # 数据标准化 scaler = StandardScaler() scaled_data = scaler.fit_transform(clustering_features) # 寻找最佳聚类数量 inertias = [] for k in range(2, 11): kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(scaled_data) inertias.append(kmeans.inertia_) # 绘制肘部法则图 plt.figure(figsize=(8, 5)) plt.plot(range(2, 11), inertias, marker='o') plt.title('肘部法则 - 确定最佳聚类数量') plt.xlabel('聚类数量') plt.ylabel('内平方和') plt.tight_layout() plt.savefig('results/clustering_elbow_method.png', dpi=200) plt.show() # 使用最佳聚类数量进行聚类 optimal_k = 5 # 根据肘部法则选择 kmeans = KMeans(n_clusters=optimal_k, random_state=42, n_init=10) features['cluster'] = kmeans.fit_predict(scaled_data) # 分析聚类特征 cluster_profiles = features.groupby('cluster').mean(numeric_only=True).reset_index() # 可视化聚类中心特征 cluster_features = [ 'active_days', 'promo_sensitivity', 'category_concentration', '购买', '加购', 'membership_duration' ] plt.figure(figsize=(12, 8)) for i, feature in enumerate(cluster_features, 1): plt.subplot(2, 3, i) sns.barplot(x='cluster', y=feature, data=features, ci=None) plt.title(f'{feature} 按集群分布') plt.tight_layout() plt.savefig('results/cluster_characteristics.png', dpi=300) plt.show() # 保存聚类结果 features[['user_id', 'cluster']].to_csv('results/user_clusters.csv', index=False) print(f"聚类分析完成,用户被分为 {optimal_k} 个不同群体") return features # -------------------------- # 主执行流程 # -------------------------- if __name__ == "__main__": # 创建结果目录 os.makedirs('data', exist_ok=True) os.makedirs('results', exist_ok=True) # 生成模拟数据 if not os.path.exists('data/jd_simulated_data.csv'): df = generate_jd_simulation_data() else: # 避免日期转换问题 df = pd.read_csv('data/jd_simulated_data.csv') # 分别转换日期列 date_cols = ['behavior_time', 'join_date'] for col in date_cols: if col in df.columns: df[col] = pd.to_datetime(df[col]) print("加载现有模拟数据...") # 预处理与特征工程 features = preprocess_data(df) # 探索性数据分析 perform_eda(df, features) # 构建预测模型 model_results = {} feature_importances = {} # 1. 预测高价值客户 high_value_results, high_value_models = build_prediction_models(features, 'is_high_value') model_results['is_high_value'] = high_value_results # 2. 预测购买高端产品意愿 high_end_results, high_end_models = build_prediction_models(features, 'will_buy_high_end') model_results['will_buy_high_end'] = high_end_results # 3. 预测PLUS会员续费意愿 renew_plus_results, renew_plus_models = build_prediction_models(features, 'will_renew_plus') model_results['will_renew_plus'] = renew_plus_results # 用户聚类分析 clustered_features = perform_clustering(features) print("\n⭐️ 系统执行完成!模型结果已保存至 results/ 目录") 这个数据模拟的太完美了,能帮我模拟的数据实际一点吗,结果不要太准确
06-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值