96SEO 2026-02-23 15:04 14
none">基于朴素贝叶斯对按键动作进行模式识别的一次学习实验

之前学习了贝叶斯更新的相关内容,正好现在也在玩开发板,板子上面有几个小的单击按键,一般识别按键动作的做法就很简单,不是中断就是查询,基本都是靠边沿或者电平的状态来进行的,这一套就很无聊,没有实现的欲望,所以想用点不一样的方法。
这就有了本片文章的出现,基于朴素贝叶斯分类,使用滑动窗口捕捉电平序列,提取特征进行模式识别,理想情况下识别效果杠杠的,但是出现边界以及混合的情况,效果一言难尽,目前水平不够,这应该也是后续需要解决的主要问题了。
本文实现的方法基于朴素贝叶斯分类器,主要就是两方面内容:贝叶斯定理与条件独立假设,涉及的概念有先验概率、后验概率和条件概率,其中先验和条件概率都是提前准备好的,可以是主观经验的,也可以是统计量化的,而贝叶斯定理中的条件概率(不是后验概率),又称为似然概率。
这个方法的基本思想是:对于给定的待分类项(就是窗口中的电平序列),求解当这个待分类项出现时,各个已经定义过的模式类别出现的概率,哪个概率最大,那么这个待分类项就属于哪个模式。
这里的窗口是实时更新的窗口,老数据移出,新数据加入,滑动窗口确定电平序列数据的范围,只有处在窗口中的序列数据才会得到特征提取的机会,它的长度与序列的时间长度成比例,也就是说采样频率会影响到窗口时效性。
它需要考虑的问题是怎么捕捉到完整的信号,对应于滑动的步长,以及特征提取的周期。
为了验证设想的可行性,通过逻辑分析仪记录按键的引脚电平变化,低电平表示按键按下,高电平表示无按键动作,采样率1MHz,时长20s,在后面的实验中,认为序列是连续的,这就是电平序列的来源,具体序列如下图所示:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img14@main/2026/02/17/1771293682747-d81c3402-eb3d-4b28-adba-763adf4d2ca0.png"
上面记录的数据可以作为一个样本,我通过观察和测量确定了几种模式,以及一些帮助识别的特征属性,在实验过程中使用python进行了方法验证。
动作的实施都是通过一个单按键来进行的,其中单击和双击涉及到电平的较快速变化,是识别的难点
并且和一般的贝叶斯方法不同的是,在实现过程中认为先验是不需要更新的,也就是在每一次识别时认为每个模式都是等概率出现的,没有转移概率或者历史因素影响
把逻辑分析仪中的数据导出为csv文件,代码首先实现了 read_sigrok_csv_simple 函数,用于读取
class="language-python">def read_sigrok_csv_simple(filename): time_data = []
signal_data = [] with open(filename, 'r', newline='') as csvfile: reader = csv.reader(csvfile) for row in reader: # 跳过注释行和空行 if not row or row[0].startswith(';'): continue # 确保行有两个列 if len(row) >= 2: try: time_val = float(row[0]) data_val = float(row[1]) time_data.append(time_val) signal_data.append(data_val) except ValueError: # 跳过无法转换为数字的行 continue return time_data, signal_data
文件中的时间戳和信号值,返回两个列表分别存储时间数据和信号数据,通过plot输出采样的数据图如下所示:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img0@main/2026/02/17/1771293729251-6f4bcd36-9a3b-40e6-beb1-e25327a680c4.png"
核心实现是 BayesianButtonRecognizer 类,用于实现基于贝叶斯分类的按键模式识别:
class="language-python">class BayesianButtonRecognizer: """基于滑动窗口和贝叶斯更新的按键模式识别器""" def __init__(self, window_size=20, sample_interval=0.01, threshold=0.7): """ 初始化识别器 Args: window_size: 滑动窗口大小 sample_interval: 采样间隔(秒) threshold: 判定阈值 """ self.window_size = window_size self.sample_interval = sample_interval self.threshold = threshold # 滑动窗口存储最近的观测序列 self.window = deque(maxlen=window_size) # 模式类别 self.modes = ['无效', '单击', '双击', '长按'] # 先验概率 - 初始等可能 self.prior = np.array([0.25, 0.25, 0.25, 0.25]) # 特征提取相关的参数(单位:采样点数) self.short_press_max = 15 # 短按最大持续时间 self.long_press_min = 30 # 长按最小持续时间 self.double_click_interval = 10 # 双击间隔阈值 # 初始化特征分布参数(基于物理理解预设) self._init_feature_distributions() # 特征权重 self.featwight={ "无效":np.array([1.2,0.8,0.8,1.2]), "单击":np.array([1,1.2,1.2,1]), "双击":np.array([1,1.2,1.2,0.8]), "长按":np.array([1.2,0.8,0.8,1.2]) }
识别器初始化时设置了各模式下特征的概率分布参数:
class="language-python">def _init_feature_distributions(self): """初始化各模式下特征的概率分布参数""" # 高电平占比的分布参数 self.high_ratio_params = { '无效': 0.05, # 无效时高电平占比很低 '单击': 0.2, # 单击时有短暂高电平 '双击': 0.3, # 双击时高电平占比稍高 '长按': 0.9 # 长按时高电平占比很高 } # 上升沿数量的分布参数 self.rise_count_params = { '无效': 0.1, # 无效时几乎无上升沿 '单击': 1, # 单击时有1个上升沿 '双击': 2, # 双击时有2个上升沿 '长按': 0.7 # 长按有1个上升沿 } # 最长高电平持续时间的分布参数(正态分布:均值,标准差) self.max_duration_params = { '无效': (0, 2), # 无效时持续时间很短 '单击': (0.2, 5), # 单击中等持续时间 '双击': (0.17, 3), # 双击每次按下时间短 '长按': (0.9, 10) # 长按持续时间长 }
从滑动窗口数据中提取特征,其中高电平占比是通过求序列平均值来获得的,然后边沿计数对应了记录序列跳变数量,最长高电平时间通过记录连续高电平时长获取:
class="language-python">def extract_features(self, window_data): """从滑动窗口数据中提取特征""" if len(window_data) == 0: return None data = np.array(window_data) # 特征1: 高电平占比 high_ratio = np.mean(data) # 特征2: 上升沿数量(0->1的变化) rises = 0 for i in range(1, len(data)): if data[i-1] == 0 and data[i] == 1: rises += 1 # 特征3: 下降沿数量(1->0的变化) falls = 0 for i in range(1, len(data)): if data[i-1] == 1 and data[i] == 0: falls += 1 # 特征4: 最长连续高电平持续时间 max_duration = 0 current_duration = 0 for val in data: if val == 1: current_duration += 1 max_duration = max(max_duration, current_duration) else: current_duration = 0 return { 'high_ratio': high_ratio, 'rise_count': rises, 'fall_count': falls, 'max_duration': max_duration }
计算给定模式下观测到特征值的似然概率,即条件概率,通过上面定义的分布参数,使用正态分布近似,在python中通过stats.norm.pdf求特征对应每个模式的似然程度,然后基于条件独立的假设,求解联合似然,表示样本对某一模式的最终似然结果:
class="language-python">def calculate_likelihood(self, features, mode): """计算给定模式下观测到特征值的似然概率""" if features is None: return 1.0 # 无特征时返回中性似然 # 使用概率密度函数计算各特征的似然 likelihoods = [] # 1. 高电平占比的似然 target_ratio = self.high_ratio_params[mode] # 使用正态分布近似, 标准差根据经验设定 like_ratio = stats.norm.pdf(features['high_ratio'], target_ratio, 0.2) likelihoods.append(like_ratio + 1e-10) # 避免零 # 2. 上升沿数量的似然 target_rises = self.rise_count_params[mode] like_rises = stats.norm.pdf(features['rise_count'], target_rises,0.3) likelihoods.append(like_rises + 1e-10) # 3. 下降沿(同上升沿)数量的似然 target_falls = self.rise_count_params[mode] like_falls = stats.norm.pdf(features['fall_count'], target_falls,0.3) likelihoods.append(like_falls + 1e-10) # 4. 最长持续时间的似然(使用正态分布) target_dur, std_dur = self.max_duration_params[mode] target_dur *= self.window_size like_duration = stats.norm.pdf(features['max_duration'], target_dur, std_dur) likelihoods.append(like_duration + 1e-10) # 组合各特征的似然(假设特征条件独立) total_likelihood = np.prod(np.array(likelihoods)) print("特征在mode[%s]的似然:"%{mode},likelihoods,"最终联合似然:%. 3f"%total_likelihood) return total_likelihood
class="language-python">def slide_window(self,io_state): # 移除最旧的值 self.window.popleft() # 将新观测值加入滑动窗口 self.window.append(io_state)
计算完样本对每个模式的似然后,就于先验概率相乘,就得到了后验概率,然后归一化得到最终结果,同时使用阈值判定机制,当最大后验超过判定阈值后,才会识别具体模式,否则就是不确定
class="language-python">def update_belief(self, io_state): """根据新观测值更新信念""" # 提取当前窗口的特征 features = self.extract_features(self.window) print("特征提取:",features) # 计算各模式的似然 likelihoods = np.array([self.calculate_likelihood(features, mode) for mode in self.modes]) # 贝叶斯更新: 后验 ∝ 似然 × 先验 unnormalized_posterior = likelihoods * self.prior evidence = np.sum(unnormalized_posterior) if evidence > 0: posterior = unnormalized_posterior / evidence else: posterior = self.prior.copy() # 更新先验(用于下一次迭代) # self.prior = posterior # 判断当前模式 best_mode_idx = np.argmax(posterior) best_prob = posterior[best_mode_idx] print("后验:",posterior) if best_prob > self.threshold: detected_mode = self.modes[best_mode_idx] else: detected_mode = '不确定' return detected_mode, posterior
因为定义了高电平为有效电平,但实际中低电平,或者说下降沿是按键动作的反应,所以处理数据序列时做了相应的取反处理。
class="language-python">if __name__ == "__main__": DeltaT = 0.01
采样间隔 UnitTime = 1e-06
原始数据点的时基 SampleInterval = math.floor(DeltaT / UnitTime) filename = "key_data_20s_all.csv" # 逻辑分析仪导出的数据 recognizer = BayesianButtonRecognizer(window_size=100, threshold=0.8) recognizer.reset() time_data, signal_data = read_sigrok_csv_simple(filename) print(f"成功读取数据,共 {len(time_data)} 个数据点") print(f"时间范围: {time_data[0]}s 到 {time_data[-1]}s") plt.figure(1) sample_data = [] res_data = [] sample_num = math.floor(len(signal_data) / SampleInterval) print("sample size is:",sample_num) for i in range(sample_num-1): sample_data.append(int(not signal_data[SampleInterval*i])) recognizer.slide_window(int(not signal_data [SampleInterval*i])) if i%recognizer.window_size==0: res,postrior=recognizer.update_belief(i) if(res not in["不确定","无效"]): res_data.append(res) print("win[%d]:"%i,res) plt.plot(recognizer.window) plt.show() plt.figure(1) plt.plot(sample_data) plt.show() print(res_data)
data-src="https://fastly.jsdelivr.net/gh/bucketio/img16@main/2026/02/17/1771293783552-374386fd-51ad-4ecc-90cb-c38b2aa5a203.png"
上图是一个无效按键样本序列图,保持无效电平,没有边沿变化。
下图给出了识别的过程和结果:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2026/02/17/1771293803151-94e9e91f-21ff-4ccc-995a-e4dee84ed04b.png"
可以看到特征提取的信息是正确的,高电平占比为0,边沿计数为0,最长高电平延时为0,在各个模式的似然列表中,给出了对应的似然结果,同时从列数据对比来看,也可以直接从数值上看出样本特征更偏向哪个模式,最终的后验结果,确实是无效模式的概率最高,即判定窗口中的序列为无效。
单击样本示例:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img12@main/2026/02/17/1771293891198-b35c3126-6cdb-4e0c-aa8d-866bb50f0abb.png"
上图是一个单击按键样本序列图,有边沿变化,一个上升沿,一个下降沿,高电平占比大约0.2。
下图给出了识别的过程和结果:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img18@main/2026/02/17/1771293913887-243f48a3-b10e-4539-a556-b64928912fe1.png"
可以看到特征提取的信息是正确的,最终的识别结果也是正确的
双击样本示例:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img1@main/2026/02/17/1771293929485-ba56eac4-b956-455e-91a9-60a337f17f6d.png"
上图是一个双击样本的示例图,可以看到由两个高电平组成,下图给出识别过程和结果:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img1@main/2026/02/17/1771293960527-ad7cc398-d923-41ef-8d6f-ad712670b32d.png"
可以看出特征提取信息正确,有两个上升沿和两个下降沿,然后最终的后验概率中也是双击的概率最大,并且超过阈值判定正确。
边界双击情况示例:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img16@main/2026/02/17/1771293978238-9f37d07c-3bc9-4da7-9aa1-b1e750afd69f.png"
上图中可以看出很明显是一个双击的动作,但是由于窗口长度固定的原因,导致一部分序列缺失,下图给出识别结果:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img13@main/2026/02/17/1771293991565-251bfdcd-4f0c-4384-bad2-ec45caef56a4.png"
特征提取的信息倒是正确的,识别出下降沿只有1个,在计算似然过程中,相应位置的似然结果也反应了这一点,最终的后验表中可以看到前两个大的概率是单击和双击,但是都没超过阈值,所以判定为不确定
边界单击情况示例:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2026/02/17/1771294007027-42bf15d6-a5f4-439c-9b7e-9373e29bb1e6.png"
可以看出这个情况像是单击,但是实际上是一段长按序列,下图给出识别过程:
data-src="https://fastly.jsdelivr.net/gh/bucketio/img11@main/2026/02/17/1771294043631-9760e86e-65e2-4318-ba9a-a81f8662083f.png"
特征信息提取是正确的,然后似然结果都偏低,表示不偏向某一个模式,但在最终的后验结果中单击的后验概率异常的高,应该是在归一化过程中,单击概率占比比其他概率大很多导致的,这也是同样的问题,也就是信号完整性缺失导致了误判
在这次实验中,基于朴素贝叶斯分类方法,通过滑动窗口采集数据、提取多维度特征、计算概率分布和应用贝叶斯更新,学到了不少,也融合了很多内容,算是一次不小的学习体验吧,虽然目前测试下来效果有限,还无法真正用在项目中,也总结了一些不足的地方。
比如信号完整性保证不了,不同特征属性对不同模式的权重实际并不一致等,这些都是需要解决的问题,虽然对现在的我来说很困难,但探索新方法的过程还是蛮喜欢的,也可能是对现有方法的审美疲劳导致的吧。
但有一说一,传统的方法,还是简单高效的,也不涉及到什么数学的内容,全凭逻辑加判断就可以搞定了,真是省时省力啊。
role="contentinfo">
本文来自博客园,作者:pie_thn,转载请注明原文链接:https://www.cnblogs.com/pie-o/p/19622890
class="post-meta-container">
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback