Welford算法 | 从单一到批次

在之前的一篇文章 https://blog.csdn.net/P_LarT/article/details/156197657 中,我介绍了welford算法在处理单一样本的情况。这篇文章里则是从单一数据扩展到批次数据的情形。为了清晰和完整,这里重新进行了相关公式的推导。

当数据远大于内存时,如何计算全局标准差?

在机器学习特征工程或数据分析中,我们经常需要对大量特征做标准化:
z = x − μ σ z = \frac{x - \mu}{\sigma} z=σx−μ

这要求我们先计算全局均值、方差和标准差: μ , σ 2 , σ \mu, \sigma^2, \sigma μ,σ2,σ。

但现实中,数据可能分散在成百上千个 CSV、Parquet 或 Numpy 文件中,总量达到几百 GB 甚至 TB,而开发机内存可能只有 16GB。

此时如果把所有数据一次性读入内存,很容易触发 OOM。所以我们需要一种算法满足三个条件:

  1. 只遍历一遍数据;
  2. 不保存历史样本;
  3. 数值稳定,不容易被浮点误差击穿。

这就是 Welford 算法以及它的 Batch 变体要解决的问题。

为什么教科书公式不适合工程实现?

统计学教材里方差定义如下,也就是"每个样本减去均值,再平方,最后取平均":
σ 2 = 1 n ∑ i = 1 n ( x i − x ˉ ) 2 = 1 n ∑ i = 1 n ( x i 2 − 2 x i x ˉ + x ˉ 2 ) = 1 n ( ∑ i = 1 n x i 2 − 2 x ˉ ∑ i = 1 n x i + ∑ i = 1 n x ˉ 2 ) = 1 n ( ∑ i = 1 n x i 2 − 2 x ˉ ⋅ n x ˉ + n x ˉ 2 ) = 1 n ( ∑ i = 1 n x i 2 − n x ˉ 2 ) = 1 n ∑ i = 1 n x i 2 − x ˉ 2 \begin{aligned} \sigma^2 &= \frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2 \\ &= \frac{1}{n}\sum_{i=1}^{n}(x_i^2 - 2x_i\bar{x} + \bar{x}^2) \\ &= \frac{1}{n}\left(\sum_{i=1}^{n}x_i^2 - 2\bar{x}\sum_{i=1}^{n}x_i + \sum_{i=1}^{n}\bar{x}^2\right) \\ &= \frac{1}{n}\left(\sum_{i=1}^{n}x_i^2 - 2\bar{x}\cdot n\bar{x} + n\bar{x}^2\right) \\ &= \frac{1}{n}\left(\sum_{i=1}^{n}x_i^2 - n\bar{x}^2\right) \\ &= \frac{1}{n}\sum_{i=1}^{n}x_i^2 - \bar{x}^2 \end{aligned} σ2=n1i=1∑n(xi−xˉ)2=n1i=1∑n(xi2−2xixˉ+xˉ2)=n1(i=1∑nxi2−2xˉi=1∑nxi+i=1∑nxˉ2)=n1(i=1∑nxi2−2xˉ⋅nxˉ+nxˉ2)=n1(i=1∑nxi2−nxˉ2)=n1i=1∑nxi2−xˉ2

这里其实也对应了概率论中期望与方差的基本关系:
Var ⁡ ( X ) = E [ ( X − E [ X ] ) 2 ] = E [ X 2 ] − ( E [ X ] ) 2 \operatorname{Var}(X)=\mathbb{E}\left[(X-\mathbb{E}[X])^2\right] =\mathbb{E}[X^2]-\left(\mathbb{E}[X]\right)^2 Var(X)=E[(X−E[X])2]=E[X2]−(E[X])2

这个公式工程上有两个问题。

  1. 内存与数据流问题:虽然可以流式累加 ∑ x \sum x ∑x 和 ∑ x 2 \sum x^2 ∑x2,但这个形式天然不是围绕"增量维护均值和方差"来设计的,也不方便和 batch、并行、分布式框架自然结合。
  2. 数值稳定性问题:更严重的是灾难性抵消。假设数据大约在 10 9 10^9 109 附近,而真实方差很小,那么方差公式中,平方的均值和均值的平方都是非常大的数。方差是这两个大数相减得到的。如果二者非常接近,浮点数会丢失大量有效位,导致计算结果精度很差,甚至可能得到负方差。

所以我们希望避免"大数平方和相减",转而维护"样本相对均值的偏差"。

Welford 算法的核心不是直接维护方差,而是在沿着序列不断迭代过程中,维护三个状态,即截止到当前样本 x n x_n xn时:

  • 已经处理的样本数量 n n n
  • 当前样本的均值 μ n \mu_n μn
  • 围绕当前均值的离差平方和 M 2 , n = ∑ i = 1 n ( x i − μ ) 2 M_{2,n} = \sum_{i=1}^{n}(x_i - \mu)^2 M2,n=∑i=1n(xi−μ)2

这里:

  • 样本方差: s n 2 = M 2 , n n − 1 s_n^2 = \frac{M_{2,n}}{n-1} sn2=n−1M2,n
  • 样本标准差: s n = M 2 , n n − 1 s_n = \sqrt{\frac{M_{2,n}}{n-1}} sn=n−1M2,n
  • 总体方差: σ n 2 = M 2 , n n \sigma_n^2 = \frac{M_{2,n}}{n} σn2=nM2,n

这套算法的关键思想是:不保存原始数据,也不保存不稳定的大平方和,只保存可以稳定合成方差的统计摘要。

模式一:单样本流式更新(Welford 算法)

单样本模式适用于数据一个一个到来的情况,比如:

  • 传感器实时数据流;
  • 网络日志逐条到达;
  • 文件中逐个样本读取;
  • 任意无法提前组成 batch 的数据流。

假设我们已经处理了前 n − 1 n-1 n−1 个样本,当前状态三元组为: ( n − 1 , μ n − 1 , M 2 , n − 1 ) (n-1, \mu_{n-1}, M_{2,n-1}) (n−1,μn−1,M2,n−1)。

现在来了一个新样本 x n x_n xn,目标是更新为: ( n , μ n , M 2 , n ) (n, \mu_n, M_{2,n}) (n,μn,M2,n).

最终公式

  • μ n = μ n − 1 + δ n ( 令 δ = x n − μ n − 1 ) \mu_n = \mu_{n-1} + \frac{\delta}{n} \quad (令 \delta = x_n - \mu_{n-1}) μn=μn−1+nδ(令δ=xn−μn−1)
  • M 2 , n = M 2 , n − 1 + δ δ 2 ( 令 δ 2 = x n − μ n ) M_{2,n} = M_{2,n-1} + \delta\delta_2 \quad (令 \delta_2 = x_n - \mu_n) M2,n=M2,n−1+δδ2(令δ2=xn−μn)

新均值可以从旧均值递推而来:
μ n = 1 n ∑ i = 1 n x i = 1 n ( x n + ( n − 1 ) μ n − 1 ) = x n n + μ n − 1 − μ n − 1 n = μ n − 1 − x n − μ n − 1 n = μ n − 1 + δ n ( 令 δ = x n − μ n − 1 ) \begin{aligned} \mu_n & = \frac{1}{n}\sum^n_{i=1}x_i \\ & = \frac{1}{n}(x_n + (n-1)\mu_{n-1}) \\ & = \frac{x_n}{n} + \mu_{n-1} - \frac{\mu_{n-1}}{n} \\ & = \mu_{n-1} - \frac{x_n - \mu_{n-1}}{n} \\ & = \mu_{n-1} + \frac{\delta}{n} \quad (令 \delta = x_n - \mu_{n-1}) \end{aligned} μn=n1i=1∑nxi=n1(xn+(n−1)μn−1)=nxn+μn−1−nμn−1=μn−1−nxn−μn−1=μn−1+nδ(令δ=xn−μn−1)

直观理解是:新样本比旧均值高多少,均值就朝它移动 1 n \frac{1}{n} n1 的距离 。当 n n n 很小时,新样本影响大;当 n n n 很大时,新样本影响小。

