向量检索 ANN 参数实测:换一种数据分布,recall 掉了 51 个百分点

摘要

做 RAG 检索层时,nprobe=16、efSearch=64 这类"经验值"常被直接抄进配置。本文在本机(Windows 10 x64 / 32 逻辑核 / Python 3.11.5 / faiss-cpu 1.14.3)搭了一个可复现的测量台:5 万条 128 维合成向量、500 条查询、k=10,用 IndexFlatIP 精确检索产出 ground truth,对 IVF、HNSW、IVFPQ、SQ8 四类索引各跑 3 轮(seed=1/2/3)。结论是:同一组参数换一种语料分布,recall@10 能差 46.4~51.6 个百分点 ,所以参数只能在自己的数据上测;另外实测到几个反直觉的点------批处理比加核有用一个数量级、SQ8 省 4 倍内存却慢 15~20 倍、top_k 要得更深召回反而掉到 16%。所有数字都来自本机 stdout,跑不出的东西一律不写。

一、先说清楚要测什么

向量检索这条链路上可调的参数分两类,一类影响召回,一类影响成本,混在一起调就会变成玄学:
#mermaid-svg-XeWsmhfDKGuqR0mC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XeWsmhfDKGuqR0mC .error-icon{fill:#552222;}#mermaid-svg-XeWsmhfDKGuqR0mC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XeWsmhfDKGuqR0mC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XeWsmhfDKGuqR0mC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XeWsmhfDKGuqR0mC .marker.cross{stroke:#333333;}#mermaid-svg-XeWsmhfDKGuqR0mC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XeWsmhfDKGuqR0mC p{margin:0;}#mermaid-svg-XeWsmhfDKGuqR0mC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XeWsmhfDKGuqR0mC .cluster-label text{fill:#333;}#mermaid-svg-XeWsmhfDKGuqR0mC .cluster-label span{color:#333;}#mermaid-svg-XeWsmhfDKGuqR0mC .cluster-label span p{background-color:transparent;}#mermaid-svg-XeWsmhfDKGuqR0mC .label text,#mermaid-svg-XeWsmhfDKGuqR0mC span{fill:#333;color:#333;}#mermaid-svg-XeWsmhfDKGuqR0mC .node rect,#mermaid-svg-XeWsmhfDKGuqR0mC .node circle,#mermaid-svg-XeWsmhfDKGuqR0mC .node ellipse,#mermaid-svg-XeWsmhfDKGuqR0mC .node polygon,#mermaid-svg-XeWsmhfDKGuqR0mC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XeWsmhfDKGuqR0mC .rough-node .label text,#mermaid-svg-XeWsmhfDKGuqR0mC .node .label text,#mermaid-svg-XeWsmhfDKGuqR0mC .image-shape .label,#mermaid-svg-XeWsmhfDKGuqR0mC .icon-shape .label{text-anchor:middle;}#mermaid-svg-XeWsmhfDKGuqR0mC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XeWsmhfDKGuqR0mC .rough-node .label,#mermaid-svg-XeWsmhfDKGuqR0mC .node .label,#mermaid-svg-XeWsmhfDKGuqR0mC .image-shape .label,#mermaid-svg-XeWsmhfDKGuqR0mC .icon-shape .label{text-align:center;}#mermaid-svg-XeWsmhfDKGuqR0mC .node.clickable{cursor:pointer;}#mermaid-svg-XeWsmhfDKGuqR0mC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XeWsmhfDKGuqR0mC .arrowheadPath{fill:#333333;}#mermaid-svg-XeWsmhfDKGuqR0mC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XeWsmhfDKGuqR0mC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XeWsmhfDKGuqR0mC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XeWsmhfDKGuqR0mC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XeWsmhfDKGuqR0mC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XeWsmhfDKGuqR0mC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XeWsmhfDKGuqR0mC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XeWsmhfDKGuqR0mC .cluster text{fill:#333;}#mermaid-svg-XeWsmhfDKGuqR0mC .cluster span{color:#333;}#mermaid-svg-XeWsmhfDKGuqR0mC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XeWsmhfDKGuqR0mC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XeWsmhfDKGuqR0mC rect.text{fill:none;stroke-width:0;}#mermaid-svg-XeWsmhfDKGuqR0mC .icon-shape,#mermaid-svg-XeWsmhfDKGuqR0mC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XeWsmhfDKGuqR0mC .icon-shape p,#mermaid-svg-XeWsmhfDKGuqR0mC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XeWsmhfDKGuqR0mC .icon-shape .label rect,#mermaid-svg-XeWsmhfDKGuqR0mC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XeWsmhfDKGuqR0mC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XeWsmhfDKGuqR0mC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XeWsmhfDKGuqR0mC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} IVF: 扫 nprobe 个桶
HNSW: 探索 efSearch 个节点
约束
用户问题
Embedding 模型

