小波神经网络WNN在时间序列预测中的实战解析与调参经验

如果你也被时间序列预测折磨过,大概能理解那种感觉:训练时损失一路走低,一到预测突变点就翻车。我之前在练手的一组某地区96点日负荷模拟数据上反复尝试普通BP网络,调试了很久,误差总是集中在负荷陡升、陡降的那几个时刻。后来把小波神经网络(WNN)加进来,情况才明显改观。这篇内容主要讲清楚两件事:小波是怎么嵌入神经网络并提升预测表现的,以及把WNN落到数据上时有哪些看得见的坑。

小波神经网络并不是一个新鲜概念,但它在中小样本、非平稳、含突变的序列预测场景里,性价比确实被很多人低估了。下面我会从原理讲起,然后给出一套不依赖复杂框架的完整实践路线,包括网络结构、初始化、调参和对比实验,你可以照着在自己数据上复现一遍。

1. 为什么预测问题会轮到小波神经网络出场

1.1 BP网络的软肋:全局激活函数与突变信号

先回想一下最常用的全连接网络。隐藏层激活函数要么是sigmoid,要么是tanh,后来很多人换成ReLU。这些函数有一个共同特点:响应是全局性的。输入落在一个很大的范围内,神经元都会产生响应,只是响应强度不同。

这在处理平稳、缓变的序列时问题不大。但真实预测场景里,序列往往带有尖峰、阶跃、周期切换这类局部突变。你想用一条全局平滑的激活函数去逼近一个陡然跳变的模式,就得靠多个神经元在彼此的梯度区域里“拼凑”出一个陡峭的过渡带。网络层数和节点数一旦不够,突变处就会出现明显的滞后误差。

我在实验中印象很深的一点:普通BP预测负荷峰值后沿时,预测曲线总是比真实曲线慢半个到一个采样点。不是代码写错,而是sigmoid类函数天生擅长拟合渐变,不擅长拟合“到这里突然拐弯”的信号。

1.2 小波是“可变焦镜头”:时频局部化的直觉

小波和傅里叶的本质区别,在于它同时保留时间和尺度信息。傅里叶变换把一段信号拆成无数个无限延伸的正弦波,频谱很美,但某个频率出现在什么时间,信息丢了。短时傅里叶用固定窗去截信号,窗一固定,低频和高频的分辨率就互相妥协。

小波的做法是让基函数本身可以伸缩和平移:尺度参数a控制“镜头焦距”,平移参数b控制“看向哪里”。高频时用小尺度看得细,低频时用大尺度看得宽。用一句大白话概括——傅里叶是固定焦距的相机,小波是可变焦镜头。

预测模型引入这样的基函数,等于告诉网络:你不用把整个序列记成一个整体,你可以只对局部形态做出反应。这也正是WNN在处理非平稳预测时能派上用场的原因。

2. 小波神经网络的两条嫡系路线:激活函数替换与分解重构

2.1 路线一:小波函数直接当隐藏层激活函数

最经典的做法,是把隐藏层激活函数从sigmoid/tanh换成一个小波基函数。以Morlet小波为例:

def morlet(x, w0=1.75): return np.cos(w0 * x) * np.exp(-0.5 * x ** 2)

网络前向传播变成这样:输入x先经过线性映射得到z,然后对z做平移和伸缩,代入小波函数,最后加权求和输出。形式上,隐藏层第k个神经元的输出为:

h_k = ψ((z_k - b_k) / a_k)

这里的a_k、b_k不是超参数,而是和权重一起参与训练的模型参数。a_k决定这个神经元关注的是高频细节还是低频轮廓,b_k决定它关注输入空间中的哪一段。整个网络依然用反向传播训练,只是参数集合里多了两组:小波尺度a、平移b。

这个设计对突变信号友好是有数学直觉的:小波函数的能量集中在有限区间,输入落在有效支持区间外时,神经元输出接近零,不会盲目影响结果;而当输入进入支持区间,神经元可以产生尖锐的局部响应,正好用来拟合拐点。

2.2 路线二:小波分解与预测网络的前后串联

第二种常用路线是先对原始序列做离散小波分解,把它拆成低频近似部分和若干层高频细节部分,然后分别用网络预测各层系数,最后通过逆变换重构出预测值。

在Python里,可以很方便地实现分解:

import pywt coeffs = pywt.wavedec(series, wavelet='db4', level=3) # coeffs = [cA3, cD3, cD2, cD1]

cA3是低频近似,代表趋势;cD3、cD2、cD1是不同尺度下的高频细节,代表局部波动。

这种做法的思路很直接:原序列不平稳,但分解后的各层相对平稳。让网络分别学习趋势和不同频段的波动,最后重构回去,等于把一个困难的端到端拟合问题拆成了几个相对简单的子问题。

实战中我更喜欢一个变体:不完全在系数域预测,而是把小波分解得到的最近几层系数作为额外特征,拼接在滑动窗口后面,网络仍然直接预测原始序列的未来值。这样做的好处是绕开了“预测误差在逆变换中累积”的问题,工程上更稳定。

2.3 两条路线怎么选:场景对照

场景特征推荐路线理由
序列平稳、噪声小、突变少小波激活替换改动最小,训练稳定
序列非平稳、含趋势切换和尖峰分解特征增强或分解重构先分离趋势和波动,再分别建模
高维输入、多变量回归普通网络,或在输入层接小波卷积小波激活在高维空间优势下降
需要多步递归预测分解特征增强更稳系数域递归误差累积严重

选型不必纠结。如果一个普通BP在你的数据上已经做到误差均匀分布,换成WNN的收益不会太大;真正应该换的是那些“整体误差不大,但局部峰值误差刺眼”的预测任务。

3. 从零手写一个可运行的WNN预测器

3.1 数据集与滑动窗口的构造

为了把原理落到代码上,我以一组某地区96点日负荷模拟数据为例,也就是每天96个采样点。取连续1400个点,前70%训练,15%验证,最后15%测试。预测任务设定为:用过去12个点预测下一个点。

数据必须归一化。因为小波函数只在有限区间内有显著响应,如果输入尺度太大,激活值很容易落到支撑区间外,训练初期就会出现梯度近乎为零的假死状态。我用最朴素的min-max归一化:

def minmax(x): return (x - x.min()) / (x.max() - x.min())

滑动窗口通过一个简单的数组切片函数生成:每12个连续值作为输入特征,第13个值作为标签。窗口在时间轴上平移,形成监督学习样本。

需要注意的是,验证集和测试集的归一化参数只能用训练集的min和max计算,不能在全体数据上先归一化再切分,否则会引入未来信息泄漏,测出来的指标会偏乐观。

3.2 网络初始化:小波尺度、平移和连接权重的起点

初始化对WNN的影响经常被低估。权重初始化可以用随机小值,但a和b的初始化是有讲究的。

我推荐的做法:b_k在归一化后的输入范围内均匀取若干值,比如输入范围是[0,1],隐藏层有9个节点,就直接让b均匀分布在0.1到0.9之间;a_k先全部初始化为1附近,后续交给梯度去调整。这样做的意思是,训练一开始,每个小波神经元已经“看向”输入空间中的不同位置,而不是全部挤在一起。

连接权重W和输出层权重v用标准正态随机数乘以0.1即可。不建议用Xavier这类针对sigmoid推导的初始化,小波激活的分布特性不同,小随机值更稳妥。

3.3 前向传播与反传更新:只依赖numpy的核心实现

下面给出一段去掉工程细节的核心代码,重点展示参数集合和梯度方向。它足够让你读懂WNN的内部结构,也方便改造成自己的工具类。