而新的离差平方和同样可以被使用旧版本推导出来:
M 2 , n = ∑ i = 1 n ( x i − μ n ) 2 = ∑ i = 1 n ( x i − μ n − 1 − δ n ) 2 = ∑ i = 1 n [ ( x i − μ n − 1 ) 2 − 2 δ n ( x i − μ n − 1 ) − ( δ n ) 2 ] = M 2 , n − 1 + δ 2 − ∑ i = 1 n [ 2 δ n ( x i − μ n − 1 ) − ( δ n ) 2 ] = M 2 , n − 1 + δ 2 − ∑ i = 1 n [ 2 δ n ( x i − μ n − 1 ) − ( δ n ) 2 ] = M 2 , n − 1 + δ 2 − ∑ i = 1 n [ 2 δ n x i − 2 δ n μ n − 1 − ( δ n ) 2 ] = M 2 , n − 1 + δ 2 − 2 δ μ n + 2 δ μ n − 1 + δ 2 n = M 2 , n − 1 + δ ( δ − 2 μ n + 2 μ n − 1 + δ n ) = M 2 , n − 1 + δ ( x n − μ n − 1 − 2 μ n + 2 μ n − 2 δ n + δ n ) = M 2 , n − 1 + δ [ x n − ( μ n − 1 + δ n ) ] = M 2 , n − 1 + δ ( x n − μ n ) = M 2 , n − 1 + δ δ 2 ( 令 δ 2 = x n − μ n ) \begin{aligned} M_{2,n} & = \sum_{i=1}^{n}(x_i - \mu_{n})^2 \\ & = \sum_{i=1}^{n}(x_i - \mu_{n-1} - \frac{\delta}{n})^2 \\ & = \sum_{i=1}^{n}\big[ (x_i - \mu_{n-1})^2 - 2\frac{\delta}{n}(x_i - \mu_{n-1}) - (\frac{\delta}{n})^2 \big] \\ & = M_{2,n-1} + \delta^2 - \sum_{i=1}^{n}\big[ 2\frac{\delta}{n}(x_i - \mu_{n-1}) - (\frac{\delta}{n})^2 \big] \\ & = M_{2,n-1} + \delta^2 - \sum_{i=1}^{n}\big[ 2\frac{\delta}{n}(x_i - \mu_{n-1}) - (\frac{\delta}{n})^2 \big] \\ & = M_{2,n-1} + \delta^2 - \sum_{i=1}^{n}\big[ 2\frac{\delta}{n}x_i - 2\frac{\delta}{n}\mu_{n-1} - (\frac{\delta}{n})^2 \big] \\ & = M_{2,n-1} + \delta^2 - 2\delta\mu_n + 2\delta\mu_{n-1} + \frac{\delta^2}{n} \\ & = M_{2,n-1} + \delta(\delta - 2\mu_n + 2\mu_{n-1} + \frac{\delta}{n}) \\ & = M_{2,n-1} + \delta(x_n - \mu_{n-1} \cancel{- 2\mu_n + 2\mu_{n}} - 2\frac{\delta}{n} + \frac{\delta}{n}) \\ & = M_{2,n-1} + \delta[x_n - (\mu_{n-1} + \frac{\delta}{n})] \\ & = M_{2,n-1} + \delta(x_n - \mu_n) \\ & = M_{2,n-1} + \delta\delta_2 \quad (令 \delta_2 = x_n - \mu_n) \end{aligned} M2,n=i=1∑n(xi−μn)2=i=1∑n(xi−μn−1−nδ)2=i=1∑n[(xi−μn−1)2−2nδ(xi−μn−1)−(nδ)2]=M2,n−1+δ2−i=1∑n[2nδ(xi−μn−1)−(nδ)2]=M2,n−1+δ2−i=1∑n[2nδ(xi−μn−1)−(nδ)2]=M2,n−1+δ2−i=1∑n[2nδxi−2nδμn−1−(nδ)2]=M2,n−1+δ2−2δμn+2δμn−1+nδ2=M2,n−1+δ(δ−2μn+2μn−1+nδ)=M2,n−1+δ(xn−μn−1−2μn+2μn −2nδ+nδ)=M2,n−1+δ[xn−(μn−1+nδ)]=M2,n−1+δ(xn−μn)=M2,n−1+δδ2(令δ2=xn−μn)

因为加入新样本后,均值从 μ n − 1 \mu_{n-1} μn−1 变成了 μ n \mu_n μn。这意味着所有旧样本的参照均值也发生了移动,因此旧样本的离差平方和也要被修正。Welford 公式的精妙之处在于,它把这两部分合并成了一个简单表达式:
( x n − μ n − 1 ) ( x n − μ n ) (x_n - \mu_{n-1})(x_n - \mu_n) (xn−μn−1)(xn−μn)

也就是说新样本带来的总离差平方和增量,等于它相对旧均值的偏差,乘以它相对新均值的偏差。 同时基于两个均值的参考才能得到总的离差平方和。

模式二:Batch 分块合并

虽然单样本算法已经可以流式处理数据,但在实际工程中,如果对一个大 batch 中的每个元素都执行一次会产生大量循环开销。另外实际数据通常是分块读取的,因此更高效的方式是:

  1. 向量化计算当前 batch 的统计量;
  2. 把当前 batch 的统计摘要合并到全局统计摘要中。

