Calcite优化:AggregateCaseToFilterRule将CASE聚合改写为FILTER提升SQL性能
当你在数据报表里写count(case when ... then 1 end)的时候可能没有意识到这行代码正在把优化器关在门外。Apache Calcite 里有一条专门针对这种场景的规则叫AggregateCaseToFilterRule它做的事情很纯粹把聚合参数里的 CASE 表达式改写成语义等价但更容易被优化的 FILTER 聚合比如count(*) filter (where ...)。这条规则看起来很小但它直接影响着一类高频 SQL 的下推能力和执行效率。这篇文章我会从使用场景、匹配模式、源码实现、最小复现 Demo讲到它和相邻规则的联动以及实际工程里容易踩的坑。无论你是用 Calcite 做 SQL 解析与优化还是打算自己写类似的 RelOptRule这篇应该都能给你一些启发。1. 为什么 CASE WHEN 条件聚合需要一条专门规则1.1 条件聚合在报表 SQL 中的“统治地位”只要是和数据报表打交道的人几乎每天都在写条件聚合。统计转化率要算“有购买行为的用户数”看留存要算“注册后第 7 天仍活跃的用户数”做 AB 实验要在一个分组里同时算“实验组事件数”和“对照组事件数”。最朴素的写法就是count(case when ... then 1 end)或者sum(case when ... then 1 else 0 end)。这种写法本身没有错它在一组 SQL 里同时塞进多个口径读起来也直白。但问题在于它把“哪些行参与聚合”这个信息藏进了一个聚合函数的表达式参数里面。优化器看到的是一个复杂的 RexCall而不是一个独立的谓词。换句话说你写起来很爽优化器却很难从里面提取出能用来做下推、裁剪、并行化决策的东西。我之前接手过一个慢查询排查一条分组统计 SQL 跑了十几秒条件里有分区字段上的时间判断。SQL 大概长这样select user_type, count(case when create_time 2024-01-01 and order_cnt 0 then 1 end) as paid_cnt from t_user group by user_type;从语义上这个create_time 2024-01-01明明可以做分区裁剪但因为它是嵌在 CASE 里的扫描阶段根本认不出来。这就是条件聚合最常见的问题不是算不对而是没法被优化。1.2 问题不在语义而在“可优化性”CASE 版本的问题从来不是算错而是不好算快。执行引擎要在一个聚合调用内部逐行算完 CASE才知道这一行该不该进入累加逻辑。碰上复杂 CASE 或者一层套一层的表达式算力都浪费在无意义的表达式计算上。FILTER 语法是另一回事。count(*) filter (where create_time 2024-01-01 and order_cnt 0)把谓词显式地挂在聚合外面优化器一眼就能看到条件是什么。它可以把这个谓词下推到扫描阶段可以结合分区元数据做裁剪也可以在代码生成阶段生成“先判断、再累加”的清晰循环逻辑。语义上两者等价但 FILTER 是“给优化器看的形态”。更关键的是FILTER 是标准 SQL 能力不少主流引擎都支持。优化器内部有了这个显式形态后续规则才有机会插手。CASE 版本为什么会需要一条专门规则就是因为靠用户手写 FILTER 不现实靠通用表达式化简规则也做不到语义级别的转写必须有一条针对“聚合上下文中的 CASE”做定向改写的规则。1.3 反直觉的认知差很多开发者并不知道 FILTER 语法的存在默认条件聚合只能写 CASE还有一部分人知道 FILTER但以为“SQL 怎么写都一样优化器总会给我调优”。这个认知差在 Calcite 里尤其危险。Calcite 的优化能力完全取决于它注册了哪些规则。规则没覆盖到的情况它再“智能”也不会替你联想。AggregateCaseToFilterRule就是 Calcite 选择补上的一块拼图它主动把常见的 CASE 型条件聚合翻译成 FILTER 型让后续的规则和引擎能够接手。理解这条规则其实也是在理解“优化器能做哪些事不能做哪些事”的分界线。2. 规则到底改写了什么匹配模式与语义等价性2.1 它能匹配的 CASE 形态AggregateCaseToFilterRule不是所有带 CASE 的聚合都处理它只处理特化过的模式。从我读源码和实际测试的结果看核心约束是这样的CASE 只有一个 WHEN 分支没有多个条件THEN 分支是常量 1或是某个输入列如果有 ELSEELSE 必须是 0 或者 NULL。聚合函数本身聚焦在 COUNT 和 SUM 上。常见的转换关系我整理成了一张表原始写法改写后count(case when c then 1 end)count(*) filter (where c)count(case when c then 1 else 0 end)count(*) filter (where c)sum(case when c then 1 else 0 end)count(*) filter (where c)或sum(1) filter (where c)sum(case when c then x else 0 end)sum(x) filter (where c)sum(case when c then x end)sum(x) filter (where c)前两种场景做完改写后聚合参数直接清空变成count(*)FILTER 条件就是原来的 WHEN 条件。第三种稍微特殊一点因为sum(case when c then 1 else 0 end)本身就是计数转成count(*)是最简洁的形态即使规则保持sum(1)在语义上也没有问题。第四种则保留了原聚合函数只是把参数从整个 CASE 换成了 THEN 分支里的表达式 x同时把条件挂到 FILTER 上。2.2 为什么这些改写是等价的三值逻辑说清等价性必须回到 SQL 的三值逻辑。CASE 在没有匹配分支且没有 ELSE 时返回的是 NULL不是 0。而COUNT会忽略 NULLSUM也会忽略 NULL。这就是一切等价的根基。以count(case when c then 1 end)为例当 c 为 TRUECASE 结果是 1参与计数当 c 为 FALSE 或 NULLCASE 结果是 NULL被 COUNT 忽略。折算下来只有 c 为 TRUE 的行贡献了一次计数。count(*) filter (where c)的行为是先对每行判断 c只有 c 为 TRUE 的行才能进入 count(*) 的统计范围。注意如果 c 是 NULLFILTER 条件不满足这一行同样被排除。两者口径完全一致。sum(case when c then x else 0 end)同理。c 为 TRUE 时累加 xc 为 FALSE 或 NULL 时累加 0。0 对 SUM 没有影响所以等价于先筛出满足 c 的行再做sum(x)也就是sum(x) filter (where c)。这里有个容易想岔的点如果 x 本身是 NULLCASE 版本在 c 为 TRUE 时会返回 NULLSUM 忽略它FILTER 版本是先通过 c 的过滤然后 sum(x) 遇到 NULL 也忽略它行为仍然一致。我常用一组小数据验证这个等价性。比如三行数据c 的值分别是 TRUE、FALSE、NULLx 分别是 5、6、7sum(case when c then x else 0 end)的结果是 5 0 0 5。sum(x) filter (where c)先过滤掉 FALSE 和 NULL 两行只剩 x5结果也是 5。2.3 不满足条件的 CASE 会被拒之门外规则最保守的地方在于不会碰它不确定能等价的情况。举几个我在实际使用中遇到过的例子多 WHEN 的 CASE比如case when c1 then 1 when c2 then 1 end。语义上它等价于count(*) filter (where c1 or c2)但规则没有做这种推导。因为从一个简单的模式匹配突然跳到需要构造 OR 条件规则复杂度会上升而且多 WHEN 的语义判断更容易出错。宁可少优化不能算错。ELSE 分支非 0 非 NULL 的更危险。count(case when c then 1 else 2 end)实际上统计的是所有行不管 c 对不对每行都会贡献一个非 NULL 的值。如果规则简单粗暴地把 ELSE 丢掉再转成count(*) filter (where c)结果直接少算一批。规则一旦出现这种错误改写优化器就变成 bug 制造器了。非简单表达式同样不碰。THEN 分支如果是函数调用、子查询、易变表达式规则不会动。因为这些表达式的求值时机在改写前后可能发生变化稍有不慎就引入语义偏差。Calcite 选择只处理常量 1 和输入列本质上是在用“最保守的匹配”换取“等价性的绝对安全”。3. 源码视角从匹配到 FILTER 的一整套动作3.1 先定位规则AggregateCaseToFilterRule在org.apache.calcite.rel.rules包下面。使用的时候一般不用手动 newCalcite 的RelOptRules类里已经暴露了对应的规则常量按名字找就能看到。它是典型的RelOptRule子类operand 匹配的是LogicalAggregate也就是说它作用于逻辑计划阶段。有一点需要注意Calcite 的规则体系和版本绑定得很紧。不同大版本里规则注册方式从直接继承RelOptRule逐步迁移到RelOptRule.Config模式常量字段的位置也会变。如果你在本地源码里找不到某个字段先确认项目依赖的 Calcite 版本再按版本去查别直接照抄老帖。3.2 匹配与变换的骨架逻辑这条规则的核心流程可以概括成几步。我用伪代码来描述它和真实源码的结构基本一致for aggCall in aggregate.aggCalls: for operand in aggCall.operands: if operand is CASE(cond, thenExpr, elseExpr) and isSupportedShape(operand): newCall aggCall.toBuilder() .setFilter(cond) .setOperand(thenExpr) // 甚至清空操作数 .build() replace aggCall with newCall第一步是遍历聚合调用的参数找到 CASE 类型的 RexCall。第二步是检查这个 CASE 的形态包括 WHEN 个数、THEN 值、ELSE 值。第三步是整个改写的核心构造一个新的 AggregateCall。构造新 AggCall 时有几个细节很值得学习。一是聚合函数本身通常不变变的是操作数和 filter。二是操作数的替换规则根据函数类型不同有差异COUNT 场景可以把操作数直接换成空参数SUM 场景要把 CASE 整体替换成 THEN 表达式。三是 filter 字段接受的是 RexNode也就是原来的 WHEN 条件Calcite 内部会把它挂到 AggregateCall 的 filter 属性上。整个变换不需要修改 Aggregate 的输入节点只是把一个 AggCall 描述换成另一个语义等价的描述。这也是它风险低的原因之一它不改变输入行数不改变分组键只改变“怎么聚合”这件事。3.3 源码阅读时要注意的版本差异我最早接触这条规则时Calcite 的版本还比较老AggregateCall里的 filter 是用filterArg表示的也就是一个整数索引指向输入表达式列表中的某个位置。后来版本把 filter 改成了直接持有 RexNode语义更清晰但如果你打开新版源码又对着老文章里面的字段名去找就会觉得很迷惑。还有一点是RexBuilder和RelOptUtil里一些工具方法的迁移。比如构造 FILTER 条件、处理化简等辅助逻辑在不同版本里可能从内部类挪到了辅助类。我的建议是把源码下载到本地直接跳到AggregateCaseToFilterRule的onMatch方法里读几十行代码很容易看完整个逻辑。3.4 这个规则是学习写 OptimizeRule 的好样本如果你想写自己的 RelOptRule这条规则可以作为入门教材。它比那些动辄涉及成本计算和计划枚举的规则简单太多又能完整展示“匹配-检查-构造新节点-替换”的标准流程。我推荐做三件事第一把它的 operand 定义抄一遍理解RelOptRule怎么声明匹配目标第二读它的 transform 方法看它如何用aggCall.toBuilder()构造新调用第三自己扩展一个小场景比如处理 AVG 的 CASE 聚合写一个新的规则用 HepPlanner 做等价性验证。这套练习做完你对 Calcite 规则机制的理解会上一个台阶。4. 跑一个最小 Demo观察改写前后4.1 环境准备看代码不如跑代码。我们用一个最简环境来观察这条规则的效果。项目只需要引入calcite-core我用的是 1.35 版本具体 API 如果和你用的版本不一致定位到对应源码就行。dependency groupIdorg.apache.calcite/groupId artifactIdcalcite-core/artifactId version1.35.0/version /dependency内存里放一张 EMP 表字段就三个DEPTNO、AGE、SAL。目标 SQL 是select deptno, count(case when age 30 then 1 end) as cnt from emp group by deptno;4.2 用 HepPlanner 定向验证规则VolcanoPlanner 里规则是否触发和成本模型有关不适合做单规则验证。我习惯用 HepProgramBuilder只往里面加一条AggregateCaseToFilterRule这样能干净地看到它做了什么。核心代码大致是RelBuilder builder RelBuilder.create(RelBuilder.Config.defaultConfig()); RelNode input builder .scan(EMP) .aggregate( builder.groupKey(DEPTNO), builder.count(false, C, builder.call(SqlStdOperatorTable.CASE, builder.call(SqlStdOperatorTable.GREATER_THAN, builder.field(AGE), builder.literal(30)), builder.literal(1), builder.literal(null)))) .build(); HepProgram program new HepProgramBuilder() .addRuleInstance(RelOptRules.AGGREGATE_CASE_TO_FILTER) .build(); HepPlanner planner new HepPlanner(program); planner.setRoot(input); RelNode optimized planner.findBestExp(); System.out.println(RelOptUtil.toString(optimized));这段代码是示意性的实际跑起来还要准备 schema 信息RelBuilder 的某些 API 在不同版本里 signatures 也不同。但我建议你自己把环境搭起来因为打印出来的 RelNode 树比任何文字描述都直观。4.3 观察改写前后改写前的 AggCall 大概长这样参数是一个 CASE 表达式LogicalAggregate(groupSet[{0}], cnt[COUNT(CASE((AGE, 30), 1, null))])经过AggregateCaseToFilterRule之后计划会变成LogicalAggregate(groupSet[{0}], cnt[COUNT(*) FILTER ((AGE, 30))])注意看变化聚合函数的参数从 CASE 表达式变成了空参数也就是COUNT(*)后面多了一个 FILTER 条件内容是原来的(AGE, 30)。这一步就是我前面反复说的把“藏在参数里的谓词”提取成了显式 FILTER。4.4 版本不同输出格式会有差异我在自己环境里验证过上述改写但不同 Calcite 版本的 RelNode toString 格式差别不小。比如老版本可能打印成COUNT(CASE...)说明规则没触发新版本打印成COUNT(*) FILTER(...)说明触发成功。看到计划串里出现FILTER关键词基本可以确认改写生效。如果你的输出里 CASE 还在优先检查规则是否真的被 HepProgram 注册进去了这是最常见的翻车点。5. 与相邻规则的联动它能给后续优化打开什么门5.1 从 CASE 到 FILTER最大的红利是谓词下推AggregateCaseToFilterRule本身不带来立竿见影的性能提升它设置的是后续优化的“钩子”。最有价值的下游规则是AggregateFilterTransposeRule它专门处理聚合上的 FILTER把 FILTER 条件往输入方向推。举一个 join 场景订单表和用户表 join 之后按部门分组统计已支付用户数。如果 SQL 写成count(case when o.status PAID then 1 end)这个o.status PAID埋在 CASE 里优化器拿不到它。经过AggregateCaseToFilterRule转成count(*) filter (where o.status PAID)之后AggregateFilterTransposeRule就可以尝试把o.status PAID下推到 join 的右表先过滤掉未支付订单再参与 join。后者的执行成本可能差一个数量级。分区表场景更明显。FILTER 条件如果落在分区键上下推到扫描层之后就能直接做分区裁剪。而 CASE 版本连条件都提取不出来更别说裁剪了。5.2 FILTER 在执行引擎手里的优势从执行引擎的角度看FILTER 聚合也比 CASE 聚合友好。代码生成时引擎可以直接生成一个“谓词判定 累加器更新”的紧凑循环每行数据先过一遍 filter过了才进入累加逻辑逻辑链路非常短。CASE 版本则需要把表达式值算出来聚合函数内部再判断它是不是 NULL可能需要额外的一层中间值。在分布式执行框架里FILTER 还有一个附带好处部分聚合阶段可以先过滤掉不满足条件的行减少 shuffle 数据量。这些收益虽然不是这条规则直接产生的但没有规则在前面做改写后续阶段什么都拿不到。5.3 规则顺序与规划器配置规则之间的顺序很重要。用 HepPlanner 时要把AggregateCaseToFilterRule放在AggregateFilterTransposeRule前面否则后者没有机会消费 FILTER。用 VolcanoPlanner 时规则执行顺序由优化器控制但你仍然要想清楚整个规则集是否会因为这一条改写而引入更多计划分支。我自己在实际项目中不会把全量规则无脑打开跑 VolcanoPlanner计划空间膨胀带来的优化时间成本不可忽视。更可控的做法是分层优化逻辑阶段用定向的 HepProgram 做好几类必要的改写比如把 CASE 转 FILTER再交给成本优化阶段去枚举物理计划。6. 踩坑与边界这些情况它不会处理也不该处理6.1 最常见的“手动改写翻车”很多人知道 FILTER 等价于 CASE 之后会手痒自己改 SQL。我见过最典型的错误是把sum(case when c then 1 else 2 end)改成sum(1) filter (where c)。这两个完全不等价因为 ELSE 分支是 2每一行都会往 SUM 里贡献值正确的语义应该是不管 c 是什么所有行都累加。改成 filter 之后只有 c 为 TRUE 的行参与累加结果直接少一大截。还有一类错误是丢掉 COUNT 的 FILTER 条件。count(case when c then 1 end)和count(1)之间只差了一个 filter 吗不前者没有 filter 条件时本质上统计全表而 CASE 版本只统计满足 c 的行。很多人改写时记住了 FILTER 语法忘了带条件结果自然错得离谱。6.2 NULL 聚合的隐蔽差异COUNT 忽略 NULL 这个特性在等价转换里是基石但也容易造成隐蔽的错误。比如count(case when c then null end)无论 c 是什么CASE 结果都是 NULLCOUNT 永远统计不到任何东西结果恒为 0。如果规则盲目把它转成count(*) filter (where c)那统计的就不是 0 了。SUM 场景里还有一个细节要留意sum(case when c then x else 0 end)转成sum(x) filter (where c)要求 x 是普通列或常量不能存在需要特殊处理的语义。如果 x 来自一个 volatile 函数改写后求值次数可能发生变化这种规则不该碰。Calcite 的规则保持简单匹配宁可不转不转错。6.3 表达式代价与确定性我在模拟项目里有一次是想把sum(case when expensive_function() 0 then 1 else 0 end)转成 filter攒了个很大的 CASE 表达式信心满满地以为规则会处理。结果规则直接跳过了。原因不复杂THEN 分支不是简单常量或输入列规则不敢保证改写后语义完全一致。这个保守策略对生产环境是友好的。表达式求值的时机变化尤其遇到非确定性函数可能让 SQL 从“每次返回随机结果”变成“某行被过滤前先算了函数”行为差异说不清。如果你自己写类似的规则强烈建议在模式匹配阶段就加上确定性和副作用检查只处理简单、纯函数、可推断语义的表达式。6.4 多条件统计的替代写法如果你的统计口径是多条件 OR比如“c1 或 c2 满足其一就算一次”规则不会帮你合并成filter (where c1 or c2)。我的建议是直接在 SQL 里写两个 COUNT最后相加select count(case when c1 then 1 end) count(case when c2 then 1 end) as cnt from t;或者直接写 FILTER 语法让优化器少做一层猜测select count(*) filter (where c1 or c2) as cnt from t;日常开发里我更推荐后面这种写法。既然目标是让优化器更容易理解你的意图为什么不直接给它最容易理解的形式呢规则是用来兜底的不是用来给不规范 SQL 擦屁股的。最后说点个人体会。刚接触 Calcite 的时候我总觉得这种“小规则”不值一提真正难的是成本模型和 join 枚举。但在一个模拟项目里排查慢查询时我发现一组报表 SQL 慢的根因就是条件聚合全部写成 CASE导致分区裁剪和谓词下推全都失效。把AggregateCaseToFilterRule加到逻辑优化阶段之后后续规则终于“接住了”那些谓词整体计划立刻不一样了。从那以后我读源码的顺序也改了从最小的规则开始读反而是理解整套优化器最快的方式。如果你也想写自定义规则记住一点规则不是越聪明越好而是越安全越好正例和反例的测试用例一个都不能少。

相关新闻

基于YOLOv8/YOLOv5的田间杂草检测系统:从数据集构建到UI部署完整复盘

基于YOLOv8/YOLOv5的田间杂草检测系统:从数据集构建到UI部署完整复盘

田间杂草检测这种项目,很多人第一反应是找个开源模型跑一下,能出框就算完事。但真正做过一次田间实测的人都知道,训练集里mAP刷到0.95,拉到地里一测,漏检的、误检的能让你怀疑人生。这个基于YOLOv8/YOLOv7/YOLOv6/YOLO…

2026/10/19 13:55:57 阅读更多 →
Open Code Review:把代码评审从形式主义变成团队成长引擎

Open Code Review:把代码评审从形式主义变成团队成长引擎

1. 被误解的Code Review:为什么你们的评审总在走形式1.1 "批作业式"评审是怎么诞生的做过几年研发的人,大概率都见过这种场景:一个PR挂着三四天没人点开,临到要上线了,群里喊一嗓子,才有两三个人…

2026/10/19 13:55:57 阅读更多 →
从零搭建智能体:任务拆解、工具调用与流程编排的实践复盘

从零搭建智能体:任务拆解、工具调用与流程编排的实践复盘

第一次看到那行日志时,我确实忍不住喊了一声“哇塞”。不是因为智能体写出了多惊艳的文字,而是它干了一件过去普通AI助手绝不会主动做的事:把自己要完成的任务拆成了七个子任务,调用了两个外部工具,发现第一次生成结果…