产出 128 维归一化向量
索引层

可调 nprobe / efSearch
候选 top-k
rerank 精排

可调候选宽度
prompt 拼装
大模型生成
内存与建库耗时

nlist / M / PQ 子量化数

要回答的三个问题很具体:

  1. 召回率和延迟的交换曲线长什么样,拐点在哪;
  2. 内存该省的时候用 PQ 还是 SQ,代价各是多少;
  3. 数据涨 4 倍、请求打批、CPU 加核,哪个才真的影响线上延迟。

二、测量台:一个精确基线 + 一把"噪声尺"

语料是合成的,这点必须先说清楚,不然读数会被当成产品指标。生成方式是:取 n_topic 个随机主题中心,每条文档按 softmax 权重混合其中 mixture 个方向,加 noise 幅度高斯噪声后 L2 归一化。查询集与语料同分布但不在语料里。

python 复制代码
def make_corpus(rng, n, dim, n_topic, mixture, noise):
    """带聚簇结构的归一化向量。真实 embedding 的内在维度远小于环境维度,
    所以这里刻意用主题混合来复现"可压缩"这个特性,而不是纯随机向量。"""
    centroids = rng.standard_normal((n_topic, dim)).astype("float32")
    centroids /= np.linalg.norm(centroids, axis=1, keepdims=True)
    idxs = np.argsort(rng.random((n, n_topic)), axis=1)[:, :mixture]
    w = np.exp(rng.random((n, mixture)).astype("float32") * 3.0)
    w /= w.sum(axis=1, keepdims=True)                      # 主题权重(softmax)
    vecs = np.einsum("ij,ijd->id", w, centroids[idxs]).astype("float32")
    vecs += noise * rng.standard_normal((n, dim)).astype("float32")
    faiss.normalize_L2(vecs)                               # 归一化后内积即 cosine
    return vecs

我跑了两套分布 :A 强聚簇(20 主题、单主题、噪声 0.05)和 B 弱结构(64 主题、3 主题混合、噪声 0.15)。这不是多余的,第四节会看到它才是全文最重要的变量。

延迟测量刻意做成逐条查询(在线单并发,不带批处理摊薄),并且先量一次噪声底:

python 复制代码
def per_query(index, queries, gt=None, nprobe=None, efsearch=None, kk=10):
    if nprobe is not None:
        index.nprobe = nprobe          # IVF:扫几个桶,改这个不用重建索引
    if efsearch is not None:
        index.hnsw.efSearch = efsearch # HNSW:候选队列长度,同样在线可调
    lat = np.empty(queries.shape[0])
    ids = np.empty((queries.shape[0], kk), dtype="int64")
    for i in range(queries.shape[0]):
        q = np.ascontiguousarray(queries[i:i + 1])   # 必须连续内存,否则 faiss 走慢路径
        t0 = time.perf_counter()
        _, I = index.search(q, kk)
        lat[i] = (time.perf_counter() - t0) * 1e3
        ids[i] = I[0]
    return lat, ids, (None if gt is None else recall_at(gt, ids, kk))

噪声底实测:p50 0.003~0.006ms,p99 0.009~0.017ms (拿只装了 1 条向量的索引做同样的 search 调用)。也就是说,Python + faiss 的一次调用本身就要 3~6µs,任何低于 10µs 的延迟差都只是噪声,不值得拿去决策。

三、召回-延迟前沿(语料 B 弱结构,3 轮区间)

