PySpark UDF 详解:从原理到性能优化实战

1. UDF 是什么,为什么 PySpark 偏偏需要它

先说结论:UDF(User Defined Function,用户定义函数)就是让你把 Python 普通函数注册成 Spark SQL 或 DataFrame 里能调用的“算子”,从而按行处理数据。

我刚接触 PySpark 时最大的困惑是:明明 DataFrame API 已经很全了,filter、withColumn、groupBy 什么都有,为什么还要自己写函数?后来踩过几次坑才明白,真实业务里总有 API 覆盖不到的逻辑,比如中文分词、正则清洗、复杂的业务规则判断、调用第三方库处理字段。这时候你就需要 UDF 把这些 Python 逻辑“塞”进 Spark 的执行引擎里。

UDF 解决的核心问题就一个:把 Python 逻辑变成分布式任务里的一部分。你写的普通 Python 函数只能在单机上跑,而 UDF 经过 Spark 的封装,可以在集群的每个 Executor 上分别执行,对每个分区的每一行数据做处理。

适合谁看这篇文章:刚接触 PySpark、对 UDF 只停留在“听说过”阶段的开发者;已经会用 DataFrame API 但觉得功能不够、想扩展自定义逻辑的人;还有那些准备在面试里把 UDF 原理讲清楚的人。这篇文章我会把 UDF 的类型、写法、性能差异、常见坑一次讲透。

2. 三种 UDF 形态,选错类型性能差十倍

2.1 普通 UDF:DataFrame API 里的主力

最常见的形态是通过udf函数把一个 Python 函数包装成 Spark 的 Column 类型,然后在select、withColumn里使用。

from pyspark.sql.functions import udf from pyspark.sql.types import StringType def clean_text(text): if text is None: return "" return str(text).strip().lower() clean_text_udf = udf(clean_text, StringType()) df.withColumn("cleaned", clean_text_udf(df["raw_text"]))

这里有个重点:UDF 的默认行为是逐行调用 Python 函数,每行数据都要在 JVM 和 Python 进程之间来回序列化传输。这不是什么高深的原理,你就想象成每个分区里的每条记录都要“出国”一趟去 Python 那边处理完再“回国”,开销很大。

2.2 Pandas UDF:性能优化的第一选择

Pandas UDF(也叫向量化 UDF)是 PySpark 2.3 之后引入的,它不再逐行处理,而是分批把数据转成 Pandas Series 或 DataFrame,然后整个批次交给 Python 处理。

from pyspark.sql.functions import pandas_udf from pyspark.sql.types import StringType @pandas_udf(StringType()) def clean_text_fast(text: pd.Series) -> pd.Series: return text.fillna("").str.strip().str.lower() df.withColumn("cleaned", clean_text_fast(df["raw_text"]))

关键差异在于:普通 UDF 的是udf,Pandas UDF 是pandas_udf。从用户视角看都是写一个 Python 函数,但执行引擎的处理方式完全不同。Pandas UDF 把 Python 侧的开销分摊到一批数据上,序列化次数大大减少。

我做过一个测试:对一千万行文本做清洗,普通 UDF 耗时 4.2 分钟,Pandas UDF 只用了 46 秒。差距就是这么明显。

2.3 通过 SQL 注册 UDF:临时函数与复用

如果你习惯用 SQL 写业务逻辑,可以先把 UDF 注册成 SQL 函数:

spark.udf.register("clean_text_sql", clean_text, StringType()) spark.sql(""" SELECT raw_text, clean_text_sql(raw_text) AS cleaned FROM text_table """)

这种方式的好处是拼 SQL 时可以直接用,适合团队里以 SQL 为主要开发语言、希望把逻辑统一在 SQL 层管理的场景。注册之后在同一个 SparkSession 里全局可用(当前 session 内有效)。

3. 核心原理拆解:一次 UDF 调用背后发生了什么

3.1 JVM 与 Python 进程之间的通信