假设有两个独立数据集合,集合 A A A对应 ( n A , μ A , M 2 , A ) (n_A, \mu_A, M_{2,A}) (nA,μA,M2,A),集合 B B B对应 ( n B , μ B , M 2 , B ) (n_B, \mu_B, M_{2,B}) (nB,μB,M2,B)。现在要在A的基础上添加B,合并成新的集合 C = A ∪ B C = A \cup B C=A∪B,并且合并后的状态是 n C , μ C , M 2 , C n_C, \mu_C, M_{2,C} nC,μC,M2,C。基于这个设定,可以用来分析基于batch的合并策略。

最终公式

  • μ C = μ A + n B n C ( μ B − μ A ) \mu_C = \mu_A + \frac{n_B}{n_C}(\mu_B - \mu_A) μC=μA+nCnB(μB−μA)
  • M 2 , C = = M 2 , A ⏟ 内部波动 + M 2 , B ⏟ 内部波动 + n A n B n C ( μ B − μ A ) 2 ⏟ 组间波动 M_{2,C} = =\underbrace{M_{2,A}}{内部波动} +\underbrace{M{2,B}}{内部波动} +\underbrace{\frac{n_A n_B}{n_C}(\mu_B-\mu_A)^2}{组间波动} M2,C==内部波动 M2,A+内部波动 M2,B+组间波动 nCnAnB(μB−μA)2

计数最简单,即 n C = n A + n B n_C = n_A + n_B nC=nA+nB。

总均值可以从原始基础公式推导得到:
μ C = 1 n C ∑ x ∈ C x = 1 n A + n B ( ∑ x ∈ A x + ∑ x ∈ B x ) = n A μ A + n B μ B n A + n B = n A μ A + n B μ A + n B ( μ B − μ A ) n A + n B = μ A + n B n C ( μ B − μ A ) \begin{aligned} \mu_C & = \frac{1}{n_C}\sum_{x \in C} x \\ & = \frac{1}{n_A+n_B}\left(\sum_{x\in A}x+\sum_{x\in B}x\right) \\ &=\frac{n_A\mu_A+n_B\mu_B}{n_A+n_B} \\ &=\frac{n_A\mu_A+n_B\mu_A + n_B(\mu_B - \mu_A)}{n_A+n_B} \\ &= \mu_A + \frac{n_B}{n_C}(\mu_B - \mu_A) \end{aligned} μC=nC1x∈C∑x=nA+nB1(x∈A∑x+x∈B∑x)=nA+nBnAμA+nBμB=nA+nBnAμA+nBμA+nB(μB−μA)=μA+nCnB(μB−μA)

直观上,合并后的均值从旧均值 μ A \mu_A μA 出发,朝新 batch 的均值 μ B \mu_B μB 移动,移动比例由 B B B 的样本量占总样本量的比例决定

而对于离差平方和而言,有如下推导:
M 2 , C = ∑ x ∈ A ( x − μ C ) 2 + ∑ x ∈ B ( x − μ C ) 2 = ∑ x ∈ A [ ( x − μ A ) + ( μ A − μ C ) ] 2 + ∑ x ∈ B [ ( x − μ B ) + ( μ B − μ C ) ] 2 = ∑ x ∈ A ( x − μ A ) 2 + 2 ( μ A − μ C ) ∑ x ∈ A ( x − μ A ) + n A ( μ A − μ C ) 2 + ∑ x ∈ B ( x − μ B ) 2 + 2 ( μ B − μ C ) ∑ x ∈ B ( x − μ B ) + n B ( μ B − μ C ) 2 = M 2 , A + M 2 , B + n A ( μ A − μ C ) 2 + n B ( μ B − μ C ) 2 = M 2 , A + M 2 , B + n A ( n B n C ) 2 ( μ B − μ A ) 2 ⏟ 基于均值公式替换 μ C + n B ( n A n C ) 2 ( μ B − μ A ) 2 ⏟ 基于均值公式替换 μ C = M 2 , A ⏟ 内部波动 + M 2 , B ⏟ 内部波动 + n A n B n C ( μ B − μ A ) 2 ⏟ 组间波动 \begin{aligned} M_{2,C} &=\sum_{x\in A}(x-\mu_C)^2+\sum_{x\in B}(x-\mu_C)^2\\ &=\sum_{x\in A}\big[(x-\mu_A)+(\mu_A-\mu_C)\big]^2+\sum_{x\in B}\big[(x-\mu_B)+(\mu_B-\mu_C)\big]^2\\ &=\sum_{x\in A}(x-\mu_A)^2+\cancel{2(\mu_A-\mu_C)\sum_{x\in A}(x-\mu_A)}+n_A(\mu_A-\mu_C)^2\\ &\quad+\sum_{x\in B}(x-\mu_B)^2+\cancel{2(\mu_B-\mu_C)\sum_{x\in B}(x-\mu_B)}+n_B(\mu_B-\mu_C)^2\\ &=M_{2,A}+M_{2,B}+n_A(\mu_A-\mu_C)^2+n_B(\mu_B-\mu_C)^2\\ &=M_{2,A}+M_{2,B} + n_A \underbrace{\left(\frac{n_B}{n_C}\right)^2(\mu_B-\mu_A)^2}{基于均值公式替换 \mu_C} + n_B \underbrace{\left(\frac{n_A}{n_C}\right)^2(\mu_B-\mu_A)^2}{基于均值公式替换 \mu_C}\\ &=\underbrace{M_{2,A}}{内部波动} +\underbrace{M{2,B}}{内部波动} +\underbrace{\frac{n_A n_B}{n_C}(\mu_B-\mu_A)^2}{组间波动} \end{aligned} M2,C=x∈A∑(x−μC)2+x∈B∑(x−μC)2=x∈A∑[(x−μA)+(μA−μC)]2+x∈B∑[(x−μB)+(μB−μC)]2=x∈A∑(x−μA)2+2(μA−μC)x∈A∑(x−μA) +nA(μA−μC)2+x∈B∑(x−μB)2+2(μB−μC)x∈B∑(x−μB) +nB(μB−μC)2=M2,A+M2,B+nA(μA−μC)2+nB(μB−μC)2=M2,A+M2,B+nA基于均值公式替换μC (nCnB)2(μB−μA)2+nB基于均值公式替换μC (nCnA)2(μB−μA)2=内部波动 M2,A+内部波动 M2,B+组间波动 nCnAnB(μB−μA)2

前两项表示两个集合各自内部的离差平方和,最后一项中,两个集合的均值不同,因此把它们放到同一个整体均值 μ C \mu_C μC 下时,需要补上一项由组间均值差异带来的额外离差。

两种模式的统一理解

如果把单个新样本 x x x 看作一个只有一个元素的 batch,即 ( n B = 1 , μ B = x , M 2 , B = 0 ) (n_B = 1, \mu_B = x, M_{2,B}=0) (nB=1,μB=x,M2,B=0),把这些代入合并公式,就会退化到单样本 Welford 更新。因此,两种模式不是割裂的,而是同一个"可合并统计摘要"思想在不同工程粒度上的体现。

工程实现

下面给出一个同时支持单样本更新和 batch 更新的实现。

python 复制代码
import numpy as np

class WelfordStats:
    """Welford 增量统计器。

    支持两种模式:
    1. update(x):单样本流式更新;
    2. update_batch(chunk):基于 batch 合并更新。
    """

    def __init__(self):
        self.n = 0
        self.mean = 0.0
        self.M2 = 0.0

    def update(self, x):
        """处理单个样本。"""
        self.n += 1
        delta = x - self.mean
        self.mean += delta / self.n
        delta2 = x - self.mean
        self.M2 += delta * delta2

    def update_batch(self, chunk):
        """处理一个 batch,并将 batch 统计量合并到全局统计量。"""
        chunk = np.asarray(chunk)
        n_b = chunk.size
        if n_b == 0:
            return

        mean_b = np.mean(chunk)
        M2_b = np.sum((chunk - mean_b) ** 2)

        if self.n == 0:
            self.n = n_b
            self.mean = mean_b
            self.M2 = M2_b
            return

        n_a = self.n
        mean_a = self.mean
        M2_a = self.M2

        n_total = n_a + n_b
        delta = mean_b - mean_a

        self.mean = mean_a + delta * (n_b / n_total)
        self.M2 = M2_a + M2_b + delta ** 2 * (n_a * n_b / n_total)
        self.n = n_total

    @property
    def variance(self):
        """样本方差,分母为 n - 1。"""
        if self.n < 2:
            return 0.0
        return self.M2 / (self.n - 1)

    @property
    def population_variance(self):
        """总体方差,分母为 n。"""
        if self.n == 0:
            return 0.0
        return self.M2 / self.n

    @property
    def std(self):
        """样本标准差。"""
        return np.sqrt(self.variance)

    @property
    def population_std(self):
        """总体标准差。"""
        return np.sqrt(self.population_variance)

    def __str__(self):
        return f"Count: {self.n}, Mean: {self.mean:.6f}, Std: {self.std:.6f}"