索引与参数 recall@10 p50 (ms) p99 (ms) 单并发 QPS 建库/训练 (s) 理论内存 (MB)
Flat 精确(基线) 1.000 0.328~0.498 0.59~1.678 1,842~2,728 0.006~0.01 24.4
IVF nlist=1024 nprobe=1 0.0168~0.0184 0.023~0.046 0.033~0.075 20,521~41,812 0.63~0.67 24.9
IVF nlist=1024 nprobe=4 0.0488~0.0612 0.027~0.054 0.041~0.090 17,834~35,096 0.63~0.67 24.9
IVF nlist=1024 nprobe=16 0.1536~0.1590 0.047~0.059 0.069~0.110 15,348~20,742 0.63~0.67 24.9
IVF nlist=1024 nprobe=64 0.3790~0.3812 0.091~0.125 0.137~0.273 7,677~10,901 0.63~0.67 24.9
IVF nlist=1024 nprobe=256 0.7318~0.7382 0.238~0.271 0.370~0.500 3,493~4,059 0.63~0.67 24.9
HNSW M=16 efC=200 efS=200 0.6688~0.6832 0.265~0.301 0.405~0.729 3,093~3,659 0.93~0.98 ---
HNSW M=32 efC=200 efS=16 0.2640~0.2802 0.073~0.126 0.112~0.193 7,714~13,290 1.79~1.88 ---
HNSW M=32 efC=200 efS=64 0.6024~0.6168 0.185~0.199 0.274~0.692 3,763~5,148 1.79~1.88 ---
HNSW M=32 efC=200 efS=200 0.8924~0.9024 0.433~0.509 0.696~0.726 1,923~2,194 1.79~1.88 ---

读这张表要注意三件事:

  • IVF 的召回随 nprobe 近似线性但远未到线性 :nprobe 从 16 到 64 是 4 倍扫库量,召回 0.154~0.159 → 0.379~0.381(约 2.4 倍)。而 p50 只从 0.047~0.059 涨到 0.091~0.125ms,交换比在低 nprobe 段非常划算,到 nprobe=256 时 p50 已 0.238~0.271ms,召回 0.73 附近,接近精确基线的延迟了------那还不如直接上 Flat。
  • HNSW 在同召回下延迟更高,但建库慢得多 :要 0.89 的召回,HNSW M=32 要 efS=200、p50 0.433~0.509ms;IVF 要到 0.73 只需 nprobe=256、p50 0.238~0.271ms。而 M 从 16 翻到 32,建库耗时从 0.93~0.98s 变成 1.79~1.88s(约 1.9 倍),召回同档位只高一大截。
  • p99 和 p50 不是一回事:HNSW M=32 efS=64 的 p50 是 0.185~0.199ms,p99 却在 0.274~0.692ms 之间摆动,轮间差到 2.5 倍。写 SLA 要按 p99 排预算。

四、真正决定召回的是语料分布,不是参数

把完全相同的参数放到 A 强聚簇语料上,结果是这样(3 轮区间):

参数 A 强聚簇 recall@10 B 弱结构 recall@10 差值(百分点)
IVF nprobe=4 0.0562~0.0798 0.0488~0.0612 -0.5~3.1
IVF nprobe=16 0.3400~0.3912 0.1536~0.1590 18.1~23.8
IVF nprobe=64 0.8450~0.8946 0.3790~0.3812 46.4~51.6
HNSW M=32 efS=16 0.2564~0.3200 0.2640~0.2802 -2.4~5.6
HNSW M=32 efS=64 0.4960~0.5696 0.6024~0.6168 -12.1~-3.3
HNSW M=32 efS=200 0.7586~0.7986 0.8924~0.9024 -14.4~-9.4

两个结论值得单独拎出来:

  1. IVF 在强聚簇数据上收益极大(nprobe=64 时 0.845~0.895 vs 0.379~0.381)。原因是桶就是簇,扫对了桶一次就捞到一整组近邻;数据不聚簇时,真近邻散在许多桶里,多扫 4 倍桶数也补不回来。
  2. HNSW 的方向反过来------同一档位它在弱结构语料上召回更高,强聚簇反而低 9.4~14.4 个百分点。20 个巨簇、簇内噪声只有 0.05,意味着 top-10 里有大量几乎等距的点,贪心图搜索一旦收敛到某个"枢纽"就漏掉其余等价候选。

所以"我们项目 efSearch=64 够用"这种话,换一批 embedding、换一个领域,甚至只是重新切一次分块,都不再成立。可迁移的做法是:拿精确检索当 ground truth,在自己的语料上把上面那条 recall-latency 曲线画一遍,再按 p99 预算反推参数。

五、要省内存时,PQ 和 SQ 的代价完全不是一种东西

