Spark累加器原理与实战:解决分布式计数难题
1. 从一次“算不准”的计数说起为什么需要累加器如果你用过Spark写过一些数据处理任务特别是涉及到一些全局统计比如“统计整个数据集中有多少条异常记录”、“计算所有任务中某个特定事件发生的总次数”你很可能踩过这样一个坑你在Driver端定义了一个变量然后在各个Executor的Task里尝试去修改它最后发现这个变量的值压根没变或者变得莫名其妙。我最早遇到这个场景是在处理一个日志清洗任务时。需求很简单需要过滤掉字段缺失的脏数据同时统计一下被过滤掉的数据总量方便后续做数据质量报告。我当时的第一反应和很多刚接触分布式计算的朋友一样在Driver端写了个var badRecordCount 0L然后在map或者filter算子里面遇到一条脏数据就执行badRecordCount 1。代码逻辑看起来天衣无缝跑起来也没报错但最后打印出来的badRecordCount永远是0。那一刻的困惑相信很多人都记忆犹新。这个问题的根源在于Spark的执行模型。Spark的算子如map、filter内部执行的函数会被序列化后分发到各个Executor节点上执行。Driver端定义的变量badRecordCount会被拷贝一份到每个Task中。Task修改的是自己本地副本的值这个修改完全不会被传回Driver端。当所有Task执行完毕Driver端的原始变量badRecordCount自然还是初始值0。这就是典型的闭包变量更新问题。那么在分布式环境下我们如何才能实现一个所有Task都能安全地、正确地对其进行“累加”操作并且最终结果能汇聚到Driver端的变量呢Spark给出的答案就是累加器Accumulator。它不是一种编程技巧而是Spark框架提供的一种共享变量Shared Variable专门用于在分布式计算中实现安全的、只能“加”的聚合操作。累加器是理解Spark编程模型尤其是理解Driver与Executor交互、任务闭包序列化等核心概念的一个绝佳切入点。2. 累加器的本质一个“只能增”的分布式共享变量要正确使用累加器首先要抛开对普通变量的认知从分布式系统的角度理解它。2.1 核心工作原理两阶段更新与最终合并累加器的工作机制可以概括为“本地累加最终汇总”。它不是一个存在于某个固定位置的单一变量而是一个由Driver端和众多Executor端共同维护的分布式状态。在Driver端当你通过SparkContext创建一个累加器例如val acc sc.longAccumulator(“myAcc”)时Spark会在Driver端注册这个累加器并为其生成一个唯一的ID。此时Driver端持有的是累加器的“初始值”和“元数据”名称、ID、类型。在Executor端Task内部当包含累加器操作的Task被分发到Executor上执行时Executor会从Driver端获取该累加器的元信息。每个Task在执行过程中都会在自己本地维护一个该累加器的副本。Task对累加器的所有“加”操作add都只作用于这个本地副本。这样做的好处是避免了跨节点的网络通信开销和锁竞争极大提升了并行效率。关键的两阶段更新本地更新Local Update每个Task独立地、无需同步地更新自己的本地累加器副本。全局合并Global Merge当该Task成功执行完毕后Executor会将这个Task的本地累加器副本的最终值即这个Task贡献的增量发送回Driver端。Driver端在收到所有Task的增量报告后将这些增量累加到Driver端维护的最终值上。注意这里有一个非常重要的细节。Executor发送的不是每次add操作的流水账而是Task结束后本地副本的“总值”。这意味着如果一个Task内对同一个累加器调用了100次add(1)Executor只会向Driver发送一个“100”的消息而不是100条“1”的消息。这减少了网络传输开销。2.2 累加器的“只写”特性与容错性累加器被设计为“只写”变量这里的“写”特指“累加”。Task只能向累加器“添加”值而不能读取它的当前值在Task内部读取到的只是本地副本的瞬时值并非全局值。这个设计是深思熟虑的保证结果确定性如果Task可以读取累加器的全局当前值那么任务的输出结果将依赖于不确定的执行顺序。例如两个Task都根据累加器的值做分支判断由于Task执行顺序的随机性最终结果可能每次运行都不一样。这违背了数据处理需要确定性的原则。简化容错逻辑Spark的容错基于RDD的血统Lineage和重新计算。如果一个Stage的某个Task执行失败Spark会启动一个新的Task来重新计算。如果累加器是可读的那么重新计算的Task读到的值可能与失败前Task读到的值不同因为其他成功的Task可能已经更新了累加器导致结果不一致。将累加器设计为只写重新计算的Task只需重新执行一遍累加操作最终Driver端合并时结果依然是正确的保证了幂等性。因此记住一个黄金法则永远不要在RDD的转换Transformation或行动Action算子内部去读取累加器的值并用于业务逻辑判断。累加器的唯一正确用法就是在所有任务结束后在Driver端通过.value属性来获取最终聚合结果。2.3 累加器 vs 广播变量共享变量的两种面孔Spark提供了两种主要的共享变量累加器和广播变量。理解它们的区别能帮你更好地选择工具。特性累加器 (Accumulator)广播变量 (Broadcast Variable)核心目的聚合将各个Executor上的信息聚合到Driver。分发将Driver端的一个只读大变量高效分发给所有Executor。操作权限Executor端只能“加”(Write-Only)。Executor端只能“读”(Read-Only)。数据流向Executor - Driver (汇聚)。Driver - Executor (广播)。典型场景计数、求和、统计异常。分发只读的查询表、机器学习模型参数、配置字典。更新时机在Task执行过程中多次更新Task结束后上报增量。在任务开始前一次性发送到每个Executor节点并缓存。容错支持重新计算的Task会重新执行累加操作。支持节点失效后可以从其他节点或Driver重新拉取。简单来说当你需要“从四面八方向中心汇总数据”时用累加器当你需要“从中心向四面八方分发数据”时用广播变量。3. 累加器的实战从创建、使用到自定义理论说再多不如一行代码。我们来看看在Spark中如何具体使用累加器。3.1 内置累加器的创建与使用Spark为基本数据类型提供了开箱即用的累加器通过SparkContext创建。import org.apache.spark.{SparkConf, SparkContext} object BasicAccumulatorDemo { def main(args: Array[String]): Unit { val conf new SparkConf().setAppName(“BasicAccumulator”).setMaster(“local[*]”) val sc new SparkContext(conf) // 1. 创建Long类型的累加器 val counter sc.longAccumulator(“MyCounter”) // 2. 创建一个RDD val dataRDD sc.parallelize(1 to 100) // 3. 在转换算子中使用累加器注意转换是惰性的 val processedRDD dataRDD.map { num if (num % 2 0) { counter.add(1) // 遇到偶数计数器加1 } num * 2 // 正常的map操作 } // 重要累加器的更新发生在行动Action算子触发任务执行时 println(s“触发行动操作前的计数器值: ${counter.value}”) // 输出: 0 // 触发一个行动操作例如 collect()这会真正执行map中的累加逻辑 processedRDD.collect() // 行动操作结束后获取累加器的最终值 println(s“触发行动操作后的计数器值: ${counter.value}”) // 输出: 50 (1到100中有50个偶数) sc.stop() } }关键点解析惰性求值累加器的add操作写在map这类转换算子中。但Spark的转换是惰性的只有当一个行动算子如collect,count,saveAsTextFile被调用时包含add操作的Task才会被真正调度执行累加器才会被更新。上面代码中第一个println输出0就是因为此时还没有任何行动算子触发作业执行。行动算子的选择并非所有行动算子都会触发累加器更新。像count()、first()这类可能会被Spark优化例如从源头元数据直接获取的行动算子可能不会执行所有转换逻辑从而导致累加器更新不完整。最稳妥的方式是使用collect()或foreach()这类需要计算所有数据的行动算子来确保累加逻辑被执行。但在生产环境中对于大规模数据collect()可能导致Driver OOM需谨慎。命名给累加器起一个有意义的名字如“MyCounter”在Spark UI上查看作业详情时你可以通过这个名字定位到对应的累加器方便监控和调试。除了longAccumulatorSpark还提供了doubleAccumulator和collectionAccumulator。// 双精度累加器用于求和 val sumAcc sc.doubleAccumulator(“TotalSum”) dataRDD.foreach(x sumAcc.add(x)) // 集合累加器用于收集信息注意所有信息会汇集到Driver可能内存爆炸 val infoAcc sc.collectionAccumulator[String](“ErrorMessages”) dataRDD.foreach { x if (x 0) infoAcc.add(s“发现负数: $x”) } // 使用 infoAcc.value 获取一个 java.util.List[String]3.2 自定义累加器应对复杂聚合逻辑当内置的累加器类型无法满足需求时例如你想实现一个求平均值的累加器或者一个维护复杂数据结构如Top-N列表的累加器你就需要自定义。自定义累加器需要继承AccumulatorV2[IN, OUT]抽象类其中IN是输入类型即add方法的参数类型OUT是输出类型即value方法的返回类型。下面我们实现一个自定义的AverageAccumulator用于累加(总和, 个数)最终计算平均值。import org.apache.spark.util.AccumulatorV2 import scala.collection.mutable class AverageAccumulator extends AccumulatorV2[(Long, Double), (Long, Double, Option[Double])] { // 内部状态总个数 (count)总和 (sum) private var count: Long 0L private var sum: Double 0.0 // 判断累加器是否为空初始状态 override def isZero: Boolean count 0L sum 0.0 // 创建一个本累加器的新副本用于在Executor端复制 override def copy(): AverageAccumulator { val newAcc new AverageAccumulator newAcc.count this.count newAcc.sum this.sum newAcc } // 重置累加器状态 override def reset(): Unit { count 0L sum 0.0 } // 添加一个新值。这里IN类型是 (Long, Double)代表 (单个值, 权重)。 // 例如我们可以 add((1L, 10.5)) 表示增加一个值为10.5权重为1的数据点。 // 为了简单我们假设权重总是1只使用第二个元素作为值。 override def add(v: (Long, Double)): Unit { count v._1 // 增加个数 sum v._2 // 增加总和 } // 合并另一个同类型累加器由Driver在收到Executor报告时调用 override def merge(other: AccumulatorV2[(Long, Double), (Long, Double, Option[Double])]): Unit { other match { case o: AverageAccumulator this.count o.count this.sum o.sum case _ throw new UnsupportedOperationException(s“Cannot merge ${this.getClass.getName} with ${other.getClass.getName}”) } } // 获取累加器的当前值。OUT类型是 (总个数总和平均值Option) override def value: (Long, Double, Option[Double]) { val avg if (count 0) Some(sum / count) else None (count, sum, avg) } }使用自定义累加器object CustomAccDemo { def main(args: Array[String]): Unit { val conf new SparkConf().setAppName(“CustomAcc”).setMaster(“local[*]”) val sc new SparkContext(conf) // 注册自定义累加器 val avgAcc new AverageAccumulator sc.register(avgAcc, “AverageCalculator”) val dataRDD sc.parallelize(Seq(10.0, 20.0, 30.0, 40.0, 50.0)) dataRDD.foreach { num avgAcc.add((1L, num)) // 每个数字权重为1 } val (totalCount, totalSum, avgOption) avgAcc.value println(s“数据总数: $totalCount”) println(s“数据总和: $totalSum”) println(s“平均值: ${avgOption.getOrElse(0.0)}”) // 输出: 30.0 sc.stop() } }自定义累加器的核心要点序列化AccumulatorV2及其所有成员必须是可序列化的因为实例会被发送到Executor。isZero、copy、reset这些方法用于Spark框架内部管理累加器的生命周期和副本。add与merge这是业务逻辑的核心。add定义如何添加单个值merge定义如何合并两个部分聚合结果。merge必须是幂等且可交换的因为合并顺序可能不确定。注册创建自定义累加器实例后必须通过sc.register()在SparkContext中注册否则Spark无法识别和管理它。4. 累加器的“坑”与最佳实践累加器用起来简单但想用对、用好避免踩坑需要理解其内在机制和约束。4.1 累加器更新的时机与行动算子这是最常见的坑。如前所述累加器更新发生在行动算子触发Job执行时。但某些行动算子可能不会触发你期望的所有计算。坑点示例val acc sc.longAccumulator(“test”) val rdd sc.parallelize(1 to 10).map { x acc.add(1); x } rdd.count() // 第一次行动算子acc可能被更新 println(acc.value) // 假设输出 10 println(acc.value) // 再次读取可能还是10没问题 // 但是如果缓存了RDD val cachedRdd rdd.cache() cachedRdd.count() // 从缓存读取可能不会重新执行mapacc不会更新 println(acc.value) // 输出可能仍然是10而不是20最佳实践如果业务逻辑强依赖累加器的精确值应避免在可能从缓存读取的行动算子后依赖其更新。更稳妥的做法是在触发累加器更新的行动算子之后立即获取其值并且理解Spark的缓存和血统优化可能对累加器更新产生影响。对于关键统计考虑将累加逻辑放在foreach这类必定执行的行动算子中。4.2 在转换算子中多次触发行动算子这是一个灾难性的错误模式。val acc sc.longAccumulator(“dangerous”) val rdd sc.parallelize(1 to 10) val transformedRdd rdd.map { x acc.add(1) // 错误在转换算子内部又触发了一个行动算子这里用foreach模拟 rdd.foreach(_ ()) // 这会导致新的Job被提交可能造成递归或不可预期的多次更新 x } transformedRdd.count()绝对禁止在RDD的转换算子map,filter,flatMap等内部触发任何行动算子如count(),collect(),saveAs...()。这会导致新的Job在Task线程中被提交破坏Spark的任务调度模型很可能导致死锁、栈溢出或累加器被重复更新结果完全不可预测。4.3 累加器与Spark SQL/DataFrame在Spark SQL或DataFrame API中使用累加器需要格外小心。因为Spark SQL有自己的优化器Catalyst可能会对逻辑计划进行重写改变算子的执行顺序和次数。不推荐的做法尝试在Dataset的map、filter等算子特别是非类型安全的map中使用累加器。由于编码器Encoder和优化器的存在行为可能不符合预期。相对安全的做法如果需要在SQL/DataFrame作业中进行全局统计优先考虑以下方式使用聚合函数直接使用groupBy().agg(count(...), sum(...))。这是最高效、最符合声明式编程范式的方式。将DataFrame转换为RDD在明确知道开销可控的情况下可以df.rdd.foreachPartition{...}在分区迭代器内部使用累加器。这样绕过了Spark SQL的优化回到了RDD的确定性执行模型。使用Dataset.foreach或foreachPartition这些是行动算子在它们内部使用累加器是安全的因为它们是执行的终点。val acc sc.longAccumulator(“sqlAcc”) df.foreach { row // foreach是一个行动算子 if (row.getAs[Int](“age”) 60) acc.add(1) }4.4 累加器的性能与监控累加器本身开销很小但滥用也会带来问题大量小累加器创建成百上千个累加器会增加Driver和Executor之间的元数据通信开销。频繁更新虽然在Task内部是本地更新但每个Task结束后需要将增量发送回Driver。如果Task数量极多如数百万Driver端合并这些消息也会有压力。集合累加器内存爆炸collectionAccumulator会将所有添加的元素收集到Driver端的一个列表中。如果每个Task都添加大量数据极易导致Driver内存溢出OOM。仅将其用于收集少量关键错误信息或样本。监控在Spark UI的 “Stages” 或 “Jobs” 详情页可以找到 “Accumulators” 标签页这里列出了所有累加器在作业运行过程中的值变化是调试累加器相关问题的利器。5. 累加器在复杂场景下的应用思考理解了基础用法和坑点后我们可以看看累加器在一些更复杂场景下的应用思路。5.1 用于调试与数据质量检查累加器是分布式调试的宝贵工具。例如在一个复杂的数据处理管道中你可以在不同阶段设置多个累加器来监控数据流。val totalInputAcc sc.longAccumulator(“total_input”) val parsedErrorAcc sc.longAccumulator(“parse_errors”) val bizRuleFilterAcc sc.longAccumulator(“filtered_by_rule”) val processedRDD rawRDD .mapPartitions { iter iter.map { record totalInputAcc.add(1) // ... 解析逻辑 if (parseFailed) { parsedErrorAcc.add(1) null } else { parsedRecord } }.filter(_ ! null) } .filter { record if (!bizRule(record)) { bizRuleFilterAcc.add(1) false } else true } // 执行行动算子后 println(s“输入总数: ${totalInputAcc.value}”) println(s“解析错误数: ${parsedErrorAcc.value}”) println(s“业务规则过滤数: ${bizRuleFilterAcc.value}”) println(s“输出有效数: ${processedRDD.count()}”) // 可以验证totalInputAcc.value parsedErrorAcc.value bizRuleFilterAcc.value processedRDD.count()这样无需将中间数据拉回Driver就能对整个作业的数据质量有一个全局的、量化的把握。5.2 实现自定义的聚合器作为reduceByKey的补充虽然reduceByKey是进行分组聚合的首选但有时聚合逻辑非常复杂或者需要维护一个跨分区的全局状态。此时可以用“累加器foreachPartition”的模式来模拟。假设我们需要找出全网点击量最高的前10个广告ID但数据量极大直接mapreduceByKeytop可能在一个节点上产生数据倾斜。我们可以使用每个分区局部Top-N再用累加器合并的思路。// 自定义一个维护Top10列表的累加器 class TopNAdAccumulator(n: Int) extends AccumulatorV2[(String, Long), List[(String, Long)]] { private var topNList: List[(String, Long)] List.empty override def isZero: Boolean topNList.isEmpty override def copy(): TopNAdAccumulator { val newAcc new TopNAdAccumulator(n) newAcc.topNList this.topNList newAcc } override def reset(): Unit { topNList List.empty } override def add(v: (String, Long)): Unit { topNList (v :: topNList).sortBy(-_._2).take(n) } override def merge(other: AccumulatorV2[(String, Long), List[(String, Long)]]): Unit { other match { case o: TopNAdAccumulator topNList (this.topNList ::: o.topNList).sortBy(-_._2).take(n) case _ throw new UnsupportedOperationException } } override def value: List[(String, Long)] topNList } // 使用 val top10Acc new TopNAdAccumulator(10) sc.register(top10Acc, “GlobalTop10Ads”) // 假设 clickRDD: RDD[(adId: String, clickCount: Long)] // 先进行分区内聚合减少数据量 val perPartitionTop10 clickRDD .reduceByKey(_ _) // 先按key聚合得到每个广告的总点击量 .mapPartitions { iter // 每个分区计算自己的Top10 val localTop10 iter.toList.sortBy(-_._2).take(10) Iterator(localTop10) } // 将每个分区的Top10添加到累加器 perPartitionTop10.foreach { localList localList.foreach(top10Acc.add) } // 最终累加器中保存的就是全局Top10 println(“全局点击量Top10广告:”) top10Acc.value.foreach { case (adId, count) println(s“$adId: $count”) }这种方法将全局Top-N的计算分解为“分区局部Top-N”和“全局合并”两步避免了将所有数据汇集到一个节点更适合处理超大规模数据集。当然这只是一个思路示例实际生产环境可能需要考虑更复杂的合并策略和性能优化。累加器是Spark编程中一个看似简单却至关重要的组件。它完美体现了分布式系统设计中的权衡通过限制操作类型只写来换取一致性、容错性和性能。掌握它不仅能让你避开分布式计数中的那些“坑”更能为你打开一扇窗去实现更灵活、更强大的分布式监控与聚合模式。下次当你需要在成千上万个任务中汇总一个信息时别忘了这个可靠的工具。

相关新闻

UE5批渲染技术解析:高效绘制抗锯齿粗线条的底层实现与优化

UE5批渲染技术解析:高效绘制抗锯齿粗线条的底层实现与优化

1. 项目概述:UE5批渲染绘制粗线条的底层逻辑 在UE5的渲染管线中,批渲染(Batch Renditing)是提升绘制效率、减少Draw Call的核心技术。当我们谈论“绘制粗线条”时,这通常不是一个简单的几何体绘制问题,而是…

2026/7/29 13:44:16 阅读更多 →
3分钟解锁Microsoft 365完整功能:Ohook开源方案终极指南

3分钟解锁Microsoft 365完整功能:Ohook开源方案终极指南

3分钟解锁Microsoft 365完整功能:Ohook开源方案终极指南 【免费下载链接】ohook An universal Office "activation" hook with main focus of enabling full functionality of subscription editions 项目地址: https://gitcode.com/gh_mirrors/oh/ohoo…

2026/7/29 13:44:16 阅读更多 →
重磅审核!许继电气专家组莅临芯瑞科技,共探电力光模块深度合作

重磅审核!许继电气专家组莅临芯瑞科技,共探电力光模块深度合作

近日,国内电力装备龙头企业许继电气资深审核专家任老师、张老师一行莅临成都芯瑞科技股份有限公司,开展全流程、高标准专项审厂工作。围绕特高压、智能电网、新能源、充换电、轨道交通及工业智能化五大核心业务,许继电气对配套光通信产品的宽…

2026/7/29 13:44:16 阅读更多 →

最新新闻

Unity UI Mask在Android平台渲染异常:原理、诊断与解决方案全解析

Unity UI Mask在Android平台渲染异常:原理、诊断与解决方案全解析

1. 项目概述:当UI遮罩在Android上“失灵” 在Unity开发中,UI Mask(遮罩)组件是我们实现圆形头像、滚动列表、不规则UI裁剪等功能的基石。它通过限制子物体的渲染区域,让开发者能轻松创造出各种精致的界面效果。在编辑器…

2026/7/29 13:52:21 阅读更多 →
代码成本归零时代:软件工程管理的价值重构与杠杆策略

代码成本归零时代:软件工程管理的价值重构与杠杆策略

那天下午,团队里最资深的工程师给我发来一条消息:“总监,新功能上线了,成本比预估降了70%。”按理说这是好消息,但我盯着屏幕看了很久——这不是第一次了。过去半年,我们经历了代码成本断崖式下跌&#xff…

2026/7/29 13:52:21 阅读更多 →
AI时代,安防行业为什么正在从“看得见”走向“听得懂”?

AI时代,安防行业为什么正在从“看得见”走向“听得懂”?

过去几十年,安防行业经历了几次重要变化。从模拟监控,到数字高清;从高清摄像,到智能分析。每一次升级,都围绕一个目标:让系统获取更多现场信息。如今,摄像机已经能够识别人脸、车辆、行为。但是…

2026/7/29 13:52:21 阅读更多 →
AIGC内容去重技术解析与实战方案

AIGC内容去重技术解析与实战方案

1. AIGC内容去重:为什么成为刚需? 过去半年,我帮17家内容团队处理过AIGC内容重复问题。最夸张的案例是某教育机构用ChatGPT生成的200篇英语学习文章,在Google搜索中被判定为"低质量重复内容",导致整个站群权…

2026/7/29 13:52:21 阅读更多 →
Kimi K3 开源了,但你的 RTX 4090 连它的“零头“都装不下:一份给普通开发者的残酷生存指南

Kimi K3 开源了,但你的 RTX 4090 连它的“零头“都装不下:一份给普通开发者的残酷生存指南

引言:当开源的喜悦撞上现实的铁墙 2026 年 7 月 27 日,对于中国 AI 圈乃至全球开发者社区来说,都是一个值得被记住的日子。 就在这一天,月之暗面(Moonshot AI)兑现了此前多次公开承诺,正式将旗下旗舰大模型 Kimi K3 的完整权重开源。消息一出,技术社区瞬间沸腾。GitH…

2026/7/29 13:52:21 阅读更多 →
LaTeX学术写作实战:从环境搭建到论文排版完整指南

LaTeX学术写作实战:从环境搭建到论文排版完整指南

1. 从Word到LaTeX:为什么学术写作的尽头是它?如果你正在为毕业论文、期刊投稿或者任何需要严肃排版的文档而头疼,大概率已经听人推荐过LaTeX。第一次接触它,你可能会被满屏的代码、复杂的编译环境和层出不穷的报错吓退&#xff0c…

2026/7/29 13:51:20 阅读更多 →

日新闻

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:00:23 阅读更多 →
AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础 在上一期「AI编程系列」中,我们学习了如何构建一个基础的 AI 问答系统,通过简单的输入输出让模型回应问题。但现实世界中的 AI 应用往往需要处理更复杂的场景:…

2026/7/29 0:00:23 阅读更多 →
AI智能体开发实战:从工具调用到企业级部署

AI智能体开发实战:从工具调用到企业级部署

1. 从被动问答到主动执行:AI Agent的范式转变过去两年,大语言模型最显著的应用形态是聊天机器人——用户提问,AI回答。但真正的生产力革命发生在2023年下半年:当AI学会主动调用工具完成任务时,生产力工具的历史被彻底改…

2026/7/29 0:00:23 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/28 12:04:22 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/28 8:29:16 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/28 5:03:42 阅读更多 →

月新闻