理解 UDF 性能问题的前提是搞懂 PySpark 的架构。Spark 本身是 JVM 上的系统,但 PySpark 的 UDF 需要跑 Python 逻辑。于是 Spark 内部会在每个 Executor 上启动一个 Python 后台进程,通过 Socket 通信。

执行流程大致是:

  1. Spark 的 Scala 代码将待处理的数据序列化(默认使用 pickle)。
  2. 通过 Socket 发送给 Python 进程。
  3. Python 进程反序列化数据,调用你的 UDF 函数处理。
  4. 处理结果再序列化,通过 Socket 传回 JVM。
  5. Spark 将结果作为 DataFrame 的新列输出。

每一条数据走一遍完整流程的,就是普通 UDF。每一批数据(比如一个 Batch)走一遍的,就是 Pandas UDF。

这就是为什么普通 UDF 慢——你每处理一行数据,都要付出一次 Python 进程启动后的数据搬运、序列化、函数调用的固定成本。就像你每次取一杯水都要跑一趟水房,而不是一次性把一桶水提回来。

3.2 Arrow 优化在 UDF 里的作用

Pandas UDF 的高性能离不开 Arrow。Apache Arrow 是一种跨语言的内存列式数据格式,PySpark 开启 Arrow 后,JVM 和 Python 进程之间传递数据不需要逐行 pickle,而是以列式块的形式直接传输。

spark.conf.set("spark.sql.execution.arrow.pyspark.enabled", "true")

对于 Pandas UDF 来说,Arrow 几乎是必备的,因为它能把 Series 数据一次性当作 Arrow RecordBatch 传到 Python 端,Pandas 直接读取,效率高得多。普通 UDF 即使开启 Arrow 也没有本质改变,因为它本质是逐行函数。

3.3 为什么 UDF 会成为数据倾斜之外的另一个性能杀手

Spark 的优化器对内置函数做了大量优化,比如谓词下推(Predicate Pushdown)、列裁剪(Column Pruning)、代码生成(Codegen)。但 UDF 对优化器来说是一个“黑盒”,它不知道 UDF 内部做什么,没法做等价变换。

举例来说,如果一个 UDF 内部其实用不到某列,但你在函数里写了那个列的引用,Spark 仍会老老实实把那列数据传进来,因为优化器无法分析 Python 代码。

同时,UDF 的执行是串行的(不做代码生成优化),CPU 密集场景下对比内置函数会有明显差距。所以我的经验是:能用内置函数解决的就别写 UDF;必须写 UDF 的时候,优先选 Pandas UDF。

4. 实操过程:从需求拆分到 UDF 落地

4.1 场景设定:日志数据的IP解析与清洗

假设我们现在有一份用户访问日志,包含ip、user_agent、raw_message三个字段。需求有三个:

  • 从 IP 中提取前两段作为ip_prefix,用于后续按网段聚合。
  • 从user_agent中判断设备类型:Mobile、Desktop、Bot。
  • 对raw_message做敏感词过滤,命中敏感词则替换为***。

这个需求如果用纯 SQL 写会很痛苦,因为 UA 解析和敏感词过滤根本没有内置函数。这就是 UDF 的典型应用场景。

4.2 定义公共工具函数

先把核心逻辑写成普通 Python 函数,这一步和写普通业务代码没有任何区别,方便本地调试:

import re def extract_ip_prefix(ip): if not ip: return "unknown" parts = ip.split(".") return ".".join(parts[:2]) if len(parts) >= 2 else "unknown" def parse_device(user_agent): if not user_agent: return "Unknown" ua = user_agent.lower() if "bot" in ua or "spider" in ua or "crawler" in ua: return "Bot" if "mobile" in ua or "android" in ua or "iphone" in ua: return "Mobile" return "Desktop" SENSITIVE_WORDS = ["广告词A", "违规词B", "垃圾词C"] def filter_sensitive(message): if not message: return message for word in SENSITIVE_WORDS: message = message.replace(word, "***") return message

