基于BP神经网络的发动机万有特性图拟合与Matlab实现

干过台架测试的人都有体会:折腾一个发动机的万有特性图,最费时间的不是画图本身,而是凑够那几十上百个稳态工况点。转速、转矩、燃油消耗率三个量,台架上一组一组地测、一个一个地记,有时候连续占设备两三天,最后拿到手里的数据还是有不少空白区域。后来我做某型号柴油机标定预测项目时,开始用BP神经网络来干这件事,做法说到底就是把转速和转矩当输入、燃油消耗率当输出,让网络学出三者之间的映射关系,再在Matlab里把预测结果画成等高线式的等油耗曲线族。整套流程跑顺之后,确实比传统手工插值省事得多,而且面对数据不均的工况区域也能给出相对合理的结果。这篇文章就把从数据准备到Matlab出图的完整流程、关键代码和踩过的坑一次性整理出来,适合正在做台架标定的工程师、做发动机性能仿真的同学,以及只想快速拿出万有特性图交差的人。

1. 从台架测试数据到万有特性图,传统方法的死穴在哪

1.1 万有特性图到底是张什么图

先花一分钟把底层的物理逻辑理顺。发动机万有特性图,本质上描述的是稳态工况与经济性之间的关系。横坐标是转速,常用单位r/min;纵坐标是转矩,常用单位N·m;图中一圈一圈闭合的曲线是等燃油消耗率线,单位一般是g/kWh。除了油耗线,通常还会叠加一族红色的等功率线,单位kW。读图方法和看地图等高线完全一样:最内圈曲线对应油耗最低的工况区域,也就是经济区;越往外油耗越高;功率线则是从左上向右下伸展的一族双曲线。

能画出这张图的前提,是你手头有足够的稳态工况测试数据。台架标定的常规流程是:固定油门开度或者固定循环喷油量,把转速从低往高拉,再切换下一个油门位置重复这个过程,期间记录每个稳定工况点的转速、转矩和燃油消耗率。说白了,万有特性图就是把"测过的点"变成"连续的规律",让工程师随便指一个工况,都能大概估算出油耗水平。

1.2 为什么人工插值和传统拟合法越来越不够用

传统画万有特性图的方式也很直白:把所有测试点投到坐标轴里,然后手工或借助绘图软件插值、描点、画等值线。数据密的时候这套方法没问题,但实际工程里会遇到三个实实在在的麻烦。

第一是测试成本。一张覆盖主要工况范围的万有特性图,往往需要几十甚至上百个稳态点。每个点要让发动机稳定运转几分钟、油耗流量计读数平稳后才算有效,整套测下来占台架一两天的排期很常见。赶项目节点的时候,想再补测一批数据几乎不现实。

第二是数据分布天然不均匀。台架实验存在大量"布点盲区",比如低转速大负荷区、发动机保护策略限制的区域,有的点很难稳定测出来,有的点干脆测不了。这些空白区域如果靠传统插值硬补,插出来的形状经常会严重偏离物理规律。

第三是人为因素太强。同样一组数据,不同的人画出来的等值线曲率、经济区位置可能差别很大,报告拿到评审会上容易被质疑。

BP神经网络的思路和这些老办法不太一样。它不做局部的几何插值,而是把"转速-转矩-油耗率"当做一个整体的连续映射来拟合。只要训练数据在主要工况范围内分布合理,训练好的网络就能输出任意位置上的油耗率预测值,布点盲区和人为差异这两个问题都能得到缓解。

2. 训练数据的采集与整理:别让网络学到错误规律

2.1 数据格式、覆盖范围和数据量的经验线

先说清楚网络要学的输入输出结构:

  • 输入1:转速 n(r/min)
  • 输入2:转矩 T(N·m)
  • 输出:燃油消耗率 be(g/kWh)

这个结构的合理性很直观:万有特性图本来就是be = f(n, T)这个三元函数曲面的水平切片,两个输入、一个输出,BP网络当成一个非线性回归问题来处理即可。

对数据的第一个要求是覆盖范围。训练集必须把将来绘图范围内的每个区域都安排上代表性工况点。我见过有人为了省事就测二十来个点,回来用BP网络硬训练,结果等值线画出来完全扭曲。我自己的经验线是至少60到100组稳态点,而且边界区域要有数据:最低转速、最高转速、最大转矩附近都要有样本,不然后续外推会出大问题。

第二个要求是分布均匀性。不要只做几条"固定油门从低拉到高"的等油门曲线,那样数据在负荷方向密度太差。尽量让每个转速区间都有大负荷、中负荷、小负荷的样本点穿插存在,这样网络学出来的曲面才平滑。

第三个要求是剔除异常记录。台架数据里经常混入怠速不稳、水温过高保护降扭、爆震限扭等瞬态工况的采样点,这些点的油耗率会明显偏离正常规律。训练之前先用散点图把n、T、be的关系大致扫一眼,或者用残差法、箱线图筛掉明显离群的样本。我在实际项目里发现,有些看起来"高得离谱"的油耗点,其实是热态保护降负荷瞬间测出来的,删掉之后网络训练结果立刻好看很多。

2.2 归一化的必要性和mapminmax的固定套路

BP网络隐藏层通常用S型传递函数tansig,这种函数只在输入接近0附近有像样的梯度响应。如果直接把转速5000、转矩300这种原始数值丢进网络,S型函数早就进入饱和区了,梯度极小,训练几乎学不动。所以第一步必须把输入输出都归一化到[-1, 1]区间。

Matlab里我固定用mapminmax函数来处理。这个函数的正确用法是:先用原始训练数据做归一化,得到一组设置参数并保存;后面网格数据做预测时,用同样的设置参数去'apply';最后把网络输出的归一化预测值'reset'回油耗率的物理量纲。不少人自己手写了个min-max公式,训练归一次、预测又用新数据的min和max归一次,前后坐标系对不上,画出来的图自然错位。这个坑下面代码里会有体现,先记住结论:归一化是一次性固化好设置,不允许预测阶段重新计算min和max。

3. 核心Matlab代码:从网络创建到等值线成形

3.1 网络结构和训练参数的选型逻辑

我的默认选择是单隐藏层的前馈网络。理由很简单:万能逼近定理保证了单隐藏层前馈网络可以逼近任意连续函数,而万有特性曲面本身是二元连续曲面,一个隐藏层完全够用,没必要叠更多层去增加训练负担。

隐藏层节点数没有绝对公式,我一般用经验范围试凑。对2输入1输出的问题,8到20个节点是合理的起点。节点太少,网络欠拟合,等值线会过于平滑,连最低油耗点的位置都糊在一起;节点太多,过拟合,训练误差漂亮但等值线出现锯齿状抖动。下面这张表是我拿一组实际柴油机台架数据做的节点数对比,记住这里看的是趋势,不是绝对值:

隐藏层节点数训练R²验证R²等值线形态
50.9120.893过于平滑,经济区不清晰
120.9870.972平滑且符合物理规律
200.9950.961开始出现局部抖动
300.9980.934锯齿状明显,过拟合信号

训练函数方面,小规模样本的回归问题我用trainlm(Levenberg-Marquardt),收敛快、精度高。如果出现过拟合迹象,就换trainbr(贝叶斯正则化),它会自动惩罚过大权值,抗过拟合能力很强,代价是训练慢一些,但几千个样本以内完全能接受。

3.2 完整代码逐段拆解

下面这份代码可以直接拿到工程里改数据用。数据文件我假设叫engine_data.xlsx,三列内容依次是转速(r/min)、转矩(N·m)、油耗率(g/kWh)。

clear; clc; close all; % ---------- 第一步:导入实验数据 ---------- data = xlsread('engine_data.xlsx'); n_raw = data(:, 1); T_raw = data(:, 2); be_raw = data(:, 3); % ---------- 第二步:归一化,注意保存设置参数 ---------- [n_norm, n_setting] = mapminmax(n_raw'); [T_norm, T_setting] = mapminmax(T_raw'); [be_norm, be_setting] = mapminmax(be_raw'); input_norm = [n_norm; T_norm]; % 2×N output_norm = be_norm; % 1×N % ---------- 第三步:创建并配置BP网络 ---------- hiddenLayerSize = 12; net = feedforwardnet(hiddenLayerSize); % 隐藏层用tansig,输出层用purelin net.layers{1}.transferFcn = 'tansig'; net.layers{2}.transferFcn = 'purelin'; % 训练函数:trainlm,小样本回归首选 net.trainFcn = 'trainlm'; net.trainParam.epochs = 1000; net.trainParam.goal = 1e-6; net.trainParam.min_grad = 1e-10; net.trainParam.showWindow = true; % 数据划分:训练/验证/测试 net.divideFcn = 'dividerand'; net.divideParam.trainRatio = 0.75; net.divideParam.valRatio = 0.15; net.divideParam.testRatio = 0.10; % ---------- 第四步:训练网络 ---------- [net, tr] = train(net, input_norm, output_norm); % ---------- 第五步:生成绘图网格 ---------- n_min = min(n_raw); n_max = max(n_raw); T_min = min(T_raw); T_max = max(T_raw); n_grid = linspace(n_min, n_max, 200); T_grid = linspace(T_min, T_max, 200); [NG, TG] = meshgrid(n_grid, T_grid); % 关键:预测时用训练时的设置参数归一化网格点 NG_norm = mapminmax('apply', NG(:)', n_setting); TG_norm = mapminmax('apply', TG(:)', T_setting); grid_input = [NG_norm; TG_norm]; % ---------- 第六步:预测并还原油耗率 ---------- be_grid = sim(net, grid_input); be_grid = mapminmax('reverse', be_grid, be_setting); BE = reshape(be_grid, size(NG)); % ---------- 第七步:绘制万有特性图 ---------- figure('Color', 'w'); % 等油耗线(黑色实线) [cs, hc] = contour(NG, TG, BE, 12, 'k'); clabel(cs, hc, 'FontSize', 8, 'LabelSpacing', 200); hold on; % 等功率线(红色虚线),功率 P = n*T/9550,单位kW Pe = NG .* TG / 9550; powerLevels = [10, 20, 30, 40, 50, 60]; [cp, hp] = contour(NG, TG, Pe, powerLevels, 'r--'); clabel(cp, hp, 'FontSize', 8, 'Color', 'r'); xlabel('转速 n (r/min)', 'FontSize', 11); ylabel('转矩 T (N·m)', 'FontSize', 11); title('某型号发动机万有特性图(BP神经网络拟合)', 'FontSize', 12); legend('等燃油消耗率线', '等功率线', 'Location', 'best'); grid on;

3.3 代码里几个值得注意的细节

先说说等值线的数量和标注。代码里的12只是一个起点值,它表示自动取12个油耗率水平,实际要根据你数据的范围灵活调整。想看清经济区,可以改成给明确级别列表,比如contour(NG, TG, BE, [250 260 270 280 300 320 340 360], 'k'),这样每一圈的油耗数值都由你说了算。LabelSpacing参数也很实用,它控制油耗数值标签的间距,防止一圈线上标签挤成一团。

功率公式这里简单推导一下。功率P与转矩T、转速n的关系是P = 2πnT/60,单位换算成kW后,系数约等于9550,所以代码里直接用NG .* TG / 9550。功率级别的选择要贴合你的发动机实际情况,最大功率80 kW的机器就没必要列100 kW的线,选10、15、20、30、40、60这类有代表性的功率档位即可。功率线画得太密,和黑色油耗线叠在一起会非常乱。

另外,如果你用的Matlab版本比较老,没有feedforwardnet,可以把第三步换成旧的newff接口:net = newff(minmax(input_norm), [hiddenLayerSize 1], {'tansig', 'purelin'}, 'trainlm');,后面的训练和绘制流程完全一样。两种接口我都跑过,结果差异不大。

4. 绘制阶段的两个坑:边界外推与等值线抖动

4.1 边界外推为什么会严重失真,怎么处理

BP网络在训练数据包围的内部区域预测结果比较可信,一旦超出数据覆盖范围,网络输出基本就只能靠传递函数的饱和趋势去"猜"了。这个猜的可靠性非常差。我记得有一次为了把图画完整,把转速网格强行延伸到比训练数据最低转速还低300 r/min的区域,结果那个区域等油耗线直接倒翻,经济区位置被整套带偏,明显不符合发动机物理规律。

处理办法有两个。一是网格生成阶段直接用训练数据的min/max卡边界,超出采样范围的点不预测,这是最稳妥的。二是如果项目确实需要一定程度的外推展示,那也一定要在图上做标注,比如把外推区域用浅色或虚线区分,并在报告里写明这部分可信度低。工程图纸上,宁可缺一块,也不给一个科学上站不住的结果。

4.2 等值线锯齿状抖动是过拟合的典型症状

如果训练完发现均方误差漂亮得离谱,但画出来的等值线弯弯扭扭、每一圈的曲率都很不自然,那大概率是网络把训练点上的噪声也当成规律学了。这种过拟合问题,按我的优先级处理顺序如下:

  1. 换trainbr。贝叶斯正则化会自动对权值做惩罚,抗过拟合能力最强。代价是训练速度慢,但万有特性训练样本量一般就几百个,完全等得起。
  2. 检查早停设置。net.trainParam.max_fail默认值是6,验证集误差连续上升6轮后训练会提前终止。如果数据划分比例里验证集太少,早停机制基本失效,所以trainRatio/valRatio/testRatio比例不能过于偏斜。
  3. 减少隐藏层节点数。对照上面节点对比表的思路,往8到12这个区间收一收。
  4. 回到源头清理数据噪声。油耗流量计本身精度有限,原始数据里有几g/kWh的波动太正常了,这些波动应该在数据准备阶段就处理,不要丢给网络去硬学。

4.3 画完图必须做的物理合理性检查

不管误差指标多好看,图出来以后都要做一次物理合理性检查。正常的柴油机万有特性,最低油耗区基本落在中等转速、中等偏大负荷区域,也就是最大转矩转速附近、负荷70%到90%的区间;汽油机略有差异,但绝不会把经济区放到怠速或低负荷角落。如果画出来的经济区位置明显不对,先别急着写报告,回去查输入数据有没有标错单位,或者训练数据分布是不是严重偏科。这类问题靠误差指标往往看不出来,只有眼睛对着等值线图扫一遍才能发现。

5. 训练质量怎么看:误差指标、图形验证和交叉确认

5.1 三个固定查看的量化评估指标

训练完成之后,我习惯固定看三个指标:

  • R²(决定系数):表示回归模型的解释能力。训练集和验证集的R²都接近0.95以上,说明映射关系学得基本到位。
  • RMSE(均方根误差):单位是g/kWh,直接反映预测油耗率和实测油耗率的平均偏离。具体多少算合格取决于你的油耗流量计精度,但一般不要超过10 g/kWh。
  • 残差分布:把每个样本的实测值减预测值,看残差是否在0附近随机分布。如果残差呈现明显的趋势性,比如高速区域全是正残差,说明网络在那片区域没学够,需要补充数据。

算这三个指标,可以自己用net(input_norm)拿到训练集预测值,再和output_norm比;更准确的做法是训练结束取tr.bestT那一时刻的权值状态来预测,因为那才是验证集表现最好的网络状态。

5.2 常见问题快速排查链路

下面这张表是我实际排查问题时固定的顺序,不跳步效果最好:

现象可能原因排查顺序
训练误差不下降输入输出未归一化或单位混乱先查mapminmax和单位
验证R²远低于训练R²过拟合换trainbr、减节点
等值线在边界翻转网格超出训练数据范围限制n/T网格范围
等值线锯齿抖动数据噪声大或节点过多剔异常点、降节点数
经济区位置怪异训练点分布不均补测点并重新训练

很多人一上来就调网络结构,翻来覆去加节点换函数,最后才发现原始数据早就该清洗了,白白浪费时间。先数据、后结构、最后调参,这个顺序我在多个项目里都验证过。

5.3 用重复训练确认网络稳定性

训练数据划分是随机进行的,每次跑出来的结果不完全一样。正式交付前,我一般会重复训练5到10次,统计R²和RMSE的波动范围。如果波动很大,说明当前网络和数据组合对初值太敏感,一种方案是增加训练数据量,另一种是减小网络容量。等结果稳定了,再保存训练效果最好的那个net实例,用它生成正式图。

最后再分享一个我自己的小习惯:每次出图,除了把结果存成常见的图片格式用于报告,还会用savefig保存一份fig源文件,同时把训练好的net、归一化设置参数、原始训练数据一起打包成一个mat文件。原因是这类项目往往隔几个月就要反过头来复现或者改数据重算,只要mat文件还在,加载后不用重新训练就能直接出图,也不怕当时的网络设置细节被忘干净。这个方法在很多次复盘里帮我省下了大量时间。

相关新闻

沐曦与红帽联手MXAIE:国产GPU的AI基础设施生态破局

沐曦与红帽联手MXAIE:国产GPU的AI基础设施生态破局

在当前AI基础软件生态快速收敛的节点上,一个国产GPU厂商和一个老牌企业级Linux发行版厂商坐到一起,搞出来的这个MXAIE方案,确实值得花点时间研究下。我做AI基础设施相关工作很多年,平时打交道最多的就是各种加速卡、驱动、容器运行…

2026/10/20 14:30:54 阅读更多 →
Web自动化测试实战:验证码分级处理与PO三层架构设计

Web自动化测试实战:验证码分级处理与PO三层架构设计

1. 从“验证码绕不过去”到“再也不用碰验证码”的转变先说个我自己项目里的真实数据。某次回归测试,因为验证码模块在凌晨3点更新了一次加密逻辑,早上9点全组的自动化用例直接挂了30%。而处理这个问题花了整整两天:一部分时间在等开发改测试…

2026/10/20 14:29:41 阅读更多 →
栈的三种核心用法:配对消除、单调栈与递归转迭代

栈的三种核心用法:配对消除、单调栈与递归转迭代

刷了不少题之后你会发现一个规律:栈(Stack)这个东西,看起来是最不起眼的基础数据结构,但在实际刷题和面试里的出镜率高得吓人。而且它有个特点——直接考栈操作的题很少,真正难的是"你根本没想到这题能…

2026/10/20 14:29:41 阅读更多 →

最新新闻

数据库慢排查实战:五步定位慢查询与锁等待问题

数据库慢排查实战:五步定位慢查询与锁等待问题

数据库又慢了。后台报警刷屏,接口超时,前端白转圈,老板在群里问“怎么回事”,你打开终端,脑子一片空白——这种情况干过线上运维的人都不陌生。数据库慢是一个现象,不是结论,你不可能听它说“我…

2026/10/20 15:07:44 阅读更多 →
MySQL锁机制全解析:从全局锁到行级锁,掌握锁等待与死锁排查

MySQL锁机制全解析:从全局锁到行级锁,掌握锁等待与死锁排查

凌晨两点,我被一条告警短信叫醒:核心订单库的更新成功率掉到了40%。登录备库一看,show processlist里一大片线程卡在Waiting for table metadata lock,后面还排着一大串正在执行的SELECT。经验告诉我,这一定是某个“顺…

2026/10/20 15:07:44 阅读更多 →
用代码生成PPT:基于Markdown与结构化数据的幻灯片工作流实践

用代码生成PPT:基于Markdown与结构化数据的幻灯片工作流实践

1. 为什么我又造了一个PPT工具轮子先坦白一件事:我做过不下二十套PPT模板,从年终汇报、项目复盘到产品路演,几乎每个季度都要跟幻灯片打交道。市面上现成的工具用了不少,在线协作平台也试过一圈,但每次遇到"要把一…

2026/10/20 15:07:44 阅读更多 →
数据库慢时最先排查的5个关键点(线上经验总结)

数据库慢时最先排查的5个关键点(线上经验总结)

数据库慢的时候,我最先看哪 5 个地方?(线上经验总结)先说个真实场景:某天凌晨 1 点,线上告警突然响起来,接口超时率从 0.5% 飙到 15%,数据库 CPU 使用率直接顶到 99%。群里一片慌乱&…

2026/10/20 15:07:44 阅读更多 →
Eclipse RCP多页编辑器实战:构建可扩展的桌面工作台

Eclipse RCP多页编辑器实战:构建可扩展的桌面工作台

1. 这不是普通编辑器,而是可插拔的“工作台级”编辑界面 你有没有遇到过这样的场景:在开发一个数据处理工具时,用户既要查看原始JSON结构,又要切换到表格视图做字段筛选,还得临时写一段校验脚本——结果只能开三个独立…

2026/10/20 15:07:44 阅读更多 →
继续教育毕业论文如何用AI工具?9大平台实测与避坑指南

继续教育毕业论文如何用AI工具?9大平台实测与避坑指南

毕业论文季一到,私信里问得最集中的问题就是:继续教育学生到底能不能用AI工具写论文?市面上关于AI论文平台的文章,很多就是丢一份名单,告诉你谁谁谁很强,但真正站在在职学习者角度,把平台类型、…

2026/10/20 15:06:42 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* 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 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* 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 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* 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 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在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 阅读更多 →