方案 recall@10 p50 (ms) 单并发 QPS 理论内存 (MB) 训练+建库 (s)
Flat float32 1.000 0.328~0.498 1,842~2,728 24.4 0.006~0.01
IVFPQ m=16 8bit nprobe=16 0.0956~0.0990 0.256~0.276 3,826~4,158 1.3 8.22~8.47
IVFPQ m=16 8bit nprobe=64 0.1568~0.1632 0.583~0.619 1,361~1,447 1.3 8.23~8.74
SQ8 精确扫描 0.9776~0.9814 6.778~6.874 139~148 6.1 0.002
  • IVFPQ 把 24.4MB 压到 1.3MB(约 18.8 倍) ,代价是召回在弱结构语料上只剩 0.0956~0.0990,且训练+建库 8.22~8.74s,是 IVF-Flat 的 0.63~0.67s 的 12~13 倍。库要频繁重建的话,这个成本比内存更贵。
  • SQ8 这一行是本次最反直觉的实测 :它仍是精确扫描,量化后内存只从 24.4→6.1MB(4 倍),召回确实几乎不掉(0.9776~0.9814),但 p50 是 6.778~6.874ms,比 float32 的 Flat 慢 15~20 倍,单并发 QPS 只有 139~148。想当然地以为"量化=省内存顺带提速"是错的,至少这条路徑上量化把 SIMD 的规整访存打散了。
  • 如果你的目的是省钱而不是省延迟,先确认瓶颈真的是内存。本次 5 万条 ×128 维 float32 只有 24.4MB,百万级也才 488MB 量级,内存很少是向量检索的第一瓶颈。

六、加核几乎没用,打批有用一个数量级

同一份 HNSW 索引(M=32、efSearch=64)、同一批查询,只改 OpenMP 线程数:

omp 线程 p50 (ms) p99 (ms) 单并发 QPS
1 0.208~0.343 0.285~0.415 2,888~4,687
4 0.227~0.306 0.302~0.385 3,672~4,348
32 0.170~0.185 0.263~0.320 5,154~5,663

从 1 核到 32 核,单条查询只快 1.2~1.8 倍(图搜索本身是串行的,多线程只在一次调用内部并行扩边)。而改成批量查询:

索引 batch ms/条 等效 QPS 相对 batch=1
IVF nprobe=16 1 0.049~0.053 18,949~20,257 1x
IVF nprobe=16 32 0.012~0.017 57,656~80,273 3.0~3.8x
IVF nprobe=16 500 0.007 136,162~147,427 7.4x
HNSW M=32 efS=64 1 0.230~0.273 3,666~4,351 1x
HNSW M=32 efS=64 32 0.025~0.029 33,924~40,279 8.1~9.9x
HNSW M=32 efS=64 500 0.016~0.020 50,884~61,438 14~17x

HNSW 从批处理里赚到的比 IVF 多得多(14~17 倍 vs 7.4 倍),这也解释了为什么"逐条在线查询"和"离线批量灌库/高并发摊薄"两种场景的最优索引不是同一个。给线上服务多配 CPU 核,不如在网关侧做 10~30ms 的微批。

七、数据涨 4 倍之后,先掉的是 p99 不是召回

同一张 IVF 表(nlist=1024)从 5 万加到 20 万条,nprobe 不动:

指标 50k 200k(nprobe=16 不动) 200k(nprobe 调到 64)
recall@10 0.1536~0.1590 0.1446~0.1524 0.3498~0.3606
p99 (ms) 0.069~0.110 0.160~0.331 0.487~0.617

召回只掉了 0.7~1.4 个百分点,p99 却涨了 1.5~3 倍------桶变大了,扫的桶数没变,但每个桶里的暴力计算变多了。等召回掉眼面前,延迟先劣化。

另外测了候选深度(nprobe=64):recall@10 = 0.379~0.381,recall@30 = 0.3415~0.3459,recall@100 = 0.1631~0.1640,p50 从 0.078~0.081ms 只涨到 0.091~0.097ms。要 top-100 给 rerank,ANN 只能给你 16% 的对------"反正精排能吃大候选集"这个假设得先测 recall@100 再说。

顺带一个训练量的对照:faiss 建议训练样本 ≥ 39×nlist,本机用 20,000 条(低于 39,936 会打 WARNING)与 40,960 条对比,同 nprobe 下召回只差 0.14~1.36 个百分点 (nprobe=16)/ 0.84~2.34 个百分点(nprobe=64)。告警要读懂,但不必为了它推倒重建。