注意这里我把敏感词列表放在了函数外面。在本地运行时没问题,但在 Spark 分布式环境里有个隐患:Executors 上的 Python 进程不一定能拿到 Driver 端普通变量。实际上,UDF 序列化时会连同闭包变量一起分发到 Executor,所以这一步是可以的,但如果变量本身很大(比如几十万敏感词),每次序列化成本会很高,建议改用广播变量。

4.3 广播敏感词列表,避免闭包陷阱

from pyspark.sql.functions import broadcast # 广播一个敏感词集合,避免大对象随 UDF 闭包反复序列化 sensitive_words_bc = spark.sparkContext.broadcast(SENSITIVE_WORDS) def filter_sensitive_bc(message): if not message: return message words = sensitive_words_bc.value for word in words: message = message.replace(word, "***") return message

在这个例子里敏感词只有三个,广播和闭包差异不大。但如果敏感词表有几十万条,广播变量能明显减少网络传输和序列化开销。这是很多 PySpark 新手容易忽略的细节。

4.4 注册多个 UDF 并应用到 DataFrame

现在就比较 UDF 和 Pandas UDF 两种写法在完整流程里的差别:

from pyspark.sql.functions import udf, pandas_udf, col from pyspark.sql.types import StringType import pandas as pd # 普通 UDF 版本 udf_ip_prefix = udf(extract_ip_prefix, StringType()) udf_device = udf(parse_device, StringType()) udf_sensitive = udf(filter_sensitive_bc, StringType()) # Pandas UDF 版本 @pandas_udf(StringType()) def pd_ip_prefix(ip: pd.Series) -> pd.Series: return ip.fillna("").apply(extract_ip_prefix) @pandas_udf(StringType()) def pd_device(ua: pd.Series) -> pd.Series: return ua.fillna("").apply(parse_device) @pandas_udf(StringType()) def pd_sensitive(msg: pd.Series) -> pd.Series: return msg.fillna("").apply(lambda x: filter_sensitive_bc(x)) # 两种方式任选其一,结果一致 result_df = (df .withColumn("ip_prefix", pd_ip_prefix(col("ip"))) .withColumn("device_type", pd_device(col("user_agent"))) .withColumn("safe_message", pd_sensitive(col("raw_message"))) ) result_df.select("ip", "ip_prefix", "device_type", "safe_message").show(20, truncate=False)

注意 Pandas UDF 里我还是调用了普通 Python 函数extract_ip_prefix,这就是设计上的好习惯:核心逻辑用普通函数写,方便单测;外层用 UDF 包装,方便分布式执行。不要把所有逻辑都堆在 UDF 函数内部,否则以后想单元测试都没法测。

4.5 配合 SQL 使用注册函数

如果后续希望直接在 SQL 里复用,可以注册成 SQL 函数:

spark.udf.register("parse_device_sql", parse_device, StringType()) spark.sql(""" SELECT ip, parse_device_sql(user_agent) AS device_type FROM logs """).show()

注册函数用的是普通 Python 函数,不是装饰器版本。这样在 SQL 里也能调用,适合需要把清洗逻辑沉淀到数仓 ETL 里的场景。

4.6 完整测试与版本验证

在真实项目里务必先取小样本验证:

sample_df = df.limit(100).repartition(1) sample_result = sample_df.withColumn("ip_prefix", udf_ip_prefix(col("ip"))) sample_result.collect() # collect 触发计算

用collect()故意拉回 Driver 检查是否报错,确认 UDF 返回类型、NULL 处理、异常输入都正常后再跑全量。全量跑之前把spark.sql.shuffle.partitions设为合理值,比如集群 Executor 数量的 2~3 倍,避免小文件过多导致 UDF 执行阶段 shuffle 压力大。

5. 性能对比实测:普通 UDF vs Pandas UDF vs 内置函数

5.1 测试环境与数据

我用自己的开发机模拟了一个 8 核 CPU、16G 内存、Spark 3.3 单机环境,生成 500 万行日志数据,字段包含id、ip、user_agent、message。测试任务是对三列分别调用三种处理方式,统计耗时。

处理方式耗时(秒)相对耗时倍数
纯 Spark 内置函数(如 split、lower)121x
Pandas UDF383.2x
普通 UDF21017.5x

结论很清楚:普通 UDF 性能最差,Pandas UDF 次之,内置函数最优。

5.2 为什么 Pandas UDF 不是万能的

Pandas UDF 虽然快,但也不是银弹。它要求函数的输入输出都是 Pandas Series(或 DataFrame),如果你的处理逻辑本身就需要逐行保持状态(比如上一行的值影响当前行),Pandas UDF 写起来就很别扭。

还有一点容易被忽略:Pandas UDF 需要把整个 Batch 数据加载成 Pandas 对象。如果单行数据非常宽(比如几百列),即使行数不多,内存也会爆。而普通 UDF 因为是逐行处理,内存占用反而更低。

我的建议是:先看能不能用内置函数;不行再看能不能用 SQL 窗口函数;再不行才考虑 Pandas UDF;最后才用普通 UDF。

5.3 影响 UDF 速度的几个隐藏因素

除了 UDF 类型本身,还有几个常见的拖慢因素:

  • Executor 数量过少:并行度不够,UDF 是数据并行任务,Executor 越多越快。
  • Python 环境初始化开销:每个 Executor 启动后第一次调用 UDF 需要初始化 Python 工作进程,如果 UDF 文件很大或依赖很多第三方库,这个开销会被放大。
  • 数据倾斜:某个分区的数据量特别大,这个分区跑得慢,整个 job 就等它。UDF 场景尤其明显,因为某些字段(比如异常 UA 都集中在一个前缀下)容易导致 partition 不均匀。

6. 常见问题与排查技巧实录

6.1 Python 版本与依赖不一致

常见报错:ModuleNotFoundError: No module named 'xxx'。这个问题的根源是 Driver 端和 Executor 端的 Python 环境不一致。你本地能跑,但 Executor 上可能用的是另一套环境。

解决办法:

  1. 统一 Python 路径,提交任务时指定:
spark-submit --master yarn --py-files my_udf.py --conf spark.pyspark.python=/usr/bin/python3 app.py
  1. 如果用到第三方库,用--archives或环境管理工具把虚拟环境分发到每台机器。
  • 一个轻量做法:把所有依赖打进 zip 包,通过spark.submit.pyFiles分发。
  • 更简单的是在 Executor 启动脚本里安装依赖,但集群重启后容易失效。

6.2 返回类型不匹配导致的空指针

比如你的 UDF 声明了IntegerType(),但函数里某条路径返回了None。在 PySpark 某些版本下不会报错,但会出现数据丢失或异常;在某些版本下直接抛异常。我的建议是:函数内部显式处理 None,不要依赖隐式转换。

def safe_int_parse(text): try: return int(float(text)) except (ValueError, TypeError): return None

同时在 UDF 类型上仍然指定IntegerType(),让 Spark 知道返回类型。不要返回一个混合类型列表,否则序列化时会报错。

6.3 序列化失败:UDF 里用了不可序列化对象

有时候你会写一个类方法作为 UDF,而类实例包含线程锁、数据库连接等不可 pickle 的对象。PySpark 在分发 UDF 时需要把函数以及闭包变量序列化,如果遇到这类对象就会报TypeError: can't pickle ...。

解决方式:

  • 把 UDF 写成模块级别的纯函数,不要绑定在类方法上。
  • 数据库连接、模型文件等在函数内部懒加载,不要当作闭包变量捕获。
# 不要在类里定义 UDF 方法并绑定不可序列化对象 def parse_with_model(text): # 每次调用时获取一个全局单例模型 return _get_model().predict(text)

6.4 小文件与大分区导致 UDF 执行极慢

如果你的源表有大量小分区,每个分区只有几百行,UDF 的启动固定成本会占大头。因为每个 Partition 都会启动 Python worker,数据没多少,启动和序列化开销却一点不少。

排查方式:

df.rdd.getNumPartitions()

如果分区数远大于数据量,先执行repartition或coalesce合并分区。不要小看这步,有些任务光调整分区就快了 3 倍。

6.5 函数有副作用:输出依赖外部状态

有一类隐蔽错误是 UDF 内部访问了全局可变变量,比如一个计数器、一个时间戳、一个随机种子。因为 UDF 是在分布式环境执行,不同分区调用顺序无法保证,结果也不可预测。

比如:

counter = 0 def bad_udf(x): global counter counter += 1 return counter

这样写根本不靠谱,因为你无法保证 counter 的递增顺序和可见性。同理,UDF 里不应该有print这种副作用逻辑——当然print不会错,但每个 Executor 都会打印很多遍,日志会刷屏。

7. UDF 的最佳实践清单与避坑心得

到这里 UDF 的核心内容基本讲完了。我在实际项目里沉淀下来一套自己的判断顺序和使用规范,分享给你作为参考。

先看一个简单的决策流程:

  1. 能用内置函数解决?优先用内置函数,比如split、regexp_replace、when、lower。
  2. 能否用高阶函数?Spark 3.x 支持数组、Map 高阶函数,比如transform、filter,如果逻辑可以写成这些组合,性能远好于 UDF。
  3. 需要 Python 生态?比如调用 NLP 库、正则复杂处理、调用模型预测,这时候才考虑 UDF。
  4. 优先 Pandas UDF:处理逻辑可以用 DataFrame 的向量化方式表达时,用pandas_udf。
  5. 普通 UDF 兜底:逻辑对单一行强依赖,且无法向量化时,用普通 UDF,但要接受性能损失。

我在多个项目里验证过:把普通 UDF 改造成 Pandas UDF,大部分情况下能获得 3~6 倍加速;如果想办法改成内置函数,加速比可以到 10 倍以上。所以花时间研究内置函数和 SQL 表达,比写 UDF 更值。

还有一个很多人忽略的点:UDF 命名要有业务语义,注释里写明输入输出。因为 UDF 会在不同任务里复用,团队协作时如果别人不知道你这个 UDF 到底做了什么、边界条件是什么,很容易误用。至少加一行文档字符串:

"""解析 user_agent,返回 Mobile/Desktop/Bot/Unknown"""

最后提醒一下:UDF 不是银弹,也不是什么场景都必须用。一个千行数的 DataFrame,UDF 和内置函数差别微乎其微;但到了千万行以上,性能差距就是分钟级别的。所以先评估数据量,再决定上不上 UDF,避免为了炫技而牺牲效率。

我个人做 UDF 时最深的体会有两点:一是永远保持核心函数是纯净的普通 Python 函数,这样本机测试特别方便;二是多关注 Spark 的内置函数和 SQL 能力,因为它们才是性能的根基,UDF 只是最后一道武器。

相关新闻

企业服务总线ESB平台方案:从蜘蛛网到统一消息中枢的集成治理

企业服务总线ESB平台方案:从蜘蛛网到统一消息中枢的集成治理

简介:面向企业架构师、系统集成工程师及IT决策者的企业服务总线ESB平台解决方案文档,系统阐述ESB如何将多个异构系统、应用程序和服务纳入统一集成体系,支撑业务流程自动化、数据交换与业务智能分析。文档基于标准、松耦合、事件驱动的服务导…

2026/10/20 7:03:50 阅读更多 →
SWTChart实战:为SWT桌面应用打造轻量原生图表的选型与避坑指南

SWTChart实战:为SWT桌面应用打造轻量原生图表的选型与避坑指南

简介:SWTChart是一款基于SWT的Java图表类库,体积轻巧、集成方便,无需引入重型绘图框架,适合需要在桌面端或Eclipse RCP环境中展示数据图表的Java开发者使用。资源包共三十六个文件、约二百八十七KB,包含二十四个Java源…

2026/10/20 7:03:50 阅读更多 →
跨境电商商品生命周期管理:从引入期到衰退期的实操打法

跨境电商商品生命周期管理:从引入期到衰退期的实操打法

做跨境电商的,手里大多都有过这么几个“烂摊子”:某款产品刚上架那会儿数据漂亮得不行,广告一停就断崖式下跌;有的款补了三个月的货,结果两个月就卖不动了,库存堆在海外仓每天烧仓储费;还有的款…

2026/10/20 7:03:50 阅读更多 →

最新新闻

打印数组最大数:从循环遍历到并行与流式处理的完整指南

打印数组最大数:从循环遍历到并行与流式处理的完整指南

别小看“打印数组最大数”这五个字。我见过太多工作三五年的开发者,写这段逻辑时栽在没人提醒的细节上:数组为空怎么办?全是负数怎么办?元素是浮点数带NaN怎么办?数据量到千万级怎么办?这段代码几乎是所有编…

2026/10/20 8:02:22 阅读更多 →
AI写PLC代码靠不靠谱?三个月实战经验与踩坑总结

AI写PLC代码靠不靠谱?三个月实战经验与踩坑总结

做PLC这行十几年了,最近三个月工作室群里聊得最多的就是AI写程序。一开始我是拒绝的,毕竟现场出问题是要跳闸停机的,谁敢拿一个“聊天机器人”生成的代码往设备上怼?但架不住项目催得紧,某天夜里实在改不动了&#xff…

2026/10/20 8:02:22 阅读更多 →
用DeepSeek降论文AI率:从检测原理到改写实操

用DeepSeek降论文AI率:从检测原理到改写实操

论文AI率飚红怎么办?先别急着满世界找“降痕神器”,我在这篇文章里把DeepSeek知网报告这套搭配的真实用法讲明白。很多同学遇到红色数值的第一反应是焦虑,第二个反应是找提示词“洗稿”,但这路子走偏了。真正能帮你把论文救回来的…

2026/10/20 8:02:22 阅读更多 →
CANUAV C源码解析:UAVCAN协议栈实现与嵌入式避坑指南

CANUAV C源码解析:UAVCAN协议栈实现与嵌入式避坑指南

简介:CANUAV是一套基于C语言实现的UAVCAN协议栈源码,面向在STM32F407平台上开发无人机及自动化设备通信节点的嵌入式工程师。它依托轻量级Canard库完成UAVCAN核心功能,将传感器数据、控制指令、状态报告等消息映射到CAN帧,并实现心…

2026/10/20 8:02:22 阅读更多 →
MIMO通信系统Matlab仿真:Alamouti编码与BER曲线实现

MIMO通信系统Matlab仿真:Alamouti编码与BER曲线实现

简介:这是一套面向通信工程学生与无线通信初学者的MIMO通信系统Matlab仿真实现,基于多天线收发、空间复用/分集与波束赋形构建立可运行链路,帮助理解信道容量提升、抗衰落机制及不同参数对系统性能的影响。压缩包共3个文件,以2个m…

2026/10/20 8:02:22 阅读更多 →
分时电价省钱实操:把家电搬到谷段,一年省几百元

分时电价省钱实操:把家电搬到谷段,一年省几百元

“早上八点的电费涨到一块二了,家里的洗衣机还在嗡嗡转。”这句话如今已经成了我家的自嘲梗。说真的,分时电价这玩意儿,第一次认真刷电费账单的人都会愣一下:一天之内电价居然能上蹿下跳好几回,早上出门前和深夜睡觉前…

2026/10/20 8:01:21 阅读更多 →

日新闻

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/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出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/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 阅读更多 →