2026/10/19 13:55:57 阅读更多 →

最新新闻

多智能体协作模式深度解析:主从、对等与混合架构选型指南

多智能体协作模式深度解析:主从、对等与混合架构选型指南

多智能体协作这个题目,最近在项目里几乎是绕不开的。不管是机器人编队、物流调度,还是现在热门的多个AI智能体共同跑一个业务流程,本质上都要回答同一个问题:让一堆各自有感知、有决策的个体一起干活,到底怎么组织它们…

2026/10/19 14:39:53 阅读更多 →
Flume+HBase+Spark2.2构建秒级新闻热点实时分析链路

Flume+HBase+Spark2.2构建秒级新闻热点实时分析链路

简介:这是一份面向高校计算机专业本科生及大数据初学者的毕业设计级实战源码,聚焦基于Spark 2.2构建新闻网实时分析系统的完整实现,解决新闻用户行为日志的采集、存储、流式处理与热点挖掘等典型大数据场景问题。资源共43个文件,含…

2026/10/19 14:39:53 阅读更多 →
pymoo实战:车辆与无人机协同配送集中式模型复现

pymoo实战:车辆与无人机协同配送集中式模型复现

最近总有人拿着低空经济方向的新论文来问:车辆与无人机协同配送的优化模型,到底怎么用代码实现?尤其论文里常见的"集中式协同配送模式",听起来就是车辆带着无人机送货,真到复现的时候却要同时处理车辆路径、…

