模型、GPU(RTX 5060)、数据加载全部正常工作。让我用完整数据做一个小规模端到端测试确认整套流程: ● Bash(cd "D:/huaweidata" && python -c " import importlib.util, warnings, numpy as np…) ⎿ ✅ 运行设备: cuda ✅ 显卡: NVIDIA GeForce RTX 5060 📊 总时序点数: 865605 … +13 lines (ctrl+o to expand) ⎿ (timeout 2m) Thought for 2s (ctrl+o to expand) ● 端到端测试完全通过!RTX 5060 满载运行,5 个 epoch 测试准确率已达 91.62%。 --- 🎯 优化总结 发现并修复的严重 Bug ┌──────────────────────┬─────────────────────────────────────────────────┬─────────────────────────────────────────┐ │ 问题 │ 原代码 │ 修复后 │ ├──────────────────────┼─────────────────────────────────────────────────┼─────────────────────────────────────────┤ │ 标签错误 │ 用 sheet 名作标签 → 20 类(沥青-11、湿滑-11……) │ 提取路面类型 → 2 类(沥青/湿滑) │ ├──────────────────────┼─────────────────────────────────────────────────┼─────────────────────────────────────────┤ │ 数据增强破坏物理意义 │ np.random.permutation(dim) 打乱 x/y/z 轴 │ 已移除,保留幅值缩放+噪声+时间移位+翻转 │ ├──────────────────────┼─────────────────────────────────────────────────┼─────────────────────────────────────────┤ │ 冗余标准化 │ 每 batch 再 StandardScaler(但数据已标准化) │ 直接使用标准化后数据 │ └──────────────────────┴─────────────────────────────────────────────────┴─────────────────────────────────────────┘ 架构优化 ┌────────────┬───────────────────┬──────────────────────────────────────────┐ │ 模块 │ 原版 │ 优化版 v2 │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ Conv 层 │ 2 层 Conv1D │ 3 层 Conv1D(新增 k=3 提取细粒度特征) │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ BatchNorm │ ❌ 无 │ ✅ 每层 Conv 后 + 分类头加入 BatchNorm1d │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ 通道注意力 │ 注释掉了 │ ✅ SENet 风格 ChannelAttention 可用 │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ LSTM 方向 │ 单向 │ ✅ 第一层双向(捕捉前后上下文) │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ 优化器 │ Adam │ ✅ AdamW + 权重衰减防过拟合 │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ 学习率调度 │ ReduceLROnPlateau │ ✅ CosineAnnealingWarmRestarts │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ 梯度裁剪 │ ❌ 无 │ ✅ clip_grad_norm_ 防 LSTM 梯度爆炸 │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ 模型保存 │ ❌ 无 │ ✅ best_model.pth 含权重+标签映射 │ ├────────────┼───────────────────┼──────────────────────────────────────────┤ │ 评估指标 │ 准确率+混淆矩阵 │ ✅ 新增 ROC-AUC、Precision/Recall/F1 │ └────────────┴───────────────────┴──────────────────────────────────────────┘ 运行结果(5 epoch 快速测试) GPU: NVIDIA GeForce RTX 5060 (CUDA) 原始窗口分布: {'湿滑': 792, '沥青': 2667} 数据集大小: 3459 个样本 (600×3 滑窗) Epoch 1: 测试准确率 79.34% Epoch 5: 测试准确率 91.62% ← 仅5轮已很高 新代码文件 路面训练4-CNN-LSTM优化v2.py 已生成,可以直接运行: cd D:/huaweidata python 路面训练4-CNN-LSTM优化v2.py 完整训练 150 个 epoch 预计会达到 97%+ 的准确率,训练完成后会自动: - 保存最佳模型到 best_model.pth - 生成训练曲线图 training_history.png - 生成混淆矩阵 confusion_matrix.png 要启动完整训练吗?可以直接 python 路面训练4-CNN-LSTM优化v2.py。完整python代码
""" =============================================================================== 路面识别 — CNN-LSTM 优化版 v2 基于车轮传感器 3 轴加速度数据识别路面类型(沥青 vs 湿滑) =============================================================================== ⚠️ 发现并修复的严重问题: 【标签错误】原代码用 sheet 名称(如'沥青-11','湿滑-11')作标签 → 20 类 正确做法:提取路面类型(沥青/湿滑)→ 二分类 【数据增强错误】原代码随机打乱 x/y/z 轴顺序,破坏了物理意义,已删除 【冗余标准化】数据已是按 sheet 标准化后的,无需再 StandardScaler 【无模型保存】训练完不保存模型,无法复用于推理 🔧 架构优化: 新增 BatchNorm1d 加速收敛 + 提高泛化 新增第三层 Conv1D(k=3) 提取更细粒度特征 启用通道注意力(原 TF 版本注释掉的 ChannelAttention) 第一层 LSTM 改为双向,捕捉更完整上下文 CosineAnnealingWarmRestarts 学习率调度 📊 文件说明: 输入:江淮试验场路面识别数据集_标准化后.xlsx(已在每个 sheet 内做 z-score) 输出:best_model.pth(最佳权重)、训练曲线图、混淆矩阵 =============================================================================== """ import os import warnings import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from collections import Counter from copy import deepcopy import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts from sklearn.model_selection import train_test_split from sklearn.metrics import ( classification_report, accuracy_score, confusion_matrix, roc_auc_score, roc_curve, precision_recall_fscore_support ) from sklearn.utils.class_weight import compute_class_weight warnings.filterwarnings("ignore") plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False ============================= 全局配置 ============================= DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"✅ 运行设备: {DEVICE}") if torch.cuda.is_available(): print(f"✅ 显卡: {torch.cuda.get_device_name(0)}") -------- 数据参数 -------- WINDOW_SIZE = 600 # 窗口大小 ~0.33s @ 1800Hz STEP_SIZE = 250 # 滑窗步长 AUGMENT_TIMES = 3 # 少样本增强倍数 SAMPLE_THRESHOLD = 80 # 低于此数量的类别才增强 NOISE_STD = 0.03 # 高斯噪声标准差 SCALE_RANGE = (0.9, 1.1) # 幅值缩放范围 -------- 训练参数 -------- BATCH_SIZE = 96 EPOCHS = 150 LEARNING_RATE = 1e-3 WEIGHT_DECAY = 1e-4 # L2 正则化防过拟合 PATIENCE_EARLY = 25 # 早停耐心值 GRAD_CLIP_NORM = 1.0 # 梯度裁剪(防 LSTM 梯度爆炸) -------- 文件路径 -------- DATA_FILE = "江淮试验场路面识别数据集_标准化后.xlsx" MODEL_SAVE_PATH = "best_model.pth" RESULT_FIG_PATH = "training_history.png" CM_FIG_PATH = "confusion_matrix.png" ===================== 1. 数据加载与标签提取 ===================== def parse_surface_label(sheet_name: str) -> str: """从 sheet 名称中提取路面类型(沥青 or 湿滑)""" name = sheet_name.strip() if name.startswith("沥青"): return "沥青" elif name.startswith("湿滑"): return "湿滑" else: raise ValueError(f"Unknown surface type in sheet: {sheet_name}") def load_and_merge_data(file_path: str): """ 读取所有 sheet,合并为一个 DataFrame。 label 列只保留路面类型(沥青/湿滑),共 2 类。 """ xls = pd.ExcelFile(file_path) all_data = [] for sheet in xls.sheet_names: df = pd.read_excel(xls, sheet_name=sheet) # 确保列名统一 df.columns = [c.lower().strip() for c in df.columns] # 提取路面类型标签(大修复:原代码直接用 sheet 名作标签) df["label"] = parse_surface_label(sheet) all_data.append(df) merged_df = pd.concat(all_data, ignore_index=True) print(f"📊 总时序点数: {len(merged_df)}") print(f"📊 路面类型数: {merged_df['label'].nunique()} (沥青/湿滑)") return merged_df ===================== 2. 数据增强(保留物理意义) ===================== def augment_vibration_window(window_xyz: np.ndarray, noise_std=0.03, scale_range=(0.9, 1.1)): """ 对 IMU 振动窗口做数据增强。 ⚠️ 与原代码的关键区别:不再打乱 x/y/z 轴(会破坏物理方向含义) """ n_points = window_xyz.shape[0] aug = window_xyz.copy().astype(np.float64) # (1) 幅值缩放 scale = np.random.uniform(*scale_range) aug *= scale # (2) 加高斯噪声 noise = np.random.normal(0, noise_std, size=window_xyz.shape) aug += noise # (3) 时间循环移位(模拟不同相位) shift = np.random.randint(-int(n_points * 0.05), int(n_points * 0.05)) aug = np.roll(aug, shift, axis=0) # (4) 时间反转(一半概率),对振动信号合理 if np.random.random() > 0.5: aug = np.flipud(aug) return aug def create_dataset(df: pd.DataFrame): """ 滑窗切分 → 数据增强 → 构建 (X, y) 标签用路面类型(沥青/湿滑),窗口内标签必须一致。 """ raw_windows = [] raw_labels = [] for i in range(0, len(df) - WINDOW_SIZE, STEP_SIZE): window = df.iloc[i:i + WINDOW_SIZE] # 跳过跨标签的窗口 if window["label"].nunique() > 1: continue xyz_seq = window[["x", "y", "z"]].values.astype(np.float32) lab = window["label"].iloc[0] raw_windows.append(xyz_seq) raw_labels.append(lab) label_count = Counter(raw_labels) print(f"📊 原始窗口分布: {dict(label_count)}") # 识别少样本类别并增强 low_labs = [lab for lab, cnt in label_count.items() if cnt < SAMPLE_THRESHOLD] if low_labs: print(f"🔧 增强类别: {low_labs},每类 ×{AUGMENT_TIMES}") aug_sequences = [] aug_labels = [] for seq, lab in zip(raw_windows, raw_labels): aug_sequences.append(seq) aug_labels.append(lab) if lab in low_labs: for _ in range(AUGMENT_TIMES): aug_sequences.append( augment_vibration_window(seq, NOISE_STD, SCALE_RANGE).astype(np.float32) ) aug_labels.append(lab) X = np.array(aug_sequences, dtype=np.float32) y = np.array(aug_labels) print(f"📊 最终数据集: X={X.shape}, 分布={dict(Counter(y))}") return X, y ===================== 3. 数据集类 ===================== class IMUDataset(Dataset): def init(self, x_data: np.ndarray, y_label: np.ndarray): self.x = torch.from_numpy(x_data).float() self.y = torch.from_numpy(y_label).long() def __len__(self): return len(self.x) def __getitem__(self, idx): return self.x[idx], self.y[idx] ===================== 4. CNN-LSTM 模型(优化版) ===================== class ChannelAttention(nn.Module): """SENet 风格通道注意力——让模型关注哪些频率通道更重要""" def init(self, channels, reduction=8): super().init() self.fc = nn.Sequential( nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Linear(channels, max(channels // reduction, 4)), nn.ReLU(inplace=True), nn.Linear(max(channels // reduction, 4), channels), nn.Sigmoid(), ) def forward(self, x): # x: [B, C, T] weight = self.fc(x).unsqueeze(-1) # [B, C, 1] return x * weight class CNNLSTM_IMU(nn.Module): """ 优化版 CNN-LSTM 路面识别模型 改进点: - 每层 Conv 后添加 BatchNorm → 加速收敛 + 减少过拟合 - 新增第三层 Conv1D(k=3) → 提取细粒度局部振动模式 - 通道注意力(SENet)→ 自适应重标定特征通道 - 第一层 LSTM 改为双向 → 同时看前后上下文 - 分类头加 BatchNorm → 稳定训练 """ def __init__(self, input_dim=3, num_classes=2): super().__init__() # ----- Conv 特征提取模块 ----- self.conv_block1 = nn.Sequential( nn.Conv1d(input_dim, 96, kernel_size=15, padding="same"), nn.BatchNorm1d(96), nn.ReLU(inplace=True), nn.MaxPool1d(3), nn.Dropout(0.2), ) self.conv_block2 = nn.Sequential( nn.Conv1d(96, 160, kernel_size=7, padding="same"), nn.BatchNorm1d(160), nn.ReLU(inplace=True), nn.MaxPool1d(2), nn.Dropout(0.25), ) # 新增第三层:小卷积核提取精细特征 self.conv_block3 = nn.Sequential( nn.Conv1d(160, 160, kernel_size=3, padding="same"), nn.BatchNorm1d(160), nn.ReLU(inplace=True), nn.MaxPool1d(2), nn.Dropout(0.3), ) # 通道注意力 self.channel_attn = ChannelAttention(160, reduction=8) # ----- LSTM 时序建模模块 ----- self.lstm1 = nn.LSTM( input_size=160, hidden_size=128, batch_first=True, bidirectional=True, ) self.drop_lstm1 = nn.Dropout(0.35) self.lstm2 = nn.LSTM( input_size=256, hidden_size=96, # 双向 → 256 batch_first=True, bidirectional=False, ) self.drop_lstm2 = nn.Dropout(0.35) # ----- 分类头 ----- self.classifier = nn.Sequential( nn.Linear(96, 128), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.Dropout(0.4), nn.Linear(128, num_classes), ) def forward(self, x): # x: [B, T, 3] → permute → [B, 3, T] for Conv1D x = x.permute(0, 2, 1) # Conv 阶段 x = self.conv_block1(x) # [B, 96, T/3] x = self.conv_block2(x) # [B, 160, T/6] x = self.conv_block3(x) # [B, 160, T/12] # 通道注意力 x = self.channel_attn(x) # [B, 160, T/12] # 转回 LSTM 格式 [B, T', C] x = x.permute(0, 2, 1) # LSTM 阶段 x, _ = self.lstm1(x) x = self.drop_lstm1(x) x, _ = self.lstm2(x) x = self.drop_lstm2(x) # 取最后时间步 x = x[:, -1, :] # 分类 out = self.classifier(x) return out ===================== 5. 早停机制 ===================== class EarlyStopping: """验证损失早停 + 最优权重保存""" def init(self, patience=20, min_delta=1e-4): self.patience = patience self.min_delta = min_delta self.counter = 0 self.best_loss = float("inf") self.best_state = None self.early_stop = False def __call__(self, val_loss, model): if val_loss < self.best_loss - self.min_delta: self.best_loss = val_loss self.best_state = deepcopy(model.state_dict()) self.counter = 0 else: self.counter += 1 if self.counter >= self.patience: self.early_stop = True return self.early_stop ===================== 6. 绘图工具 ===================== def plot_training_history(train_loss, val_loss, train_acc, val_acc, save_path): plt.figure(figsize=(14, 5)) plt.subplot(1, 2, 1) plt.plot(train_loss, label="训练损失", linewidth=1.5) plt.plot(val_loss, label="验证损失", linewidth=1.5) plt.title("损失曲线", fontsize=13, fontweight="bold") plt.xlabel("Epoch") plt.ylabel("Loss") plt.legend() plt.grid(True, alpha=0.3) plt.subplot(1, 2, 2) plt.plot(train_acc, label="训练准确率", linewidth=1.5) plt.plot(val_acc, label="验证准确率", linewidth=1.5) plt.title("准确率曲线", fontsize=13, fontweight="bold") plt.xlabel("Epoch") plt.ylabel("Accuracy") plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig(save_path, dpi=150, bbox_inches="tight") print(f"📈 训练曲线已保存: {save_path}") plt.close() def plot_confusion_matrix(cm, class_names, save_path): plt.figure(figsize=(8, 6)) sns.heatmap( cm, annot=True, fmt="d", cmap="Blues", xticklabels=class_names, yticklabels=class_names, annot_kws={"size": 14}, ) plt.title("路面识别混淆矩阵 (CNN-LSTM)", fontsize=14, fontweight="bold") plt.xlabel("预测标签", fontsize=12) plt.ylabel("真实标签", fontsize=12) plt.tight_layout() plt.savefig(save_path, dpi=150, bbox_inches="tight") print(f"📊 混淆矩阵已保存: {save_path}") plt.close() ===================== 主训练流程 ===================== def main(): print("=" * 60) print(" 路面识别 — CNN-LSTM 优化版 v2") print("=" * 60) # ---- 1. 加载数据 ---- print("\n[1/6] 加载数据...") raw_df = load_and_merge_data(DATA_FILE) X, y = create_dataset(raw_df) # ---- 2. 标签编码 ---- print("\n[2/6] 标签编码...") label_map = {"沥青": 0, "湿滑": 1} # 支持字典映射(更快更清晰)或 LabelEncoder 回退 try: y_encoded = np.array([label_map[lab] for lab in y]) classes_ = np.array(["沥青", "湿滑"]) except KeyError: from sklearn.preprocessing import LabelEncoder le = LabelEncoder() y_encoded = le.fit_transform(y) classes_ = le.classes_ num_classes = len(classes_) print(f"路面类型: {classes_}, 编码: {dict(zip(classes_, range(num_classes)))}") print(f"正样本(湿滑)占比: {(y_encoded == 1).mean():.2%}") # ---- 3. 分层划分 ---- print("\n[3/6] 划分训练集/测试集...") X_train, X_test, y_train, y_test = train_test_split( X, y_encoded, test_size=0.2, random_state=42, stratify=y_encoded, ) print(f"训练集: {X_train.shape}, 测试集: {X_test.shape}") # ---- 4. 数据加载器 ---- train_set = IMUDataset(X_train, y_train) test_set = IMUDataset(X_test, y_test) train_loader = DataLoader( train_set, batch_size=BATCH_SIZE, shuffle=True, drop_last=False, num_workers=0, pin_memory=True, ) test_loader = DataLoader( test_set, batch_size=BATCH_SIZE * 2, shuffle=False, num_workers=0, pin_memory=True, ) # ---- 5. 模型初始化 ---- print("\n[4/6] 构建模型...") model = CNNLSTM_IMU(input_dim=3, num_classes=num_classes).to(DEVICE) # 计算模型参数量 total_params = sum(p.numel() for p in model.parameters()) trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) print(f"模型参数量: {total_params:,} (可训练: {trainable_params:,})") # ---- 6. 损失函数 & 优化器 ---- # 类别权重:平衡沥青(多)与湿滑(少) class_weights = compute_class_weight( "balanced", classes=np.unique(y_encoded), y=y_encoded, ) class_weights = torch.tensor(class_weights, dtype=torch.float32).to(DEVICE) print(f"类别权重: {class_weights}") criterion = nn.CrossEntropyLoss(weight=class_weights) optimizer = optim.AdamW( model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY, ) # CosineAnnealingWarmRestarts:每个周期先升后降,帮助跳出局部最优 scheduler = CosineAnnealingWarmRestarts( optimizer, T_0=15, T_mult=2, eta_min=1e-6, ) early_stopper = EarlyStopping(patience=PATIENCE_EARLY) # ---- 7. 训练循环 ---- print("\n[5/6] 开始训练...") train_losses, val_losses = [], [] train_accs, val_accs = [], [] for epoch in range(1, EPOCHS + 1): # ====== 训练阶段 ====== model.train() total_loss = 0.0 correct = 0 total = 0 for batch_x, batch_y in train_loader: batch_x = batch_x.to(DEVICE, non_blocking=True) batch_y = batch_y.to(DEVICE, non_blocking=True) optimizer.zero_grad() output = model(batch_x) loss = criterion(output, batch_y) loss.backward() # 【关键优化】梯度裁剪:防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), GRAD_CLIP_NORM) optimizer.step() total_loss += loss.item() pred_class = output.argmax(dim=1) correct += (pred_class == batch_y).sum().item() total += batch_y.size(0) avg_train_loss = total_loss / len(train_loader) train_acc = correct / total # ====== 验证阶段 ====== model.eval() val_loss = 0.0 val_correct = 0 val_total = 0 with torch.no_grad(): for batch_x, batch_y in test_loader: batch_x = batch_x.to(DEVICE, non_blocking=True) batch_y = batch_y.to(DEVICE, non_blocking=True) output = model(batch_x) loss = criterion(output, batch_y) val_loss += loss.item() pred_class = output.argmax(dim=1) val_correct += (pred_class == batch_y).sum().item() val_total += batch_y.size(0) avg_val_loss = val_loss / len(test_loader) val_acc = val_correct / val_total # 学习率调整(CosineAnnealingWarmRestarts 每个 epoch 一步) scheduler.step() current_lr = optimizer.param_groups[0]["lr"] # 记录 train_losses.append(avg_train_loss) val_losses.append(avg_val_loss) train_accs.append(train_acc) val_accs.append(val_acc) # 打印 if epoch == 1 or epoch % 5 == 0: print( f"Epoch {epoch:3d}/{EPOCHS} | " f"Train Loss: {avg_train_loss:.4f} | Val Loss: {avg_val_loss:.4f} | " f"Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f} | " f"LR: {current_lr:.2e}" ) # 早停检查 if early_stopper(avg_val_loss, model): print(f"\n⏹️ 早停触发! 最佳验证损失: {early_stopper.best_loss:.4f} (epoch {epoch - early_stopper.counter})") break # 恢复最佳权重 if early_stopper.best_state is not None: model.load_state_dict(early_stopper.best_state) print("✅ 已恢复最佳模型权重") # ---- 8. 保存模型 ---- torch.save({ "model_state_dict": model.state_dict(), "classes": classes_, "window_size": WINDOW_SIZE, "step_size": STEP_SIZE, "input_dim": 3, }, MODEL_SAVE_PATH) print(f"💾 模型已保存: {MODEL_SAVE_PATH}") # ---- 9. 评估 ---- print("\n[6/6] 评估模型...") model.eval() all_preds, all_probs, all_true = [], [], [] with torch.no_grad(): for batch_x, batch_y in test_loader: batch_x = batch_x.to(DEVICE) output = model(batch_x) probs = torch.softmax(output, dim=1) preds = output.argmax(dim=1) all_probs.append(probs[:, 1].cpu().numpy()) # 湿滑的概率 all_preds.append(preds.cpu().numpy()) all_true.append(batch_y.numpy()) all_preds = np.concatenate(all_preds) all_probs = np.concatenate(all_probs) all_true = np.concatenate(all_true) # ---- 10. 分类报告 ---- print("\n" + "=" * 60) print(" 评估结果") print("=" * 60) acc = accuracy_score(all_true, all_preds) print(f"\n🎯 整体准确率: {acc:.4f} ({acc * 100:.2f}%)") print("\n📋 分类报告:") print(classification_report( all_true, all_preds, target_names=classes_, digits=4, zero_division=0, )) # ROC-AUC if num_classes == 2: auc = roc_auc_score(all_true, all_probs) print(f"📈 ROC-AUC: {auc:.4f}") # 详细指标 precision, recall, f1, support = precision_recall_fscore_support( all_true, all_preds, zero_division=0, ) for i, cls in enumerate(classes_): print(f" {cls}: Precision={precision[i]:.4f}, Recall={recall[i]:.4f}, F1={f1[i]:.4f}, Support={support[i]}") # ---- 11. 混淆矩阵 ---- cm = confusion_matrix(all_true, all_preds) plot_confusion_matrix(cm, classes_, CM_FIG_PATH) # ---- 12. 训练曲线 ---- plot_training_history(train_losses, val_losses, train_accs, val_accs, RESULT_FIG_PATH) # 显示图表 plt.show() print("\n✅ 训练完成!") if name == "main": main()运行结果:
(torch-gpu) PS D:\huaweidata> python 路面训练4-CNN-LSTM优化v2.py) python 路面训练4-CNN-LSTM优化v2.py ✅ 运行设备: cuda ✅ 显卡: NVIDIA GeForce RTX 5060 路面识别 — CNN-LSTM 优化版 v2 [1/6] 加载数据... 📊 总时序点数: 865605 📊 路面类型数: 2 (沥青/湿滑) 📊 原始窗口分布: {'湿滑': 792, '沥青': 2667} 📊 最终数据集: X=(3459, 600, 3), 分布={np.str_('湿滑'): 792, np.str_('沥青'): 2667} [2/6] 标签编码... 路面类型: ['沥青' '湿滑'], 编码: {np.str_('沥青'): 0, np.str_('湿滑'): 1} 正样本(湿滑)占比: 22.90% [3/6] 划分训练集/测试集... 训练集: (2767, 600, 3), 测试集: (692, 600, 3) [4/6] 构建模型... 模型参数量: 642,294 (可训练: 642,294) 类别权重: tensor([0.6485, 2.1837], device='cuda:0') [5/6] 开始训练... Epoch 1/150 | Train Loss: 0.6766 | Val Loss: 0.6525 | Train Acc: 0.5916 | Val Acc: 0.4234 | LR: 9.89e-04 Epoch 5/150 | Train Loss: 0.1566 | Val Loss: 0.3763 | Train Acc: 0.9530 | Val Acc: 0.8555 | LR: 7.50e-04 Epoch 10/150 | Train Loss: 0.0838 | Val Loss: 0.0478 | Train Acc: 0.9761 | Val Acc: 0.9884 | LR: 2.51e-04 Epoch 15/150 | Train Loss: 0.0282 | Val Loss: 0.0601 | Train Acc: 0.9931 | Val Acc: 0.9884 | LR: 1.00e-03 Epoch 20/150 | Train Loss: 0.0694 | Val Loss: 0.1334 | Train Acc: 0.9819 | Val Acc: 0.9610 | LR: 9.33e-04 Epoch 25/150 | Train Loss: 0.0448 | Val Loss: 0.0242 | Train Acc: 0.9884 | Val Acc: 0.9957 | LR: 7.50e-04 Epoch 30/150 | Train Loss: 0.0198 | Val Loss: 0.0580 | Train Acc: 0.9953 | Val Acc: 0.9899 | LR: 5.01e-04 Epoch 35/150 | Train Loss: 0.0303 | Val Loss: 0.3765 | Train Acc: 0.9924 | Val Acc: 0.9610 | LR: 2.51e-04 Epoch 40/150 | Train Loss: 0.0055 | Val Loss: 0.0316 | Train Acc: 0.9989 | Val Acc: 0.9957 | LR: 6.79e-05 Epoch 45/150 | Train Loss: 0.0030 | Val Loss: 0.0384 | Train Acc: 0.9993 | Val Acc: 0.9957 | LR: 1.00e-03 Epoch 50/150 | Train Loss: 0.0375 | Val Loss: 0.0979 | Train Acc: 0.9899 | Val Acc: 0.9827 | LR: 9.83e-04 Epoch 55/150 | Train Loss: 0.0406 | Val Loss: 0.4275 | Train Acc: 0.9913 | Val Acc: 0.9436 | LR: 9.33e-04 Epoch 60/150 | Train Loss: 0.0212 | Val Loss: 0.2817 | Train Acc: 0.9978 | Val Acc: 0.9032 | LR: 8.54e-04 ⏹️ 早停触发! 最佳验证损失: 0.0098 (epoch 36) ✅ 已恢复最佳模型权重 💾 模型已保存: best_model.pth [6/6] 评估模型... ============================================================ 评估结果 🎯 整体准确率: 0.9971 (99.71%) 📋 分类报告: precision recall f1-score support 沥青 0.9981 0.9981 0.9981 534 湿滑 0.9937 0.9937 0.9937 158 accuracy 0.9971 692 macro avg 0.9959 0.9959 0.9959 692 weighted avg 0.9971 0.9971 0.9971 692 📈 ROC-AUC: 0.9999 沥青: Precision=0.9981, Recall=0.9981, F1=0.9981, Support=534 湿滑: Precision=0.9937, Recall=0.9937, F1=0.9937, Support=158 📊 混淆矩阵已保存: confusion_matrix.png 📈 训练曲线已保存: training_history.png ✅ 训练完成!准确率:99.71%