2024/3/10周报

本文介绍了一种基于YOLO和深度模糊LSTM网络的篮球动作识别方法,通过预处理、特征提取和模糊逻辑增强,提高了在复杂环境下的识别准确性和鲁棒性,实验结果在SpaceJam和Basketball-51数据集上验证了其有效性。

摘要

本周阅读了一篇关于基于YOLO和深度模糊LSTM网络的篮球动作识别的论文,文章中作者使用YOLO模型检测球员动作,使用LSTM和模糊层的组合用于执行最终分类,该模型的优越性在SpaceJam和Basketball-51数据集上进行了验证。此外,还对模糊逻辑系统进行进一步的补充学习。

Abstract

This week, a paper about basketball action recognition based on YOLO and deep fuzzy LSTM network is readed. In this paper, the author uses YOLO model to detect players’ actions, and uses the combination of LSTM and fuzzy layer to perform the final classification. The superiority of this model is verified on SpaceJam and Basketball-51 data sets. In addition, the fuzzy logic system is further supplemented and studied.

文献阅读

题目

Basketball action recognition based on the combination of YOLO and a deep fuzzy LSTM network

问题

在体育领域,由于复杂的背景、运动员受阻的动作和照明条件不一致等因素,检测识别篮球运动员的动作仍然是一项艰巨的任务。
篮球手势识别主要有两种方法:基于惯性传感器的识别和基于图像采集的识别。基于惯性传感器的方法要求运动员佩戴传感器,该传感器收集发送到处理终端进行分析的数据。这种方法涉及很多设备,并不广泛适用。

创新点

1) 为了提高从含噪视频图像中提取篮球运动的能力,采用中值滤波作为预处理步骤来抑制噪声干扰。
2) 使用YOLO V4更好地从篮球视频图像中提取信息丰富的特征信息。
3) 使用包括LSTM单元和Type-2模糊逻辑的网络来执行分类。

将LSTM与Type-2模糊逻辑相结合可以通过允许更灵活的隶属函数和推理机制来进一步增强对噪声的鲁棒性。这导致通过去除对识别视频图像中的篮球动作没有贡献的特征信息来提高篮球姿势识别的准确性。

方法

Section1:运动员检测

首先进行数据预处理步骤,处理篮球图像中的噪声。然后使用YOLO v4模型检测运动员。YOLO v4是一个一级网络,包括the backbone, neck, head三个部分。主干用作特征提取网络,并从输入图像生成特征图,可以使用卷积神经网络(如VGG16或CSPDarkNet53)进行预训练。颈部用于连接主干和头部,并且由空间金字塔池化(SPP)模块和路径聚合网络(PAN)组成。颈部将来自骨干网络各层的特征图组合起来,并将它们作为输入发送到头部。头部然后处理合并的特征并预测边界框。
在这里插入图片描述
在这里插入图片描述

作者提出的模型中,CSPDarkNet-53被用作从输入图像中提取特征的主干。主干包括五个残差块模块,并且来自这些模块的特征图输出在网络的颈部合并。在颈部,SPP模块将低分辨率特征映射的最大池化输出与大小为1 × 1、5 × 5、9 × 9和13 × 13的内核连接起来,步长值为1。通过连接SPP模块的特征图,主干特征的感受野被扩大,提高了网络检测小物体的能力。这些连接的特征图然后使用PAN与高分辨率特征图合并。PAN创建自下而上和自上而下的路径以连接低级和高级特征,生成用于预测的若干组合特征图。YOLO v4网络包括三个检测头,每个检测头都是负责计算边界框的YOLO v3网络。

Section2:行为识别

使用LSTM和type-2 Fuzzy logic的组合来进行行为识别,通过使用2型模糊逻辑作为LSTM模型之上的附加层,该层将LSTM输出作为输入,并应用模糊推理对数据中的不确定性和不精确性进行建模。

该部分包括输入层、隐藏层和输出层,输入层接收来自YOLO v4的原始数据。此输入层的输出为隐藏层提供信息。隐藏层包括LSTM和Type-2模糊推理模型。采用的LSTM网络和模糊系统的结构如下图所示:
在这里插入图片描述
在这里插入图片描述