2026/10/19 14:39:53 阅读更多 →
OpenCode 美化增强指南:用 AI 一键装好 oh-my-opencode

OpenCode 美化增强指南:用 AI 一键装好 oh-my-opencode

最近很多朋友开始折腾 OpenCode,原因很直白:它能直接用 AI 帮你写代码、改配置、跑命令,确实比传统编辑器省事。但省事归省事,刚装完的 OpenCode 默认界面和默认快捷键都比较朴素,用起来总有那么点“不够顺手”。oh-my…

2026/10/19 14:39:53 阅读更多 →
VS Code数据科学提效插件DST:AST解析+内核探针+安全沙箱

VS Code数据科学提效插件DST:AST解析+内核探针+安全沙箱

1. 这不是又一个“语法高亮插件”:它重新定义了VS Code里的数据分析工作流你有没有过这样的时刻:在VS Code里打开一个Jupyter Notebook,写完一段pandas数据清洗代码,想立刻看df.head()结果,却得切到右侧的交互窗口——…

2026/10/19 14:39:53 阅读更多 →
离线环境二进制部署Kubernetes集群完整指南

离线环境二进制部署Kubernetes集群完整指南

做私有化交付这几年,“离线部署K8S集群,而且还得用二进制方式”基本上是隔离网络项目里躲不掉的一关。所谓离线,就是目标机房跟互联网物理隔离,或者网络策略收得很紧,你没法在目标环境直接拉依赖;所谓二进制…

2026/10/19 14:38:52 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →