草庐IT

【Python】近似熵,样本熵,模糊熵计算高效版

记录无知岁月 2023-06-16 原文

文章目录

前言

  最近在学习机器学习,发现对于与生物医学信号相关的机器学习任务,在选定特征时,各种针对时间序列的熵是绕不开的重要特征,诸如近似熵,样本熵,模糊熵等。因为它们所包含的信息要远比均值方差等特征要多得多,通过写python程序实现的过程中收获了不少,这里简单总结一下。

整体思路

  写好一个程序的前提一定是理解其具体的计算思路,所以在写程序之前首先需要知道那些熵到底是怎么算出来的,这里个人强烈建议直接查找文献,而不要完全依赖各种二手的博客,因为有可能描述不清楚而直接导致程序写错。

一个讲得很全面,但程序编写不友好的教程

1 近似熵(Approximate Entropy, ApEn)

1.1 理论基础

  近似熵是Pincus在1991年提出的一种只需要较短数据就能表现信号的动力学参数,它具有以下特点:

  • 只需要比较短的数据就能得到比较稳健的估计值,所需要的数据点数大致是100~5000点,一般是1000点左右;
  • 有较好的抗噪和抗干扰能力。特别是对偶尔产生的瞬态强干扰有较好的承受能力。

  它的计算方法如下:【图片来自文献1

  这里有一个点需要注意,那就是近似熵在计算相似向量的个数时,是会包含其自身的,也就是说,总的矢量个数为N-m+1,这一点在程序编写时要尤为注意。

1.2 python第三方库实现

  像这种非常常用的熵,必然会有第三方库整合其算法,这里推荐使用的是EntropyHub这个库。里面包含了多种熵的计算方式。其使用方式如下。

import EntropyHub as EH
import numpy as np
def ApEn (Datalist, r=0.2, m=2):
	th = r * np.std(Datalist)
	return EH.ApEn(Datalist,m,r=th)[0][-1]

需要注意的是,里面的阈值容限r是指绝对量,这里是强制将其转化为相对量,即几倍的标准差。

1.3 基于多线程numpy矩阵运算实现

  打开第三方库中函数的定义,可以发现其计算熵的方式是基于循环来计算的,因此效率不是特别高。加上计算近似熵一般都有几千个点,计算起来会非常慢。而如果通过numpy矩阵运算实现,再加上多线程,其速度会快很多。

不熟悉numpy使用的可以看一下我另外一篇博客

其代码如下所示。

from pathos.multiprocessing import ThreadPool as Pool #多线程
import numpy as np
def ApEn2 (s :list|np.ndarray, r:float, m :int =2):
	s = np.squeeze(s)
	th = r * np.std(s) #容限阈值
	def phi (m):
		n = len(s)
		x = s[ np.arange(n-m+1).reshape(-1,1) + np.arange(m) ]
		ci = lambda xi: (( np.abs(x-xi).max(1) <=th).sum()) / (n-m+1) # 构建一个匿名函数
		c = Pool().map (ci, x) #所传递的参数格式: 函数名,函数参数
		return np.sum(np.log(c)) /(n-m+1)

  值得一提的是,这里用到了numpy的广播模式,即如果两个不同型的矩阵相加减,其会自动复制矩阵内的数值,使其成为同型矩阵,然后再加减。举个例子

import numpy as np

A = np.array([1,2])
B = np.array([[1,2],
              [2,3]])

C = B - A  # type: ignore
print(C)

#输出:
>>> [[0 0]
 [1 1]]


2 样本熵 (Sample Entropy, SampEn)

2.1 理论基础

  样本熵是基于近似熵的改进,计算方式非常类似,但是也有一些差别。其计算方式如下图所示,注意红字哦~ 【截图来自文献2

  这里个人觉得计算方式有点奇怪,假设m初始值为2,根据上面的计算方式,当m=2时,将原时间序列划分为子序列时,最后一个点x(N)是不考虑的,这样就能得到N-2个子序列,而不是N-1个。但是当m加1之后,划分子序列时又要考虑最后一个点,因此最后得到的子序列还是N-2个。
  关于这个问题,如果要强制理解,那只能理解为要保证两种划分模式下子序列个数相同
  这里我一开始理解错了,因为很多博客喜欢直接说“将m变成m+1,重复上述过程”,但实际上似乎不是只更换参数的意思,之所以这样理解是因为我试了好几个第三方库,它们的计算结果就是按照上面这种理解方式。

/*【2022.11.16】找到一篇文献3提到了样本熵的这个特点,并且强调这个就是样本熵相比于近似熵的一个重要改进点:*/

2.2 python第三方库实现

  这里推荐使用的第三方库还是上面提到的EntropyHub,它里面也有计算样本熵的函数。如下所示。

import EntropyHub as EH
import numpy as np
def SampleEntropy2(Datalist, r, m=2):
	th = r * np.std(Datalist) #容限阈值
	return EH.SampEn(Datalist,m,r=th)[0][-1]

2.3 基于多线程numpy矩阵运算实现

  正如上面的注意部分所强调的,在写代码的时候要尤为注意,就是子序列划分那一块的代码。

from pathos.multiprocessing import ThreadPool as Pool #多线程
import numpy as np
def SampleEntropy(Datalist, r, m=2):
	list_len = len(Datalist)  #总长度
	th = r * np.std(Datalist) #容限阈值

	def Phi(k):
		list_split = [Datalist[i:i+k] for i in range(0,list_len-k+(k-m))] #将其拆分成多个子列表
		#这里需要注意,2维和3维分解向量时的方式是不一样的!!!
		Bm = 0.0
		for i in range(0, len(list_split)): #遍历每个子向量
			Bm += ((np.abs(list_split[i] - list_split).max(1) <= th).sum()-1) / (len(list_split)-1) #注意分子和分母都要减1
		return Bm
	## 多线程
	# x = Pool().map(Phi, [m,m+1])
	# H = - math.log(x[1] / x[0]) 
	H = - math.log(Phi(m+1) / Phi(m))
	return H

除用python实现外,这里还有一个是用MATLAB写的计算样本熵的代码,也可以看看。链接



3 模糊熵

3.1 理论基础

  模糊熵是在样本熵的基础上进行改进得到的。从上面对样本熵的表述来看,在判断一个序列与另一个序列是否近似时,使用的是阶跃判断,即只有相似(1)和不相似(0)之间的判断,而模糊熵则是引入了一个相似度的概念,类似于模糊控制中的隶属度

对模糊控制不熟悉的同学也可以看一下我的另外一篇博客:模糊控制算法

  关于模糊熵的计算方式,发现网上很多博客甚至很多文献(也不知道咋参考的。。。)在计算模糊熵这块有很多版本。所以为了得到正确答案,我参考了模糊熵的“鼻祖论文”——陈伟婷在2007发表在IEEE上的论文4,截图如下:

3.2 python第三方库实现

  如果数据量小且不想写代码的可以参考使用第三方库。

import EntropyHub as EH
import numpy as np
def FuzzyEn2(s:np.ndarray, r=0.2, m=2, n=2):
	th = r * np.std(s)
	return EH.FuzzEn(s, 2, r=(th, n))[0][-1]

3.3 基于numpy实现

  这里需要注意的就是对计算规则的理解。其代码如下所示。

import numpy as np
import math
def FuzzyEn(s, r = 0.2, m = 2, n = 2):
	'''s:需要计算熵的向量; r:阈值容限(标准差的系数); m:向量维数; n:模糊函数的指数
	'''
	N = len(s)  #总长度
	th = r * np.std(s) #容限阈值

	def Phi(k):
		list_split = [s[i:i+k] for i in range(0,N-k+(k-m))] #将其拆分成多个子列表
		#这里需要注意,2维和3维分解向量时的方式是不一样的!!!
		B = np.zeros(len(list_split))
		for i in range(0, len(list_split)): #遍历每个子向量
			di = np.abs(list_split[i] - np.mean(list_split[i]) - list_split + np.mean(list_split,1).reshape(-1,1)).max(1)
			Di = np.exp(- np.power(di,n) / th)
			B[i] = (np.sum(Di) - 1) / (len(list_split)-1) #这里减1是因为要除去其本身,即exp(0)
		return np.sum(B) / len(list_split)
	H = - math.log(Phi(m+1) / Phi(m))
	return H

总结

  计算各种熵的关键还是在于对计算方式的理解,如果博客说法不一,那就去查找文献,如果文献说法不一,那就去找提出这个熵的论文。
  计算速度方面,发现对于较大的数据量,如3000,第三方库计算近似熵和样本熵的速度比numpy矩阵运算速度慢,但模糊熵计算速度却比numpy矩阵运算速度快很多。

但是按理说,模糊熵的复杂度至少是样本熵的两倍,但是模糊熵的计算速度却比样本熵快,我估计是第三方库作者可能是觉得样本熵的代码太简单,没有必要进行优化。

参考文献


  1. 杨福生,廖旺才.近似熵:一种适用于短数据的复杂性度量[J].中国医疗器械杂志,1997(05):283-286. ↩︎

  2. 赵志宏, 杨绍普.一种基于样本熵的轴承故障诊断方法[J].2012-06. ↩︎

  3. 刘慧, 谢洪波, 和卫星, 等. 基于模糊熵的脑电睡眠分期特征提取与分类[J]. 数据采集与处理,2010,25(4):484-489. ↩︎

  4. Chen W, Wang Z, Xie H, Yu W. Characterization of surface EMG signal based on fuzzy entropy. IEEE Trans Neural Syst Rehabil Eng. 2007 Jun;15(2):266-72. doi: 10.1109/TNSRE.2007.897025. PMID: 17601197. ↩︎

有关【Python】近似熵,样本熵,模糊熵计算高效版的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  3. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  6. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  7. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  8. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  9. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

    2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

  10. Python 刷Leetcode题库,顺带学英语单词(31) - 2

    ValidPalindromeGivenastring,determineifitisapalindrome,consideringonlyalphanumericcharactersandignoringcases. [#125]Example:"Aman,aplan,acanal:Panama"isapalindrome."raceacar"isnotapalindrome.Haveyouconsiderthatthestringmightbeempty?Thisisagoodquestiontoaskduringaninterview.Forthepurposeofthisproblem

随机推荐