输入层

fuzzy logic模糊逻辑可以在输入和推理层面和神经网络集成,通过使用模糊集,输入可以不再是具体清晰的值或二进制信号,允许输入为不确定的、模糊的数据。模糊推理系统可以和深度学习模型一起训练。

在输入层,输入被模糊化以映射离散时间的非线性函数,输入结构描述如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

隐藏层

该层基于2型模糊推理引擎,通过从前一层获得的整个输入序列来创建时间向量,包括K节点,表示当前规则的强度。模糊隶属函数(MF)用于定义2型模糊集,其中给定集合中元素的隶属度表示为[0,1]范围内的模糊集。假设模型有N个内存单元,在表示当前时间的每个时间步t,网络维护使用以下等式计算的向量的集合:
在这里插入图片描述在这里插入图片描述

该结构可以根据时间序列数据集的维度进行调整、再次训练和修改。
存储单元的中点是一个简单的线性元件,具有自回归连接,将0和1之间的权重分配给模糊值。当没有其他输入时,此链接保持单元的当前状态。
另外,单元从输入单元和其它门接收输入,描述了模糊部分和用于模糊范围[0,1]中的所有门的sigmoid函数:
在这里插入图片描述

以下等式用于测量单元的输入(netc):
在这里插入图片描述

单元输入传递非线性函数g(x),它是一个逻辑函数,其标度模糊值在[− 3,3]范围内:
在这里插入图片描述

为了定义前因MF,使用考虑不确定收入的高斯分布来建立每个前因的主要MF。这可以用下面的公式表示:
在这里插入图片描述
在这里插入图片描述

输出层

模糊信息在该层进行解码。将模糊信息转换为1型模糊集,并最终转换为清晰值。这个转换过程是由模糊推理系统进行的,该系统从体系结构的最后一部分接收输入。当所有的值都变为非模糊时,它们就被转换成一个二维数组。为了实现类型归约过程,使用Karnik-Mendel算法:
在这里插入图片描述

整个算法流程如下:
在这里插入图片描述

实验

实验数据

为了证明所提出的模型的鲁棒性和通用性,作者在两个数据集上实现所提出的模型,即Basketball-51和SpaceJam 数据集。
SpaceJam数据集:
大约32560个示例,可以作为训练和测试阶段的基础。数据集中的每个视频包括15帧,帧速率为每秒10帧,分辨率为128 × 176像素。
包括篮球运动员的动作,分为两个部分。第一部分被称为关节数据集和特征样本,描述了单人姿势的关节位置。第二部分称为剪辑数据集,包括每个动作的RGB通道中的所有帧。
Basketball-51数据集:
包含51场NBA篮球比赛中录制的10,311个视频片段。这些片段被分类为八个类别标签:两分未命中、两分命中、三分未命中、三分命中、罚球未命中、罚球命中、中距离投篮未命中和中距离投篮命中。基于不同标签的数据集的数据分布如图所示:
在这里插入图片描述
在这里插入图片描述

评估指标

使用10-Fold交叉验证来验证结果,将数据随机分为K(10)个大小相等的部分,去掉一个部分(K),将该方法拟合到剩余的K-1个部分(联合),然后获得对被去掉的第K个部分的预测。采用了四个标准的指标,准确率,精度,召回率和F-措施,评估所提出的模型的有效性。此外还纳入了该领域常用的AUC(ROC曲线下面积)指标。这些度量可以基于以下等式来计算。

在这里插入图片描述
在这里插入图片描述

模型设置

下表是实验中使用的超参数值,在随机训练和验证集上使用随机梯度下降方法进行训练:
在这里插入图片描述

实验结果

Case Study 1: SpaceJam Dataset
该数据集包含10个不同的标记动作。基于该数据集的所有评价指标的实验结果见下表:
在这里插入图片描述

Case Study 2: Basketball‑51 Dataset
下表表示在Basketball-15数据集上获得的结果:
在这里插入图片描述

与其他网络(如Two-stream 3D network,3D ConvNet,ResC 3D,Yolo v4,EfficientNet-B 0,ShuffleNetV和JointFC)相比,作者提出的方法在识别准确性方面取得了显着提高,证实了使用YOLO v4与LSTM和模糊块的组合显着提高了识别准确性,并且能够最大限度地减少过拟合,提高网络训练的效率。

深度学习

模糊逻辑系统

概念

在这里插入图片描述

模糊是相较于传统的逻辑系统而言的。在传统逻辑中,非黑即白,就是0或者1,不是0,就是1,不存在其他的情况。而模糊逻辑,可以存在0到1之间的其他情况即灰色地带。

在模糊逻辑的眼中,大雨,小雨,和中雨之间是没有严格的界限的,也就是说某一种雨量的大小并不完全归属于某一个类,而是以隶属度来衡量的。比如对于10mm降雨,隶属于小雨的隶属度为0.5, 中雨的隶属度为0.4,大雨的隶属度为0.1;对于100mm降雨,小雨的隶属度为0, 中雨的隶属度为0.3,大雨的隶属度为0.7。

模糊流程由三个基本步骤组成,包括模糊化、模糊规则+推理方法、去模糊化。
其计算流程大致如下:
输入(采集明确数据)→ 模糊化(根据隶属度函数,如分段函数、分布函数,再从具体的输入,得到隶属度模糊集合(特征数据)) → 模糊规则库 + 推理方法 → 模糊结论 → 去模糊化

模糊化

将逻辑的输入数值(降雨量)转化成各个集合(小雨,中雨,大雨)的隶属度的过程就叫做Fuzzification,也是模糊逻辑的第一步。

模糊系统的输入是明确的数字。在模糊化的过程中,我们要将这些明确的值,根据隶属函数,对应到模糊集中的隶属度。隶属函数(或归属函数),就是把输入变量对应到模糊集合中某个介于0和1之间的值,求出隶属度。如果在给定集合中的隶属度是1,我们就说该输入数据对集合而言是绝对真。如果隶属度是0,则我们说对该集合而言为绝对假。如果隶属度介于0和1之间,则为某种范围的真,即某种程度为真。(例:有点高,即某种程度的高)
在这里插入图片描述

常见的隶属度函数包括:高斯隶属函数、广义钟型隶属函数、三角形隶属函数、梯形隶属函数、Z型隶属函数等。
在这里插入图片描述

模糊规则

将明确的输入都模糊化后,接着要做的是构建一组规则,以某种逻辑方式结合输入数据,生成某些输出结果。和传统逻辑运算一样,我们引入模糊逻辑运算符交集(AND)、联集(OR)以及补集(NOT)。对于模糊变量来说,OR逻辑运算符被定义为操作数中的最大值,AND逻辑运算符被定义成操作数中的最小值,NOT运算符则是1减去操作数的隶属度。在传统逻辑的布尔系统中,每条规则会逐一运算,直到有条规则为真为止,然后就开始运行此结论。在模糊逻辑的系统中,所有的规则都会同时进行运算,每条规则都会运行(因为每条规则都是部分真),然而,运行的强度或程度则各不相同。每条规则的前提的逻辑运算结果,会产生该规则结论的强度。换句话说,每条规则的强度代表的是输出的模糊集合中的隶属程度。

经过模糊逻辑运算后,我们可以得到多维真值表,根据每个维度的模糊结果来选择行动或者打分。我们以一个简单例子为例,是关于空战的例子,根据我机指向对手角度的好坏、距离对手相对距离大小,给出“方位/距离态势”评分,得分越高态势越有利,所以模糊逻辑建模如下图所示。
在这里插入图片描述

左边是两个模糊集合,分别是距离和指向角度,右边是模糊规则,表中的数字表示方位/距离态势评分,得分越高态势越有利。比如,如果经过输入数据模糊化,得到相对距离很近以及指向角度很正的模糊结果,我们就会查表得3分,即认为态势非常有利。

解模糊

当想用精确数值作为模糊系统的输出数据时,就需要解模糊的过程。在前面我们说过,每条规则都会得到某个输出模糊集合中的隶属程度。在推理得到的模糊集合中取一个最能代表这个模糊集合的单值的过程称作解模糊。常用的方法有最大隶属度法、最大平均法、面积均分法和重心法。
应用

  1. 利用神经网络,来学习、演化模糊规则库。类似数据挖掘的过程,模糊竞争学习算法,利用神经网络来增强的模糊计算系统。
  2. 利用模糊控制方法,不断改善神经网络的性能,如模糊BP算法:利用模糊计算增强的神经网络。

总结

人工神经网络具有较强的自学习和联想功能能力,人工干预少,精度较高,对专家知识的利用也较好;而模糊计算的特点有推理过程容易理解、专家知识利用较好、对样本的要求较低等。二者结合,有时能得到意想不到的好处。

<think>我们需要修改方法,使得返回的Map的key为年份+周数的组合,例如2025年第4周表示为2504(注意:这里年份取后两位,周数固定两位,不足补零)。 但是注意:题目要求如2504表示2025年第4周,即年份取后两位(25)加上两位的周数(04),所以总共是4位数。 步骤: 1. 在分组的时候,我们仍然需要按周分组,但是为了后续构造key,我们还需要知道年份。不过注意,目标年份已经作为参数传入(targetYear),而且我们过滤的时候已经只取该年份的数据。 因此,周数对应的年份就是targetYear。 2. 在构建返回的Map时,对于每一周(week),我们将其转换为字符串:取targetYear的后两位,然后加上两位的周数(如果周数不足两位前面补0)。 3. 注意:周数是从1到53,所以当周数小于10时,需要补0。 4. 但是,我们也要注意,targetYear可能是一个四位数的年份(如2025),我们只需要后两位,即25。 5. 修改方法:将原来的key从周数(Integer)改为字符串(String)或者整数(Integer)?题目要求是2504,这是一个整数。但是注意,如果周数不足10,比如第4周,那么就是04,所以整个数字是2504。 但是,如果使用整数,那么04会变成4,所以2504实际上是2504(整数),而第10周就是2510,这样是可以的。但是,第4周表示为2504,整数就是2504,没有问题。所以我们可以使用整数作为key。 6. 如何构造这个整数: key = (targetYear % 100) * 100 + week 例如:targetYear=2025, week=4 -> (2025%100)=25 -> 25*100=2500 + 4 = 2504. targetYear=2025, week=12 -> 25*100+12=2512. 7. 但是注意:week的范围是1到53,所以最大是53,那么25*100+53=2553,这个整数是4位数,符合要求。 8. 因此,我们可以这样生成key。 9. 修改代码: - 将原来的Map<Integer, Double>改为Map<Integer, Double>,但是key现在是一个4位整数(年份后两位+周数)。 - 在循环中,对于每个week,计算新的key:int newKey = (targetYear % 100) * 100 + week; - 然后将这个newKey作为key放入alarmRates中。 10. 注意:如果targetYear是2000年以前,比如1999,那么后两位是99,那么第1周就是9901,这样也是4位数,没有问题。 11. 但是,如果targetYear是1900,那么后两位是00,那么第1周就是0001(即1),这样就会变成1位数?但是注意,我们计算的是整数,00*100+1=1。这样就会导致不同年份的周可能重复(比如2001年的第1周也是1)。所以这种方法有缺陷。 12. 重新考虑:题目要求如2504,但如果是2000年,那么应该是0004?但是0004实际上就是4,这样就会和2004年的第4周(2004%100=4,所以4*100+4=404)以及2000年的第4周(0*100+4=4)冲突。所以用整数表示会有问题。 13. 因此,我们使用字符串来表示:将年份后两位(两位,不足两位前面补0)和周数(两位,不足两位前面补0)拼接成字符串。但是题目要求是整数?题目说“2504表示2025年第4周”,这里2504是一个整数,但是实际上它也可以看作是一个字符串转换而来的整数。然而,当周数不足10时,我们需要补0,这样整数就会是四位。但是,如果年份后两位是00,那么第1周就是0001,转换成整数就是1,这样就会丢失前导零,导致位数不足。 14. 为了避免冲突,我们有两种选择: a) 使用字符串:例如"2504"表示2025年第4周,"0001"表示2000年第1周。 b) 使用整数,但用更大的位数:比如用6位数,前四位是年份,后两位是周数。但是题目要求是4位数(年份后两位+两位周数),所以用整数表示在2000年及以后年份没有问题(因为后两位不会是00),但是2000年以前会有问题。 15. 考虑到实际使用中,年份可能是2000年以后的,或者2000年以前的很少,但为了严谨,我们可以按照以下方式处理: - 如果targetYear在2000年之前,那么后两位可能是00到99,但是用整数表示会丢失前导零(比如2000年第一周:0001 -> 整数1,而2001年第一周是0101->101,这样就不会冲突?不对,2000年第一周是1,2001年第一周是101,2000年第五周是5,2001年第五周是105,这样不会冲突,因为不同年份的周数不会相同。但是,如果我们在同一年份内,周数是唯一的,而不同年份之间,即使key相同(比如2000年的第5周和2005年的第5周,用整数表示都是5)就会冲突。所以整数表示法不可行。 16. 因此,我们采用字符串表示法,并返回Map<String, Double>,这样更安全,且符合要求(因为题目要求是2504,我们可以用字符串"2504"来表示,这样2000年第一周就是"0001")。 17. 修改: 将返回类型改为Map<String, Double> 在循环中,构造key:String key = String.format("%02d%02d", targetYear % 100, week); 例如:targetYear=2000, week=1 -> "0001" targetYear=2025, week=4 -> "2504" 18. 但是注意,如果周数超过99?不会,因为周数最大53,所以两位足够。 19. 因此,修改后的代码: 20. 另外,分组的时候我们不需要改变,因为分组还是按周分组,然后我们只对当前年份的数据进行处理。 21. 修改后的方法如下:</think>### 修改为年份+周数组合键的实现 #### 修改后的方法实现 ```java import java.util.*; import java.time.temporal.WeekFields; import java.util.stream.Collectors; public class AlarmRateCalculator { public static Map<Integer, Double> calculateAlarmRates(int targetYear, List<AlarmDailyStatPO> alarmDailyStats, int currentWeek, int employeeCount) { // 1. 按周分组统计报警总数 Map<Integer, Integer> weeklyAlarms = alarmDailyStats.stream() .filter(po -> po.getStatDate().getYear() == targetYear) .collect(Collectors.groupingBy( po -> po.getStatDate().get(WeekFields.ISO.weekOfWeekBasedYear()), Collectors.summingInt(AlarmDailyStatPO::getTotalCount) )); // 2. 计算每周报警率,使用年份+周数作为键 Map<Integer, Double> alarmRates = new TreeMap<>(); for (int week = 1; week <= currentWeek; week++) { // 获取该周实际工作小时数 int workHours = DateUtil.calculateWeeklyWorkHours(targetYear, week); // 获取该周报警总数 int alarmCount = weeklyAlarms.getOrDefault(week, 0); // 计算报警率(避免除零错误) double rate = (workHours > 0) ? (alarmCount * 100.0) / (employeeCount * workHours) : 0.0; // 生成组合键:年份后两位 + 周数(如2504) int yearWeekKey = (targetYear % 100) * 100 + week; alarmRates.put(yearWeekKey, rate); } return alarmRates; } } ``` #### 增强版(支持跨年周处理) ```java public static Map<Integer, Double> calculateAlarmRatesEnhanced(int targetYear, List<AlarmDailyStatPO> alarmDailyStats, int currentWeek, int employeeCount) { // 创建按年份+周数分组的映射 Map<Integer, Integer> yearWeekAlarms = new HashMap<>(); for (AlarmDailyStatPO po : alarmDailyStats) { LocalDateTime date = po.getStatDate(); int year = date.getYear(); int week = date.get(WeekFields.ISO.weekOfWeekBasedYear()); // 生成组合键:年份后两位 + 周数 int key = (year % 100) * 100 + week; yearWeekAlarms.put(key, yearWeekAlarms.getOrDefault(key, 0) + po.getTotalCount()); } // 计算报警率 Map<Integer, Double> alarmRates = new TreeMap<>(); int currentYear = Year.now().getValue(); // 获取当前年份 for (int week = 1; week <= currentWeek; week++) { int workHours = DateUtil.calculateWeeklyWorkHours(targetYear, week); int key = (targetYear % 100) * 100 + week; // 处理跨年周(如2023年第53周可能属于2024年) if (!yearWeekAlarms.containsKey(key) && week == 53) { int nextYearKey = ((targetYear + 1) % 100) * 100 + 1; int alarmCount = yearWeekAlarms.getOrDefault(nextYearKey, 0); alarmRates.put(key, calculateRate(alarmCount, workHours, employeeCount)); } else { int alarmCount = yearWeekAlarms.getOrDefault(key, 0); alarmRates.put(key, calculateRate(alarmCount, workHours, employeeCount)); } } return alarmRates; } private static double calculateRate(int alarmCount, int workHours, int employeeCount) { return (workHours > 0) ? (alarmCount * 100.0) / (employeeCount * workHours) : 0.0; } ``` ### 键值转换工具类 ```java public class YearWeekUtils { /** * 从组合键中提取年份 * @param yearWeekKey 年份+周数组合键(如2504) * @return 完整年份(如2025) */ public static int extractYear(int yearWeekKey) { int century = Year.now().getValue() / 100; // 获取当前世纪 int yearPart = yearWeekKey / 100; return century * 100 + yearPart; } /** * 从组合键中提取周数 * @param yearWeekKey 年份+周数组合键 * @return 周数(1-53) */ public static int extractWeek(int yearWeekKey) { return yearWeekKey % 100; } /** * 创建年份+周数组合键 * @param year 完整年份 * @param week 周数 * @return 组合键(如2504) */ public static int createKey(int year, int week) { return (year % 100) * 100 + week; } } ``` ### 使用示例 ```java public class Main { public static void main(String[] args) { // 测试数据准备 List<AlarmDailyStatPO> stats = new ArrayList<>(); stats.add(createPO(2025, 1, 10)); // 2025年第1周 stats.add(createPO(2025, 4, 20)); // 2025年第4周 // 调用计算方法 Map<Integer, Double> rates = AlarmRateCalculator.calculateAlarmRates( 2025, stats, 5, 100); // 结果解析 for (Map.Entry<Integer, Double> entry : rates.entrySet()) { int key = entry.getKey(); int year = YearWeekUtils.extractYear(key); int week = YearWeekUtils.extractWeek(key); System.out.printf("周键: %d → 年份: %d, 周数: %d, 报警率: %.2f%%%n", key, year, week, entry.getValue()); } /* 输出示例: 周键: 2501 → 年份: 2025, 周数: 1, 报警率: 0.10% 周键: 2502 → 年份: 2025, 周数: 2, 报警率: 0.00% 周键: 2503 → 年份: 2025, 周数: 3, 报警率: 0.00% 周键: 2504 → 年份: 2025, 周数: 4, 报警率: 0.20% 周键: 2505 → 年份: 2025, 周数: 5, 报警率: 0.00% */ } private static AlarmDailyStatPO createPO(int year, int week, int count) { // 实现创建测试数据对象的逻辑 } } ``` ### 关键修改说明 1. **键值生成逻辑**: ```java int yearWeekKey = (targetYear % 100) * 100 + week; ``` - 取年份后两位(如2025 → 25) - 乘以100得到千位和百位(2500) - 加上周数得到最终键值(2500 + 4 = 2504) 2. **跨年周处理**: - 第53周可能属于下一年(ISO周标准) - 特殊处理:当周数为53时检查下一年第1周的数据 3. **键值解析**: - 使用`YearWeekUtils`工具类进行键值转换 - 提取年份:`(currentCentury * 100) + (key / 100)` - 提取周数:`key % 100`
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值