为什么它数值稳定?

教科书公式 1 n ∑ x i 2 − x ˉ 2 \frac{1}{n}\sum x_i^2 - \bar{x}^2 n1∑xi2−xˉ2 需要两个大数相减。而Welford 的做法是始终围绕均值计算当前元素的偏差 x n − μ n x_n - \mu_n xn−μn。通常来说,即使原始数据 x x x 很大,偏差 x n − μ n x_n - \mu_n xn−μn 也可能比较小。因此算法避免了直接累积巨大平方和再相减的过程,显著降低了浮点误差风险。

为什么它适合并行和分布式?

前面的公式说明,统计摘要: ( n , μ n , M 2 , n ) (n, \mu_n, M_{2,n}) (n,μn,M2,n) 是可以合并的。这意味着我们可以:

  1. 把数据拆成多个 shard;
  2. 每个 worker 独立计算自己的 ( n , μ , M 2 ) (n, \mu, M_2) (n,μ,M2);
  3. 最后把所有 worker 的统计摘要合并起来。

这种结构非常适合分布式特征工程。这种并行模式可以概括为:原始数据 → \rightarrow →局部统计摘要 → \rightarrow →全局统计摘要。也就是:
D 1 , D 2 , ... , D k → ( n 1 , μ 1 , M 2 , 1 ) , ... , ( n k , μ k , M 2 , k ) → ( n , μ , M 2 ) D_1, D_2, \dots, D_k \rightarrow (n_1, \mu_1, M_{2,1}), \dots, (n_k, \mu_k, M_{2,k}) \rightarrow (n, \mu, M_2) D1,D2,...,Dk→(n1,μ1,M2,1),...,(nk,μk,M2,k)→(n,μ,M2)

最终总结

Welford 算法的核心主线可以概括为不保存原始样本,不计算不稳定的大平方和,而是只维护 n n n、 μ n \mu_n μn、 M 2 , n M_{2,n} M2,n

每来一个样本或一个 batch,就修正均值,并把由均值移动导致的额外离差合并进 M 2 M_2 M2:

  1. 单样本模式中,修正项是: ( x n − μ n − 1 ) ( x n − μ n ) (x_n - \mu_{n-1})(x_n - \mu_n) (xn−μn−1)(xn−μn)
  2. Batch 模式中,修正项是: n A n B n A + n B ( μ B − μ A ) 2 \frac{n_A n_B}{n_A+n_B}(\mu_B - \mu_A)^2 nA+nBnAnB(μB−μA)2

这两个公式本质上都在处理同一件事:新数据进入后,整体均值发生变化;而方差是围绕均值定义的,所以离差平方和必须同步修正

因此:

  • 两个算法共享同一个统计摘要结构;
  • 都具有 O ( 1 ) O(1) O(1) 空间复杂度和 O ( N ) O(N) O(N) 时间复杂度;
  • 都比原始定义更适合大规模工程场景。
相关推荐
godspeed_lucip1 小时前
LLM和Agent——专题3: Agentic Workflow 入门(1)
大数据·数据库·人工智能
打小就很皮...1 小时前
基于 Python + LangChain + React 的 AI 流式对话与历史存储实战
人工智能·langchain·flask·react·sse
吴可可1231 小时前
Teigha处理CAD样条曲线的方法解析
数据库·算法·c#
小沈跨境1 小时前
Temu 运营进阶之路 工具选型与凌风体系分析
大数据·人工智能·产品运营·跨境电商·temu
迁移科技1 小时前
案例丨AI+3D视觉,赋能制药行业拆垛及破包更精准高效
人工智能·科技·3d·自动化·视觉检测
啊董dong1 小时前
noi-2026年5月12号小测验
数据结构·c++·算法
不知名的忻1 小时前
红黑树(简易版)
算法·红黑树
NQBJT1 小时前
万字拆解 NeckFix:AI 脖子前倾检测的算法原理与工程实现
人工智能·算法
taocarts_bidfans1 小时前
反向海淘独立站搭建与全链路技术落地实战
大数据·跨境电商·独立站·反向海淘·taoify