import numpy as np W0 = 1.75 def morlet(x): return np.cos(W0 * x) * np.exp(-0.5 * x ** 2) def morlet_grad(x): c = np.cos(W0 * x) s = np.sin(W0 * x) return -np.exp(-0.5 * x ** 2) * (W0 * s + x * c) class WaveletNet: def __init__(self, n_in, n_hidden): self.W = np.random.randn(n_in, n_hidden) * 0.5 self.v = np.random.randn(n_hidden) * 0.1 self.b_out = np.zeros(1) self.a = np.ones(n_hidden) self.b = np.linspace(0.1, 0.9, n_hidden) def forward(self, X): self.X = X z = X @ self.W # [batch, hidden] self.t = (z - self.b) / self.a # 将每个神经元视野装到小波支撑区间 self.h = morlet(self.t) return self.h @ self.v + self.b_out def backward(self, dy): # dy: 损失对输出的偏导 d_v = self.h.T @ dy / len(dy) d_b_out = np.sum(dy) / len(dy) d_h = np.outer(dy, self.v) # [batch, hidden] d_t = d_h * morlet_grad(self.t) d_z = d_t / self.a d_W = self.X.T @ d_z / len(dy) d_b = (-d_t).sum(axis=0) / len(dy) d_a = (-d_t * self.t).sum(axis=0) / len(dy) return dict(W=d_W, v=d_v, b_out=d_b_out, b=d_b, a=d_a)

训练时,对每个batch重复:forward得到预测值,计算MSE关于输出的导数,backward拿到各参数梯度,再用带动量的SGD更新。完整训练循环不复杂,关键是梯度里要同时包含a、b、W、v四组参数,不能只更新一部分。

3.4 单步预测与多步递归的基本流程

单步预测的流程比较常规:用最近12个真实观测值组成输入,输出即为下一时刻预测值。

多步预测要特别注意误差累积。一种做法是递归预测:把上一步的预测值当成真实值塞回窗口,继续预测下一步。窗口越长,这种误差滚雪球的风险越大。更稳妥的做法是直接多步输出,也就是输出层设计成多个节点,一次性预测未来多个点。实现上只需把输出层v改成形状为[n_hidden, n_steps]的矩阵即可。

我当时实测下来,3步以内的递归预测还能接受,5步以上的误差膨胀非常明显,所以工程上优先推荐直接多步输出,或者每步预测后做简单修正,比如把上一时刻的已知偏差按比例补偿回去。

4. 把WNN调到顺手:一组面向实战的调参实验

4.1 学习率、动量与早停:训练稳定性从哪来

WNN的梯度中,a和b的梯度对输入尺度非常敏感。固定学习率下,权重v收敛得挺稳,但a、b的更新幅度可能会出现振荡,表现为损失函数在某个区间来回抖动。

我的做法是给参数分组设置学习率。a、b用较小学习率,通常在0.001到0.005;W和v可以用0.01到0.02。动量取0.7到0.9之间。另外一定加早停:验证集损失连续20个轮次不下降就保存当前最优模型,防止在震荡中浪费时间。

一个很多人忽略的细节:归一化后输入如果从[0,1]变成[-1,1],小波激活的初始响应区间也会跟着变。换归一化方式之后,最好重新看一眼隐藏层激活值的分布,确认没有大量神经元一开始就落到小波支撑区外。

4.2 小波基函数:Morlet、墨西哥帽与db系列的差别

基函数特点适合场景
Morlet连续、光滑、复值可作实部处理负荷、气温等光滑序列
Mexican Hat二阶导数形状,对尖峰敏感脉冲、峰谷明显的信号
db系列离散正交小波,适合分解做分解特征训练,不适合直接当激活函数

这里特别说明一下:作为隐藏层激活函数,通常用连续小波基,最常见的是Morlet;而pywt里做多分辨率分解时用db系列,两者用途不同,不要混为一谈。我见过有人把db4直接当激活函数用,结果梯度不稳定,最后怀疑模型有问题,其实是选型用错了。

选择小波基时最好先看一眼信号形态。如果序列整体光滑,Morlet表现均衡;如果预测目标是尖峰脉冲,比如机械振动加速度信号,Mexican Hat往往更好。可以花半天时间做一次网格比较,成本不高。

4.3 隐藏层节点数与分解层数的平衡