避坑指南

  1. 别在 10µs 以下比延迟。 本机调用开销底就 3~6µs(p99 9~17µs),IVF nprobe=1 的 0.023~0.046ms 里有相当比例是框架开销。
  2. 别照抄 nprobe / efSearch。 同参数换语料分布能差 46.4~51.6 个百分点,而且 IVF 与 HNSW 的优劣方向会反转。换 embedding 模型、换分块策略之后必须重测。
  3. nlist 不是越大越好,且要跟着数据量走。 本次固定 nlist=1024,数据 4 倍后掉的是 p99(1.5~3 倍)。要么同步调大 nlist 重建,要么按数据量把 nprobe 一起提上去。
  4. 别默认"量化就是加速"。 SQ8 精确扫描实测慢 15~20 倍、QPS 只有 139~148;IVFPQ 省 18.8 倍内存但建库慢 12~13 倍、弱结构语料上召回不到 10%。
  5. top_k 要深之前先测 recall@k。 实测 k 从 10 到 100,召回 0.379→0.163,候选集越深 ANN 越漏。
  6. 改 nprobe / efSearch 不用重建索引,改 nlist / M / efConstruction 必须重建。 M 16→32 建库耗时 0.930.98s→1.791.88s。在线服务把前者做成可热调参数,后者只能走重建+灰度。
  7. faiss 的索引对象默认非线程安全。 多线程共享同一 index 时 search 可以并发,但改 nprobe 这类参数会影响所有线程;要按线程各建一个索引句柄,或把参数固定后再并发。
  8. IndexScalarQuantizer 不调 train() 直接 add() 会在本机段错误 (进程 access violation 崩溃)。我一开始照抄"8bit 量化不需要训练"去硬设 sq.is_trained = True,直接崩;正确写法是老老实实调 sq.train(x)(QT_8bit 下它是空操作,但必须调)。
  9. 查询向量一定要连续内存。 np.ascontiguousarray 之前先确认 flags['C_CONTIGUOUS'],否则 faiss 可能走拷贝慢路径,延迟读数直接翻倍。
  10. 合成语料只能用来定参数、不能用来报指标。 本文所有召回率都只在同分布的合成向量间可比,换到你的 embedding 模型上绝对值一定变,能带走的是方法而不是数字。

结论

按本次实测,选型顺序应该是:① 先在自己的语料上用 Flat 建 ground truth;② 扫 nprobe / efSearch 画 recall-latency 曲线,用 p99 预算反推参数;③ 只有内存真撑不住才碰 PQ,并确认建库耗时可接受;④ 吞吐不足优先做微批(实测 7.4~17 倍)而不是加核(实测 1.2~1.8 倍);⑤ 把 recall@k 随 k 变化一起测了,再决定 rerank 候选宽度。 5 万条 ×128 维这个量级上,Flat 精确检索 p50 只有 0.328~0.498ms、单并发 1,842~2,728 QPS------如果你的库还在十万级以内,最该先考虑的其实是"要不要用 ANN"。

复现代码(demos/ann_index_recall_lab.py,3 轮 seed=1/2/3,输出留档 ann_index_recall_lab.transcript.txt):语料生成、逐条计时、recall_at 计算、各索引构建与 A/B 两套分布对照都在里面,改 N_DOC、DIM、REGIMES 即可换成你自己的数据规模重跑。

相关推荐
CS创新实验室1 小时前
一棵树怎么做到“自平衡还不太费力“?——红黑树的旋转与染色
数据结构·算法·红黑树·408·计算机考研
白雪讲堂1 小时前
企业如何获得AI长期调用权?《GEO实战:AI时代的增长密码》提出系统经营路径
人工智能·geo·geo从入门到精通
Highcharts.js1 小时前
Highcharts 数据过滤实战指南:从基础实现到性能优化
javascript·性能优化·bi·highcharts·前端可视化·数据过滤·交互图表
码上观世界1 小时前
Agent工具调用实现原理
人工智能·智能体
Logic1011 小时前
C语言/数据结构位运算题解:异或XOR找出被挑选一次的果子编号——只出现一次的数字
c语言·数据结构·算法·数组·位运算·时间复杂度·空间复杂度
AI视频工具指南1 小时前
怎么用 AI 做一两分钟的小故事视频?
人工智能·aigc·音视频
深圳市快瞳科技有限公司1 小时前
面向端侧部署的多帧宠物身份识别:完整性评分机制与工程约束
人工智能·算法·计算机视觉·端侧ai·宠物识别·宠物ai·宠物身份识别
szxinmai主板定制专家1 小时前
软硬协同!ZYNQ 高速多通道同步采集,变电站 / 工厂设备状态监测方案详解
人工智能·深度学习·机器学习·fpga开发·zynq·rk3576
FL16238631291 小时前
古建筑缺陷检测数据集VOC+YOLO格式4018张6类别
人工智能·yolo·机器学习