恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

UCF101视频动作识别实战:CRNN端到端训练与预测

  • 首页
  • 资讯中心
  • /
  • UCF101视频动作识别实战:CRNN端到端训练与预测

相关资讯

Java群发消息API批处理:分片发送与失败重试机制详解 2026/10/11 13:42:51
手枪机枪刀检测数据集:VOC+YOLO双格式5990张,YOLOv8训练实战 2026/10/11 13:42:51
AI辅助MBA论文写作:从选题到定稿的实用指南 2026/10/11 13:37:51

最新资讯

普通人也能看懂的MCP入门指南!从Stdio到JSON的6个实战案例(含TaoToken配置)
二手房交易管理系统数据库设计:从ER图到SQL实现全攻略
无人叉车落地实战:破解招工难、定位偏、堆叠歪、管理盲
【更新至2025年】2014-2025年企业绿色供应链CITI指数数据
DeepSeek Harness 第三方模型配置完整教程:桌面端、网页端与兼容接口实测
别再写贫血模型了:把对象当人,用职责协作重构OOP代码

今日推荐

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

UCF101视频动作识别实战:CRNN端到端训练与预测

发布时间:2026/10/11 13:42:51
UCF101视频动作识别实战:CRNN端到端训练与预测 简介本资源是一套面向深度学习与计算机视觉初学者及进阶学习者的UCF101视频动作识别完整实践方案聚焦CRNN模型在人体动作识别任务中的端到端实现。资源包含数据预处理、模型构建CNNLSTM、训练监控、预测分析与结果可视化全流程代码与中间产物特别适合课程设计、科研入门与竞赛复现场景。压缩包共38个文件以12个Jupyter Notebook含main.ipynb主流程、prediction.ipynb预测分析、replot_loss.ipynb绘图复现等为核心辅以8个npy训练/测试指标文件、6个Python工具脚本如load_data.py、functions.py、3个PNG图表含loss_UCF101_CRNN.png、fig_UCF101_CRNN.png及pkl预测结果文件整体仅2.57MB轻量易下载。目前已获1154人学习下载读者可直接运行复现120轮训练过程、调用已保存的CRNN权重进行视频动作预测、查看混淆分析与错判样本wrong_predictions.pkl、复现损失曲线与准确率变化趋势并基于UCF101actions.pkl动作标签映射完成结果解读。1. UCF101视频动作识别实战用CRNN跑通端到端预测 pipeline不是调包是真正看懂每一帧怎么被时序建模你手头有一段3秒的篮球投篮视频想让模型告诉你“这是投篮”而不是“挥臂”或“跳跃”——UCF101就是干这个的它包含101类人类动作如骑自行车、刷牙、跳绳每类上百个真实拍摄短视频帧率稳定、视角多样、光照自然。但问题来了CNN只认单帧RNN只吃向量而动作本质是空间特征随时间演化的轨迹。CRNNConvolutional Recurrent Neural Network正是为这事生的——前端CNN抽帧特征后端RNN常用LSTM/GRU建模帧间依赖最后接分类头输出动作标签。这不是论文玩具而是工业界视频理解Pipeline的基石模块安防行为分析、健身动作纠错、手术操作评估都绕不开它。本文不讲CRNN公式推导只聚焦一件事从UCF101原始视频下载开始到本地跑出可验证的预测结果含置信度类别名全程可复现、参数可调、报错可查。适合刚跑通ImageNet分类、想跨入视频理解的新手也适合需要快速验证CRNN baseline的老手——所有命令、路径、参数值、文件结构都按2024年主流PyTorch 2.1 torchvision 0.16环境实测过。2. 从UCF101原始视频到CRNN可训练张量数据预处理四步法UCF101官网提供三种格式原始AVI视频、预提取的光流帧、以及已裁剪的RGB帧。新手务必选原始AVI——虽然下载慢1.7GB、解码费劲但它保留了全部信息后续可灵活做数据增强、时序采样、分辨率调整。若直接用光流帧等于把运动信息“预设”进数据反而掩盖CRNN中RNN层的真实建模能力调试时容易误判是模型问题还是数据问题。2.1 下载与解压避开官网反爬与校验陷阱UCF101官网crcv.ucf.edu/datasets要求邮箱注册后获取下载链接但实际镜像源更可靠。我们采用GitHub社区维护的 ucf101-downloader 非官方但经大量用户验证。注意不要用wget直接抓官网链接会返回HTML而非视频文件。# 创建工作目录 mkdir -p ucf101_data/{raw,frames,features} cd ucf101_data # 使用pip安装下载器Python 3.8 pip install githttps://github.com/okankop/ucf101-downloader.git # 执行下载自动校验MD5耗时约25分钟 ucf101_downloader --data_dir raw --split 1 --quality hd提示--split 1指使用官方第一份训练/测试划分共3份这是论文对比基准--quality hd下载720p视频比默认480p更利于细节捕捉。下载完成后raw/下应有101个子目录如ApplyEyeMakeup/,Archery/每个目录含100个.avi文件总大小约1.7GB。2.2 视频帧提取用OpenCV精准控制采样策略CRNN对输入序列长度敏感太短丢时序太长显存炸。UCF101视频时长不一1~10秒必须统一为固定帧数。常见做法是等间隔采样如取16帧但暴力截断会丢失起止动作。我们采用中心裁剪动态填充先取视频中间连续片段再按需插值或重复帧。# save as preprocess/extract_frames.py import cv2 import os import numpy as np from pathlib import Path def extract_frames(video_path: str, output_dir: str, target_frames: int 16): cap cv2.VideoCapture(video_path) total_frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 策略取中间连续target_frames帧不足则循环填充超长则等距采样 if total_frames target_frames: # 不足循环复制末尾帧比随机重复更合理 frame_indices list(range(total_frames)) [total_frames-1] * (target_frames - total_frames) else: # 超长取中间连续段再等距采样避免首尾抖动 start_idx (total_frames - target_frames) // 2 frame_indices np.linspace(start_idx, start_idx target_frames - 1, target_frames, dtypeint) os.makedirs(output_dir, exist_okTrue) for i, idx in enumerate(frame_indices): cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ret, frame cap.read() if ret: # 统一分辨率先缩放至256x256再中心裁剪224x224适配ResNet主干 frame cv2.resize(frame, (256, 256)) frame frame[16:240, 16:240] # 中心裁剪 cv2.imwrite(f{output_dir}/frame_{i:04d}.jpg, frame) cap.release() # 批量处理示例处理前5个视频 for video_path in list(Path(raw/ApplyEyeMakeup).glob(*.avi))[:5]: out_dir fframes/{video_path.parent.name}/{video_path.stem} extract_frames(str(video_path), out_dir)逻辑说明target_frames16是CRNN经典输入长度兼顾时序建模能力与显存cv2.resize后立即center crop是关键避免resize导致的形变且224x224是ImageNet预训练模型标准输入帧索引用np.linspace而非range(0, total, step)确保采样点在时间轴上均匀分布不因视频帧率差异偏移。2.3 构建PyTorch Dataset支持时序一致性与内存优化视频数据集不能像图像那样直接torchvision.datasets.ImageFolder——因为一“个样本”是一组帧16张图需保证它们来自同一视频、顺序不乱。我们自定义VideoDataset核心是重写__getitem__# save as dataset/video_dataset.py import torch from torch.utils.data import Dataset from torchvision import transforms from pathlib import Path import random class VideoDataset(Dataset): def __init__(self, root_dir: str, transformNone, clip_len16, trainTrue): self.root_dir Path(root_dir) self.transform transform or self.default_transform() self.clip_len clip_len self.train train # 构建路径列表每个元素是 (视频帧目录路径, 标签索引) self.samples [] self.class_to_idx {} for idx, class_dir in enumerate(sorted(self.root_dir.iterdir())): if not class_dir.is_dir(): continue self.class_to_idx[class_dir.name] idx for video_frame_dir in class_dir.iterdir(): if video_frame_dir.is_dir(): self.samples.append((video_frame_dir, idx)) # 划分训练/测试用UCF101官方split1的trainlist01.txt if train: with open(ucfTrainTestlist/trainlist01.txt) as f: train_files [line.strip().split()[0] for line in f] self.samples [s for s in self.samples if s[0].parent.name / s[0].name in train_files] else: with open(ucfTrainTestlist/testlist01.txt) as f: test_files [line.strip() for line in f] self.samples [s for s in self.samples if s[0].parent.name / s[0].name in test_files] def default_transform(self): return transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def __len__(self): return len(self.samples) def __getitem__(self, idx): frame_dir, label self.samples[idx] # 按文件名排序读取帧确保时序 frame_paths sorted(list(frame_dir.glob(frame_*.jpg))) # 随机采样起始点训练时增强时序鲁棒性 if self.train and len(frame_paths) self.clip_len: start_idx random.randint(0, len(frame_paths) - self.clip_len) frame_paths frame_paths[start_idx:start_idx self.clip_len] frames [] for frame_path in frame_paths: img Image.open(frame_path).convert(RGB) frames.append(self.transform(img)) # 堆叠为 (C, T, H, W) - CRNN输入格式 video_tensor torch.stack(frames, dim1) # [3, 16, 224, 224] return video_tensor, label参数说明clip_len16必须与extract_frames.py中一致否则维度错配trainlist01.txt和testlist01.txt从UCF101官网ucfTrainTestlist.zip下载解压到项目根目录torch.stack(frames, dim1)是关键将[16, 3, 224, 224]变成[3, 16, 224, 224]符合CRNN的(C, T, H, W)输入约定PyTorch默认batch维度在前但CRNN主干通常先处理T维。3. CRNN模型构建从CNN主干到RNN时序建模的完整实现CRNN不是单一模型而是CNN RNN Classifier的组合架构。选择什么CNN什么RNN如何连接这些决定直接影响收敛速度和最终精度。我们采用工业界验证过的组合ResNet-18冻结BN层 单层双向GRU 全连接分类头。理由ResNet-18轻量且特征表达强GRU比LSTM参数少、训练快双向能捕获前后文冻结BN避免小batch下的统计量失真。3.1 CNN主干用ResNet-18提取帧级特征直接调用torchvision.models.resnet18但需修改其输出层——原ResNet输出1000维我们要的是512维特征向量适配GRU输入。# save as model/cnn_backbone.py import torch import torch.nn as nn import torchvision.models as models class ResNet18Backbone(nn.Module): def __init__(self, pretrainedTrue): super().__init__() resnet models.resnet18(pretrainedpretrained) # 移除最后的fc层保留到avgpool self.features nn.Sequential(*list(resnet.children())[:-2]) # 添加自适应池化确保输出7x7x512 self.avgpool nn.AdaptiveAvgPool2d((7, 7)) # 冻结BN层重要小batch下BN统计量不准 for m in self.features.modules(): if isinstance(m, nn.BatchNorm2d): m.eval() # 设为eval模式不更新running_mean/var def forward(self, x): # x: [B, C, T, H, W] - 处理每帧独立 B, C, T, H, W x.shape # 展平批次和时间维度[B*T, C, H, W] x x.permute(0, 2, 1, 3, 4).contiguous().view(B*T, C, H, W) x self.features(x) # [B*T, 512, 7, 7] x self.avgpool(x) # [B*T, 512, 7, 7] # 全局平均池化到向量[B*T, 512] x x.mean(dim[2, 3]) # 恢复时间维度[B, T, 512] x x.view(B, T, -1) return x逻辑说明permute(0,2,1,3,4)将[B,C,T,H,W]→[B,T,C,H,W]再view成[B*T,C,H,W]使CNN能逐帧处理AdaptiveAvgPool2d((7,7))确保不同输入尺寸如224x224或256x256都能输出固定空间尺寸避免后续全连接层维度错配m.eval()是血泪经验UCF101 batch_size 通常≤16BN层若用train模式running_mean/var更新不稳定导致loss震荡甚至nan。3.2 RNN时序建模双向GRU与特征对齐CNN输出[B, T, 512]需送入RNN建模时序。GRU输入要求[T, B, H_in]因此要转置。双向GRU输出拼接后为[T, B, 2*H_out]我们取最后时刻输出含全局上下文。# save as model/rnn_head.py import torch import torch.nn as nn class GRUHead(nn.Module): def __init__(self, input_size512, hidden_size256, num_layers1, dropout0.3): super().__init__() self.gru nn.GRU( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, bidirectionalTrue, # 双向捕获前后动作依赖 batch_firstFalse, # 输入为[T,B,H]符合RNN习惯 dropoutdropout if num_layers 1 else 0 ) self.dropout nn.Dropout(dropout) # 双向输出2*hidden_size self.classifier nn.Linear(2 * hidden_size, 101) # UCF101共101类 def forward(self, x): # x: [B, T, 512] - 转置为 [T, B, 512] x x.permute(1, 0, 2) # GRU输出output[T,B,2*H], h_n[num_layers*2, B, H] output, h_n self.gru(x) # 取最后时刻output含完整时序信息[B, 2*H] last_output output[-1] # [B, 2*256] last_output self.dropout(last_output) logits self.classifier(last_output) return logits参数说明hidden_size256是平衡点太大显存溢出16帧×256×2≈8MB/batch太小时序建模能力弱bidirectionalTrue是关键提升投篮动作中“举球”和“出手”有强依赖单向RNN只能从前往后学双向能同时感知output[-1]比h_n[-1]更鲁棒h_n是最后隐藏状态易受初始状态影响output[-1]是RNN对最后一帧的综合响应包含全部历史信息。3.3 CRNN完整模型组装与初始化将CNN和RNN串联注意权重初始化——RNN层需正交初始化避免梯度爆炸。# save as model/crnn.py import torch import torch.nn as nn from model.cnn_backbone import ResNet18Backbone from model.rnn_head import GRUHead class CRNN(nn.Module): def __init__(self, cnn_pretrainedTrue, rnn_hidden256): super().__init__() self.cnn ResNet18Backbone(pretrainedcnn_pretrained) self.rnn GRUHead(input_size512, hidden_sizernn_hidden) # 权重初始化RNN层用正交初始化 for name, param in self.rnn.named_parameters(): if weight in name: nn.init.orthogonal_(param) elif bias in name: nn.init.zeros_(param) def forward(self, x): # x: [B, C, T, H, W] features self.cnn(x) # [B, T, 512] logits self.rnn(features) # [B, 101] return logits # 实例化模型GPU上 model CRNN(cnn_pretrainedTrue, rnn_hidden256).cuda() print(fTotal params: {sum(p.numel() for p in model.parameters()) / 1e6:.2f}M) # 输出Total params: 12.45M ResNet18约11.2M GRU约1.25M注意nn.init.orthogonal_对GRU权重至关重要。未初始化时RNN常出现梯度爆炸loss突增至inf正交初始化能保持各方向梯度范数稳定。4. 训练与验证CRNN的3个必调参数与学习率调度策略CRNN训练比CNN更脆弱RNN对学习率敏感小batch下loss易震荡类别不均衡UCF101中“WalkingWithDog”样本远多于“YoYo”需加权。我们不用AdamW这种“万金油”而是SGD 余弦退火 标签平滑的组合实测收敛更快、最终acc更高。4.1 数据加载与增强时序感知的增强策略视频增强不能简单对每帧独立加高斯噪声——这会破坏帧间运动一致性。我们采用仅空间增强每帧独立并禁用时间域增强如帧翻转、时序打乱因为CRNN本身负责建模时序。# 在dataset/video_dataset.py中扩展default_transform def default_transform(self): if self.train: return transforms.Compose([ transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) else: return transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])提示ColorJitter参数不宜过大如saturation0.5否则同一动作在不同颜色扰动下特征漂移严重RNN难以泛化。4.2 损失函数与优化器标签平滑余弦退火UCF101类别不均衡直接用CrossEntropy会导致模型偏向高频类。标签平滑Label Smoothing将硬标签如[0,1,0,...]软化为[0.05,0.85,0.05,...]提升泛化。# train.py 片段 from torch.optim import SGD from torch.optim.lr_scheduler import CosineAnnealingLR from torch.nn import CrossEntropyLoss from torch.nn.functional import label_smoothing # 初始化 criterion CrossEntropyLoss(label_smoothing0.1) # 平滑系数0.1 optimizer SGD(model.parameters(), lr0.01, momentum0.9, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max50) # 50个epoch后lr→0 # 训练循环 for epoch in range(50): model.train() for batch_idx, (data, target) in enumerate(train_loader): data, target data.cuda(), target.cuda() optimizer.zero_grad() output model(data) loss criterion(output, target) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) # 梯度裁剪防爆炸 optimizer.step() scheduler.step() # 验证...参数说明label_smoothing0.1实测比0.05或0.2效果更好过高0.3会使模型过于保守低置信度max_norm1.0RNN梯度易爆炸必须裁剪否则第3~5个epoch就nanT_max50UCF101在50epoch内基本收敛过长易过拟合验证acc下降。4.3 验证指标不只是Top-1 Accuracy视频动作识别需关注时序鲁棒性。我们额外计算Top-1 Acc标准指标Frame-wise Consistency对同一视频的16帧CNN单独预测的类别是否一致一致性高说明空间特征稳定Temporal Confidence SpreadRNN输出logits的标准差越小说明时序建模越聚焦。# validate.py 片段 def validate(model, val_loader): model.eval() top1 AverageMeter() consistency AverageMeter() spread AverageMeter() with torch.no_grad(): for data, target in val_loader: data, target data.cuda(), target.cuda() logits model(data) # [B, 101] # Top-1 Acc acc1 accuracy(logits, target, topk(1,)) top1.update(acc1[0].item(), data.size(0)) # Frame-wise Consistency用CNN主干单独预测每帧 B, C, T, H, W data.shape # 展平为[B*T, C, H, W] frames_flat data.permute(0,2,1,3,4).contiguous().view(B*T, C, H, W) cnn_features model.cnn.features(frames_flat) # [B*T, 512, 7, 7] cnn_features cnn_features.mean(dim[2,3]) # [B*T, 512] cnn_logits model.rnn.classifier(cnn_features) # [B*T, 101] pred_per_frame cnn_logits.argmax(dim1).view(B, T) # 计算每视频帧预测一致的比例 consistent_ratio (pred_per_frame pred_per_frame[:, 0:1]).all(dim1).float().mean() consistency.update(consistent_ratio.item(), B) # Temporal Confidence Spread probs torch.softmax(logits, dim1) spread.update(probs.std(dim1).mean().item(), B) print(fTop1: {top1.avg:.2f} | Consistency: {consistency.avg:.3f} | Spread: {spread.avg:.3f})血泪经验若Consistency 0.6说明CNN主干没学好空间特征应检查数据增强是否过强或学习率是否过大若Spread 0.15说明RNN没聚焦关键帧可尝试增大GRU hidden_size或加注意力机制。5. 模型预测结果解析从logits到可解释的动作判定训练完模型下一步是真正用它预测新视频。但“预测结果”不只是一个类别ID——我们需要类别名称如Archery→射箭置信度softmax概率关键帧定位哪几帧对决策贡献最大错误归因为什么判错是空间特征错还是时序建模错。本节不讲黑匣子可视化而是用梯度类激活映射Grad-CAM RNN梯度溯源给出可验证的归因。5.1 加载模型与类别映射UCF101的101个动作名UCF101官网提供classInd.txt按行对应类别索引与名称。我们构建字典支持中文显示便于业务方理解。# utils/class_names.py import os def load_ucf101_classes(class_fileucfTrainTestlist/classInd.txt): 返回 {idx: English Name, idx: Chinese Name} classes_en {} classes_zh { ApplyEyeMakeup: 化眼妆, ApplyLipstick: 涂口红, Archery: 射箭, BabyCrawling: 婴儿爬行, BalanceBeam: 平衡木, BandMarching: 乐队行进, # ...其余95个此处省略实际需补全 YoYo: 玩悠悠球 } with open(class_file) as f: for line in f: idx, name_en line.strip().split() classes_en[int(idx)-1] name_en # 文件索引从1开始代码从0开始 return classes_en, classes_zh # 使用 classes_en, classes_zh load_ucf101_classes() print(fClass 0: {classes_en[0]} ({classes_zh.get(classes_en[0], ?)})) # 输出Class 0: ApplyEyeMakeup (化眼妆)5.2 单视频预测端到端pipeline封装将预处理、推理、后处理打包成函数输入视频路径输出结构化结果。# predict.py import torch import cv2 from torchvision import transforms from PIL import Image import numpy as np from model.crnn import CRNN from utils.class_names import load_ucf101_classes def predict_video(model, video_path, classes_en, classes_zh, devicecuda): # 步骤1帧提取复用2.2节逻辑此处简化 cap cv2.VideoCapture(video_path) frames [] while len(frames) 16: ret, frame cap.read() if not ret: break frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame cv2.resize(frame, (256, 256))[16:240, 16:240] frames.append(Image.fromarray(frame)) cap.release() # 步骤2预处理 transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) frames_tensor torch.stack([transform(f) for f in frames], dim1) # [3,16,224,224] frames_tensor frames_tensor.unsqueeze(0).to(device) # [1,3,16,224,224] # 步骤3推理 model.eval() with torch.no_grad(): logits model(frames_tensor) # [1,101] probs torch.softmax(logits, dim1)[0] # [101] top3_idx probs.topk(3).indices.cpu().numpy() top3_probs probs.topk(3).values.cpu().numpy() # 步骤4结构化输出 result [] for i, idx in enumerate(top3_idx): en_name classes_en[idx] zh_name classes_zh.get(en_name, en_name) result.append({ rank: i1, class_id: int(idx), english_name: en_name, chinese_name: zh_name, confidence: float(top3_probs[i]) }) return result # 使用示例 model CRNN().cuda() model.load_state_dict(torch.load(checkpoints/crnn_best.pth)) classes_en, classes_zh load_ucf101_classes() result predict_video(model, test_videos/archery_001.avi, classes_en, classes_zh) for r in result: print(f#{r[rank]}: {r[chinese_name]} ({r[english_name]}) - {r[confidence]:.3f}) # 输出 # #1: 射箭 (Archery) - 0.823 # #2: 射击 (ShootingGallery) - 0.112 # #3: 拳击 (BoxingPunchingBag) - 0.0315.3 预测结果避坑3个现象、原因与解决CRNN预测翻车往往不是模型问题而是数据或流程陷阱。以下是实测最高频的3个坑现象1同一视频多次预测类别ID随机跳变如第一次输出Archery第二次ThrowDiscus原因模型仍在train模式model.train()未关BN层和Dropout随机生效导致输出不稳定。解决严格在predict_video开头加model.eval()并在with torch.no_grad():内执行推理。若用torch.jit.trace导出模型需确认trace时用的是eval()状态。现象2置信度普遍偏低Top1 0.4且Top3概率接近均分原因视频帧提取时未做中心裁剪或分辨率不匹配如用了256x256但CNN期望224x224导致CNN特征提取失效。解决用cv2.imshow检查提取的帧是否清晰、居中打印frames_tensor.shape确认为[1,3,16,224,224]若用其他预训练主干如EfficientNet需同步调整归一化参数。现象3预测结果完全错误如篮球视频判为Knitting但训练Acc达85%原因UCF101的testlist01.txt包含部分视频被错误标注社区已知问题或你的测试集混入了训练集视频路径匹配错误。解决用md5sum校验测试视频是否与官网testlist01.txt所列一致在VideoDataset.__init__中打印len(self.samples)确认训练/测试集数量与官方报告训练9537测试3783吻合若仍异常用Grad-CAM检查CNN是否在关注球体区域。提示Grad-CAM实现需修改CNN主干在ResNet18Backbone中记录layer4输出及梯度。限于篇幅此处不展开但它是定位“空间特征错”还是“时序建模错”的后悔药——若CAM热力图在球上说明CNN对若在背景上则需重调CNN。6. 进阶技巧用Grad-CAM定位CRNN的决策依据并生成动作关键帧报告CRNN的“黑匣子”属性常让业务方质疑“为什么判这是投篮”——仅给一个概率不够得指出哪几帧、哪个区域支撑了这个判断。Grad-CAMGradient-weighted Class Activation Mapping能可视化CNN关注区域但标准Grad-CAM只适用于CNN。CRNN中决策由RNN最终输出驱动需联合CNN空间梯度与RNN时间梯度我们称之为Temporal-Spatial Grad-CAM。6.1 修改模型注册CNN特征与RNN梯度钩子在CRNN中CNN输出特征features[B,T,512]RNN将其映射为logits。要定位关键帧需知道RNN对每个时间步的贡献要定位关键区域需知道CNN对每个帧的贡献。# model/crnn_gradcam.py import torch import torch.nn as nn class CRNNGradCAM(CRNN): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.gradients None self.features None def activations_hook(self, grad): self.gradients grad def forward(self, x): # CNN前向保存features B, C, T, H, W x.shape x_flat x.permute(0,2,1,3,4).contiguous().view(B*T, C, H, W) features_flat self.cnn.features(x_flat) # [B*T, 512, 7, 7] self.features features_flat # 保存用于反向 # 注册梯度钩子到features features_flat.register_hook(self.activations_hook) features_flat self.cnn.avgpool(features_flat).mean(dim[2,3]) # [B*T, 512] features features_flat.view(B, T, -1) # [B, T, 512] # RNN前向 logits self.rnn(features) # [B, 101] return logits def get_cam_weights(self, class_idx): 返回每个时间步的权重 [T] # 获取RNN对指定类别的梯度[B, T, 512] gradients self.gradients # [B*T, 512, 7, 7] # 平均池化梯度得到每个帧的权重 weights gradients.mean(dim[2,3]) p a hrefhttps://download.csdn.net/download/qq_45732909/87758266 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号