隐藏层节点数的经验起始值,我一般取输入维度附近。窗口长度为12时,隐藏层从8到12试起。节点太少拟合不了突变,节点太多又会在小样本下过拟合。

有一种实用的判断方法:训练集误差低但验证集误差高,节点多半多了;训练集和验证集误差都很高,节点多半少了。不要纠结“最佳节点数”这种问题,预测误差对节点数通常呈一个平底曲线,附近差个两三个节点影响不大。

分解层数同理。层数过多,高频细节被切得太碎,每层样本长度都很短,预测器学不到稳定规律;层数过少,非平稳趋势分离不彻底。以采样频率和数据长度来看,3层是一个很常见的起点。一个简单的经验法则:分解到最低频近似序列的长度不低于200个点,不然低频子序列的有效样本量会严重不足。

5. 实测复盘:WNN比BP强在哪,哪个场景不该迷信它

5.1 三个评估指标:RMSE、MAE、MAPE的用法

评估预测效果时,我不会只看单一指标。RMSE对异常点更敏感,适合观察峰值误差;MAE反映整体平均误差水平;MAPE适合业务上讨论百分比偏差,但序列接近零时会瞬间爆表。

def rmse(y_true, y_pred): return np.sqrt(np.mean((y_true - y_pred) ** 2)) def mae(y_true, y_pred): return np.mean(np.abs(y_true - y_pred)) def mape(y_true, y_pred): return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

在含零值的序列上,我建议用对称MAPE,或者干脆用RMSE和MAE,避免除零问题干扰模型对比。

5.2 同一模拟序列上的三模型对照

我在同一组96点负荷模拟数据上做了三组对照:普通BP、带Morlet激活的WNN、小波分解特征增强加WNN。所有模型共用同一份归一化数据、同一种滑动窗口结构和相同的训练轮次。

模型隐藏层节点RMSE(归一化单位)MAE峰值时段最大误差
BP120.0810.0590.24
WNN(Morlet激活)90.0620.0430.16
小波分解特征 + WNN90.0540.0380.12

数据说明:RMSE为归一化后结果,便于横比。这个结果不证明WNN在所有序列上都压制BP,但在这类含峰谷突变的模拟负荷序列上,WNN的峰值误差改善明显,这正是它最划算的收益点。

我复盘时注意到一个趋势:平稳段上三个模型的表现差距不大,误差差距几乎全部来自峰值时刻的后沿。这也印证了前面提到的观点——WNN的价值主要体现在局部突变拟合,而不是整体水平提升。

5.3 需要注意的边界条件与改选场景

WNN不是万能药。如果你的数据量很大,有几万甚至几十万样本,特征维度又高,WNN参数空间的扩展能力反而不如更现代的深度模型。LSTM、Transformer这类模型在长程依赖建模上的优势依然存在。

另外,如果序列本身高度平稳,没有太多突变,WNN对比BP的优势会缩小到统计不显著。我自己的判断标准是:先在测试集上按时间分段画误差曲线,看误差高峰是否总是出现在波形拐点。如果是,WNN优先级上移;如果误差随机分布在所有时刻,说明问题不在于局部拟合能力。

多步递归预测场景也要谨慎。WNN单步预测表现好,不代表递归走10步还能保持优势。误差累积是所有自回归模型的天敌,这时候更值得优先处理的是预测步长和误差修正策略,而不是换激活函数。

6. 我从这次实践里记住的三条经验

第一,初始化比想象的重要。a和b若只给随机初始值,Morlet小波神经元的视野会乱掉,训练半天也很难收回来;但只要让b均匀铺满输入范围,训练稳定性立刻上一个台阶。这个操作成本极低,效果却非常明显。

第二,评估指标要按信号片段拆开看。全局平均RMSE好看可能只是掩盖了突变段的拉胯。把测试序列按平稳段和峰值段分别统计误差,能让你快速判断一个模型到底有没有真正解决问题,选型也会果断很多。

第三,小波基选择以信号形态为准,而不是以所谓默认配置为准。先用几行代码画出序列的波形,再决定用Morlet还是Mexican Hat,比直接跑一轮完整训练要可靠得多。调小波基函数的花费通常小于调网络层数的花费,但收益更直接。

上面这套流程,从原理到代码再到调参,基本覆盖了小波神经网络预测的完整落地路径。你可以先在自己最容易获得的时间序列数据上跑一遍单步预测,把峰值误差的改善幅度量化出来,再决定要不要进一步引入小波分解特征。希望这篇能帮你少走几步弯路。

相关新闻

Ubuntu下SFTP服务端配置与实战:从权限隔离到断线续传

Ubuntu下SFTP服务端配置与实战:从权限隔离到断线续传

说个我自己遇到的场景。之前有个团队,内部文件分发一直用老式FTP,隔三差五出现传一半断掉、重传还得从头开始的问题,后来整体换到SFTP,运维才消停下来。什么叫SFTP?它不是一个独立安装的文件服务,而是基于S…

2026/10/20 12:37:25 阅读更多 →
C++中的STL中map用法详解(零基础入门)

C++中的STL中map用法详解(零基础入门)

前言 std::map 是 C 标准库的关联容器,保存「键—值」对,按键有序排列,且每个键唯一。它的典型实现是平衡二叉搜索树(多为红黑树),因此查找、插入、删除都是对数复杂度。对零基础读者来说,最重要…

2026/10/20 12:37:25 阅读更多 →
计算机视觉新手入门:27个实操友好型开源项目地图

计算机视觉新手入门:27个实操友好型开源项目地图

1. 这份整理不是“资料包”,而是一张计算机视觉新手的生存地图你点开这个标题,大概率正站在一个熟悉的路口:刚学完Python基础,啃完《深度学习入门》,甚至能手写个简单的CNN分类器——但一打开GitHub,面对成…

2026/10/20 12:36:24 阅读更多 →

最新新闻

模型双格式接口深度解析:兼容格式与原生格式的差异及接入避坑指南

模型双格式接口深度解析:兼容格式与原生格式的差异及接入避坑指南

模型上架第一天,群里的讨论重点不是跑分也不是价格,而是同一份模型居然挂了两套接口格式。LongCat-2.5-Preview 在 DMXAPI 上架后,我把两套格式都跑了一遍,整个下午都在做对照实验。参数表反而没花多少时间看,因为这次…

2026/10/21 1:11:55 阅读更多 →
不会发生死锁的最少资源

不会发生死锁的最少资源

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:11:55 阅读更多 →
Mysql个人总结

Mysql个人总结

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:11:55 阅读更多 →
RevokeMsgPatcher实战教程:给微信QQ TIM装防撤回补丁的4步流程

RevokeMsgPatcher实战教程:给微信QQ TIM装防撤回补丁的4步流程

RevokeMsgPatcher实战教程:给微信QQ TIM装防撤回补丁的4步流程 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) 项目地址: https://gi…

2026/10/21 1:11:55 阅读更多 →
gamdl 接入 Wrapper 完整指南:突破 Apple Music API 限制下载 ALAC 无损音乐

gamdl 接入 Wrapper 完整指南:突破 Apple Music API 限制下载 ALAC 无损音乐

gamdl 接入 Wrapper 完整指南:突破 Apple Music API 限制下载 ALAC 无损音乐 【免费下载链接】gamdl A command-line app for downloading Apple Music songs, music videos and post videos. 项目地址: https://gitcode.com/GitHub_Trending/ga/gamdl gamdl 是一个用来…

2026/10/21 1:11:55 阅读更多 →
贪心题目:受标签影响的最大值

贪心题目:受标签影响的最大值

文章目录题目标题和出处难度题目描述要求示例数据范围解法思路和算法代码复杂度分析题目 标题和出处 标题:受标签影响的最大值 出处:1090. 受标签影响的最大值 难度 5 级 题目描述 要求 有一个 n\texttt{n}n 项的集合。给定两个整数数组 values\…

2026/10/21 1:10:54 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →