简介:EQTransformer 是一套面向地震学与深度学习研究者的开源 Python 工具包,基于带注意力机制的深度神经网络完成地震信号检测与 P、S 相位拾取,适合需要处理连续地震波形、开展震相识别和模型实验的科研与工程人员。压缩包共 92 个文件,约 31.34MB,包含 Python 源码、说明文档、示例 Notebook、模型权重及测试数据等,覆盖数据下载、预处理、检测拾取、模型构建与测试、简单相位关联等完整流程。项目采用分层网络结构,并使用全球地震数据训练,可同时输出检测概率、到时结果与模型不确定性,便于评估预测可靠性。配套示例涵盖训练、检测、可视化、下载和关联等场景,模型文件与样例数据可直接用于复现和二次开发。已有 1176 人学习下载,适合作为地震信号智能处理入门的实践资料。
1. 波形上那 0.1 秒的偏差,是后面所有定位结果的地基
某个微震监测项目里,值班的人对着一天 400 多条触发记录,一条条在屏幕上点 P 波初动。点到第 80 条时手一抖,到时差 0.1 s,后面定位残差立刻从 0.2 km 涨到 0.8 km,震源深度直接飘出去两公里。EQTransformer 就是冲这个场景来的 Python 软件包:一个多任务深度学习网络,同时对三分量连续波形做事件检测、P 波拾取和 S 波拾取,把「先检测再拾取」两套流程压进一次前向推理。它骨子里是 Transformer 自注意力编码器加 LSTM 解码器,配上三个并行输出头,deep-learning 那套东西该有的都有,但工程上真正省事的是它把三个任务塞进一个模型共享特征。适合已经在跑台网日常处理、想把手动拾取换掉一半、又不想自己从零训模型的人;也适合做地震信号处理方向、需要一个能直接推理的 baseline 的学生。
2. 骨架拆解:Transformer 编码器为什么非要配一个 LSTM 解码
动手之前先把网络每一层在干什么搞清楚,不然调参就是玄学。EQTransformer 的结构不算复杂,但每一步的设计都有明确的工程理由:卷积负责把 6000 个采样点的原始波形压成短序列,自注意力负责在整段窗口上建立全局依赖,LSTM 负责把注意力输出的抖动磨平,最后三个 sigmoid 头分别给出检测、P、S 的逐点概率。哪一层出问题,表现出来的现象都不一样。
2.1 输入张量与滑窗:6000 点、三分量、按通道归一化
模型的输入是一个形状为(6000, 3)的二维数组,6000 对应 60 s 长度、采样率 100 Hz,3 对应 Z/N/E 三个分量。这几个数字不是随便定的:窗口太短,S 波还没到窗口就结束了;窗口太长,一个窗口里塞进两个事件,检测头会输出两段概率峰,后处理反而更难切。60 s 是兼顾了近震和区域震的一个折中,绝大多数项目直接沿用。
预处理这一步看起来琐碎,但它是翻车率最高的环节。下面这段是每次接新台网数据我都会先跑一遍的函数,把原始 miniseed 变成模型能吃的数组:
import numpy as np from obspy import read def build_window(path, target_sr=100.0, win=6000, fmin=1.0, fmax=45.0): st = read(path) # 只保留三分量,多余通道直接丢掉,避免形状对不上 st = st.select(component="Z") + st.select(component="N") + st.select(component="E") st.detrend("demean") # 去直流,消除仪器零点漂移 st.detrend("linear") # 去线性趋势,压住长周期基线游走 st.taper(max_percentage=0.05) # 两端 5% 余弦窗,防止切窗处产生高频振铃 st.filter("bandpass", freqmin=fmin, freqmax=fmax, corners=4, zerophase=True) st.resample(target_sr) # 统一到模型训练采样率,不做这步基本必错 st.merge(method=1, fill_value=0) # 三分量对齐到同一时间轴,缺数据补零 data = np.stack([st.select(component=c)[0].data for c in "ZNE"], axis=-1) data = data.astype(np.float32) # 逐通道做 max-abs 归一化,三通道各管各的,不能让 Z 的幅值压死 N/E scale = np.max(np.abs(data), axis=0, keepdims=True) scale[scale == 0] = 1.0 data = data / scale # 不足一个窗口就补零,超长就先切第一个窗口,连续数据走下面的批处理 if data.shape[0] < win: data = np.pad(data, ((0, win - data.shape[0]), (0, 0))) return data[:win]这段代码里有两个参数值得单独说。fmin/fmax决定带通范围,默认 1–45 Hz 是按 100 Hz 采样率的奈奎斯特上限留了余量;如果你处理的是低频远震或者低频背景噪声占主导的台站,把fmin降到 0.5 Hz 往往能明显改善 S 波拾取,代价是长周期噪声也被放大,检测头会变得话多。zerophase=True用零相位滤波,保证滤波不引入到时偏移——这一点在做拾取的时候是硬要求,普通因果滤波会把 P 波整体往后拖十几到几十毫秒,你的拾取结果就系统性偏晚。
提示:归一化按通道做,不要按整个数组做全局最大值归一化。全局归一化在某个分量噪声很大时会把另外两个分量压到接近零,重建出来的概率曲线会整段塌掉。
2.2 自注意力在波形上抓的是什么
一维卷积的感受野是局部的,堆到十几层也只能覆盖几百个采样点。P 波和 S 波的到时差在近震里可能只有一两秒,在远震里能到几十秒,卷积想覆盖这个跨度要么堆很深的层,要么用很大的 dilation,前者参数爆炸,后者调参麻烦。自注意力的做法是让序列上任意两个位置直接算相似度,一步就建立起全局感受野,这是 transformer 在波形任务上最实打实的收益。
| 结构 | 感受野 | 参数效率 | 对长周期到时差的适应性 |
|---|---|---|---|
| 纯 1D 卷积 | 局部,随层数线性增长 | 高 | 需要很深的网络或大 dilation |
| 卷积 + 自注意力 | 全局,一层到位 | 中 | 好,任意两点直接建模 |
| 纯自注意力 | 全局 | 低,序列长时显存吃紧 | 好,但小样本容易过拟合 |
但自注意力也有它的问题:它输出的序列是「每个位置都综合了全局信息」的结果,平滑性反而变差,孤立噪点上容易冒出虚假的概率峰。这就是后面还要接 LSTM 的原因,属于典型的工程折中。
2.3 多任务头与损失权重:检测、P、S 三路怎么不打架
三个输出头分别对应 detection、P、S 三条概率曲线,训练时是三路加权交叉熵求和。这就是 multi-task-learning 在拾取任务上的落地方式:共享同一套编码特征,头部各自独立。它的好处是检测任务学到的事件上下文能反过来帮助 P/S 判别——一个位置该不该有 P 波,跟它周围有没有事件是强相关的。
代价是任务之间会互相拉扯。检测任务的样本极度不平衡(整段波形里事件只占几个百分点),P/S 任务在事件段内又有自己的不平衡,三路权重配不好,就会出现「检测很准但 P 波全线偏移」或者「P/S 很漂亮但一堆误检」。预训练模型自带的权重分配(检测占比很小,P/S 占大头)就是这么调出来的,自己微调时不要一上来就改成均等权重,先沿用再小步动。
| 输出头 | 物理含义 | 常见阈值起点 | 调高后的表现 |
|---|---|---|---|
| detection | 该时刻处于事件内的概率 | 0.3 | 漏检增加,误检减少 |
| P | 该时刻是 P 波到时的概率 | 0.3 | P 拾取数量下降,精度上升 |
| S | 该时刻是 S 波到时的概率 | 0.3 | 同上,S 通常比 P 更敏感 |
2.4 一次前向的形状变化
把整个流程的形状变化列出来,出问题的时候对着表就能定位是哪一层对不上:
| 阶段 | 形状(单样本) | 关键说明 |
|---|---|---|
| 输入 | (6000, 3) | 60 s @ 100 Hz,Z/N/E |
| 卷积下采样 | (约 375, C) | 约 16 倍下采样,通道逐层加倍 |
| 自注意力编码 | (约 375, C) | 序列长度不变,只换特征表达 |
| BiLSTM 解码 | (约 375, H) | 前后向拼接,做时序平滑 |
| 输出头上采样 | 3 × (6000,) | 回到原始采样率,逐点概率 |
形状对不上是最常见的报错来源,尤其是自己改窗口长度之后——模型的卷积下采样倍数是固定的,输入长度必须是倍数的整数倍,6000 是验证过的,改成 5000 大概率在拼接处炸。
3. 装起来跑通第一条记录:从依赖到三张概率曲线
原理清楚了,接下来是把它跑起来。这一章的目标很朴素:一条三分量记录进去,三条概率曲线和一份到时结果出来。别急着上连续数据,先用单条记录把链路走通,能省掉大量排查时间。
3.1 环境与依赖
Python 3.8 到 3.10 之间最稳,再新一点也没关系,但 tensorflow 的版本要和 Python 对上,这是最容易卡住的一步。核心依赖就三样:深度学习框架、波形读写库、数值计算库。
python -m venv eqt-env source eqt-env/bin/activate # Windows 下用 eqt-env\Scripts\activate pip install --upgrade pip pip install tensorflow==2.11.0 # 与 Python 版本强相关,装完先 import 验证 pip install obspy h5py numpy scipy pandas matplotlib # 从仓库源码安装,不要只装 PyPI 上的旧包,推理脚本在源码里 git clone <仓库地址> EQTransformer cd EQTransformer && pip install -e .装完先验一下,别等到跑数据才发现框架没起来:
import tensorflow as tf import obspy print("tf:", tf.__version__) print("gpu:", tf.config.list_physical_devices("GPU")) print("obspy:", obspy.__version__)GPU 那一行如果返回空列表,说明走的是 CPU。单条 60 s 记录在 CPU 上大概几秒,能忍;连续数据几十万条窗口就是几小时和几分钟的差距,能上 GPU 就上。
注意:tensorflow 和 numpy 的版本互相牵制,numpy 2.x 会导致部分旧版框架直接 import 失败。装完框架再装 numpy,让 pip 自己解依赖,别手动指定过新的 numpy。
3.2 权重文件的放置与校验
预训练权重是一个 h5 文件,几个 GB 量级,下载下来之后建议单独放一个目录,路径写进配置,不要和代码混在一起。校验这件事必须做:权重文件下载中断是常事,文件大小对但内容截断的情况我遇到过不止一次,加载时报的错五花八门,最后才发现是文件本身不完整。
mkdir -p models # 权重放到 models/ 下,文件名保持原样,很多脚本靠默认名找 ls -lh models/*.h5 # 用 h5py 打开一次,能列出顶层 key 就说明文件结构完整 python -c "import h5py; f=h5py.File('models/EqT_model.h5','r'); print(list(f.keys())[:5]); f.close()"如果这一步报OSError: Unable to open file,八成是文件没下完或者下载过程中被改写,重新下一遍比折腾修复快得多。
3.3 单条记录的端到端调用
仓库里提供了推理入口函数,做单条记录用test_predictor,做连续波形用mseed_predictor,两者参数高度重叠。下面这段是我一般会先跑的验证脚本,把输入目录、权重、输出目录三个路径确定下来,其余先用默认值:
from EQTransformer.core.predictor import test_predictor test_predictor( input_dir="data/single", # 里面放 .h5 格式的波形文件,每个文件一条记录 input_model="models/EqT_model.h5", output_dir="out/single", detection_threshold=0.3, # 先沿用默认,跑通再调 P_threshold=0.3, S_threshold=0.3, batch_size=1, number_of_plots=1, # 单条验证时打开,肉眼看曲线最直接 plot_mode="time", estimate_uncertainty=False, # 单条先关掉,开了会慢好几倍 )参数里有几个需要解释。input_dir要求的是 h5 格式,如果你手上是 miniseed,需要先转一道,转换的时候保持三分量、保持 100 Hz 采样率、保持数组形状为(6000, 3),字段名按仓库示例文件来,不要自己起名字。detection_threshold这一组三个阈值在单条验证阶段不用纠结,统一 0.3 就行,真正调参放在连续数据阶段做。number_of_plots=1会输出一张波形加三条概率曲线的叠图,这张图的价值比任何数值指标都高——曲线整体贴不贴波形、P/S 峰是不是落在合理位置、有没有出现平台状的饱和输出,一眼就能看出来。
3.4 输出目录里每个文件是什么
跑完之后out/single下会多出几类文件,第一次用容易懵,列个表对一下:
| 文件/目录 | 内容 | 用途 |
|---|---|---|
*.h5或结果表 | 每条记录的到时与峰值 | 后续做关联、定位的输入 |
| 概率曲线数据 | 检测、P、S 三列逐点概率 | 自己重做后处理、换阈值重扫 |
| 图像目录 | 波形与概率曲线叠图 | 人工抽查,快速判断质量 |
| 日志 | 每条记录的处理状态 | 排查哪一条挂了、挂在哪一步 |
单条跑通之后,先别急着批量。挑三五条不同震中距、不同信噪比的记录各跑一遍,把叠图摆在一起看。如果远震的 S 波峰总是偏晚,说明带通下限设高了;如果近震的检测曲线在事件尾部拖出一条长尾,说明窗口末尾可能有能量泄漏,检查 taper。这些问题在单条阶段发现,改一个参数就行;等跑完十万条再发现,重跑的时间成本就是另一回事了。
4. 连续波形批处理:滑窗步长、阈值和事件目录
单条记录是理想情况,真实的台网数据是一天 24 小时的连续流,几百 MB 到几个 GB。这一步要做的事情是把连续流切成一个个重叠的窗口,逐窗推理,再把重叠区域的结果拼回去,最后从概率曲线里提取到时、配对成事件。
4.1 滑窗步长与重叠:切太粗会丢事件
窗口 6000 点、步长选多少,是个直接影响召回率的参数。步长等于窗口长度就是不重叠,边界附近的事件会被切成两半,检测头两边各给一半概率,两半都过不了阈值。常见做法是步长取窗口长度的 1/10 到 1/6,也就是 600 到 1000 点,重叠部分足够让一个完整事件落在某个窗口的中央。
代价是计算量成倍增加。步长 600 的话,一段 1 小时的 100 Hz 数据要跑 3600×100/600 ≈ 600 个窗口,重叠带来的冗余算力是实打实的。折中的办法是先粗后细:用大步长(比如 3000 点)做一遍初筛,只在检测概率超过阈值的时间段附近用小步长重跑一遍。这套两级策略我在数据量大的项目里一直在用,能省掉一半以上的推理时间。
import numpy as np from eqcct import chunk_stream # 示意:按步长切片,实际按仓库提供的预处理工具来 def sliding_chunks(data, win=6000, step=600): """data 形状 (N, 3),返回 [(起始样点, 窗口数组), ...]""" chunks = [] for start in range(0, max(1, data.shape[0] - win + 1), step): seg = data[start:start + win] if seg.shape[0] < win: seg = np.pad(seg, ((0, win - seg.shape[0]), (0, 0))) chunks.append((start, seg)) return chunks # 用法:先切,再逐块喂给推理函数,结果按 start 记下偏移量这里最容易被忽略的是偏移量。窗口是从连续数据里切出来的,模型输出的概率曲线是相对窗口起点的,拼回绝对时间轴时必须把start加回去,否则结果会整体错位一个步长,而且错得很有规律——每隔一个步长就跳一下,看起来像拾取精度很差,实际是索引没对齐。
4.2 三个阈值的物理含义与调参顺序
三个阈值不建议一起动,会互相干扰。我一般的顺序是:
第一步,固定 P/S 阈值在 0.3,只调detection_threshold。这个参数控制「什么样的时间段算有事件」,它的作用是门控——检测概率低于阈值的时间段,后面 P/S 峰一律不看。先保证召回:把阈值降到 0.15 左右看能多捞出多少事件,再逐步往上抬,找到召回率开始明显掉的拐点。
第二步,固定检测阈值,调P_threshold。P 波在垂直分量上能量突出,通常比 S 好拾,阈值可以设得比 S 略高,比如 P 用 0.35、S 用 0.3。
第三步,调S_threshold。S 波在水乎分量上,受场地放大和横波分裂影响,概率曲线往往更毛糙,阈值不宜设高,宁可靠后面的相位关联去剔除离群值。
| 参数 | 控制对象 | 调高的后果 | 建议动作 |
|---|---|---|---|
| detection_threshold | 事件是否存在 | 漏检上升 | 优先保召回,最后再收紧 |
| P_threshold | P 到时点 | P 数量减少、精度上升 | 比 S 略高 |
| S_threshold | S 到时点 | S 数量减少 | 偏低,交给关联器过滤 |
提示:阈值不是越高越准。地震拾取的下游是定位,定位对漏检的容忍度远低于对误检的容忍度——多一个假 P 波,关联阶段配不出对,整条事件就废了;少一个真 P 波,还有机会靠其他台站补上。所以整体策略偏向「宁可多报」。
4.3 从概率峰到到时目录:相位关联那一步
模型输出的是三条逐点概率曲线,不是「事件列表」。中间还差一步:在 P 概率曲线上找峰、在 S 曲线上找峰、按时间顺序把同一事件的 P 和 S 配成对,再跨台站把同一个事件的不同台站记录聚到一起。这一步常见做法是引入一个相位关联器,把各台站的 P/S 到时当成候选点集,用走时约束或者聚类的方式配对。
单台内部的 P-S 配对相对简单,用 S 减 P 的时间差做约束就行:
import numpy as np from scipy.signal import find_peaks def pick_phases(p_prob, s_prob, det_prob, sr=100.0, p_thr=0.35, s_thr=0.30, det_thr=0.30, min_gap=0.5, sp_range=(0.5, 25.0)): """从三条概率曲线提取 P/S 到时,返回 [(p_time, s_time), ...]""" # 只在检测概率过阈值的区间里找峰,这是门控,能砍掉大半虚假峰 active = det_prob > det_thr p_idx, _ = find_peaks(p_prob, height=p_thr, distance=int(min_gap * sr)) s_idx, _ = find_peaks(s_prob, height=s_thr, distance=int(min_gap * sr)) p_idx = [i for i in p_idx if active[i]] s_idx = [i for i in s_idx if active[i]] pairs = [] for pi in p_idx: # 对每个 P,找它后面时间差落在合理区间内的最近 S cands = [si for si in s_idx if sp_range[0] <= (si - pi) / sr <= sp_range[1]] if cands: pairs.append((pi / sr, min(cands) / sr)) return pairsmin_gap控制同一个相位两次拾取之间的最小间隔,单位秒,0.5 s 是经验值,密集余震序列里可以降到 0.2 s。sp_range是 S 减 P 的合理时间差范围,这个区间要根据你台网到震源区的距离分布来定,设太窄会漏掉远一点的 S,设太宽会把下一个事件的 P 错配成 S。这一段逻辑看起来朴素,但它决定了后面事件目录的质量,血泪经验是:先别急着上复杂的关联算法,用最朴素的规则跑一版,看看错在哪,再决定加什么约束。
4.4 拿人工目录验一遍:容差匹配脚本
参数调完必须验证,否则你不知道自己是在优化还是在瞎调。验证的做法是拿一份人工拾取目录,按容差匹配,算查全率和查准率。
def match_events(pred, true, tol=0.5): """pred/true: 按时间排序的到时列表(秒)。贪心一对一匹配。""" pred, true = sorted(pred), sorted(true) used, hits = set(), 0 for t in true: best, best_d = None, tol for i, p in enumerate(pred): if i in used: continue d = abs(p - t) if d <= best_d: best, best_d = i, d if best is not None: used.add(best) hits += 1 precision = hits / max(len(pred), 1) recall = hits / max(len(true), 1) return precision, recalltol的取值直接决定结论。0.5 s 是文献里常见的宽松容差,0.1 s 是严格容差。同一份结果,0.5 s 下查全率 0.9、0.1 s 下可能只有 0.4,这很正常,别拿宽松容差下的好看数字去汇报。我一般两个容差都算,宽松容差看「有没有漏事件」,严格容差看「拾取精度够不够做定位」。
5. 避坑记录:五个把结果跑歪的真实原因
模型本身很稳,出问题的九成在数据侧和后处理侧。下面五条是踩过的坑,按「现象 → 原因 → 解决」整理,遇到类似症状可以直接对号。
5.1 概率曲线整体平移几十个采样点
现象:所有事件的 P 到时都系统性偏晚,偏差量还基本一致,人工对照能明显看出来。
原因:预处理用了因果滤波。带通滤波如果是因果实现,会引入与频率相关的群延迟,低频成分被拖得更久,而 P 波初动的能量恰好集中在中低频。
解决:滤波器强制走零相位(zerophase=True),或者先正向滤一遍再反向滤一遍。改完之后偏差量级会掉到采样点以内。这个问题在数据处理流程里藏得很深,因为波形看起来「很正常」,只有跟人工目录比才暴露。
5.2 检测概率全线接近 1,或者全线接近 0
现象:不管什么数据,输出的检测曲线都贴着上沿或者下沿,没有区分度。
原因:归一化方式跟训练时不一致。最常见的是把三分量拉平做一个全局最大值归一化,或者归一化之前没去均值,导致直流分量把所有幅值压扁。
解决:严格按训练时的口径来,逐通道 max-abs 归一化,归一化之前做去均值和去趋势。如果确认归一化没问题,再检查采样率——采样率不对会改变波形的频率结构,模型看到的东西和它训练时见过的完全不是一回事,输出自然是没有区分度的饱和值。
5.3 分量顺序搞反,P 波变得很难拾
现象:垂直分量上拾不到 P,反而在水平分量上冒出一堆莫名其妙的峰。
原因:不同来源的数据通道顺序不一样,有的按 ZNE 排,有的按 ENZ 排,还有的干脆是三个无标记通道。模型是按固定顺序训练的,顺序错了等于把垂直分量喂给了水平分量的通道。
解决:读数据之后先打印通道名确认,明确按 Z/N/E 重排再堆叠。对于没有通道标记的数据,用能量特征判断:P 波在垂直分量上初动最清楚,S 波在水平分量上能量最强,拿几条清晰事件对一下就能定下来。
5.4 事件被窗口边界切断,两个半截都过不了阈值
现象:某些事件在人工目录里明明有,模型就是不给,而且这些事件的时间点看起来没什么规律。
原因:滑窗步长等于或者接近窗口长度,事件正好落在两个窗口的交界处,前半段在一个窗口的末尾、后半段在下一个窗口的开头,两边都只有半截波形,检测概率被稀释到阈值以下。
解决:把步长降到窗口长度的 1/10 到 1/6,保证任何事件都能完整落在某个窗口内部。同时检查切窗时是否做了边界填充——窗口末尾不足长度的部分如果没有补零,数组形状会不对,某些实现里会静默截断。
5.5 批量跑一半卡死或者显存爆掉
现象:前几百条正常,跑到某个位置进程挂掉,日志里是显存分配失败或者内存溢出。
原因:不同记录的长度不一致,批量拼接时按最长的那条分配内存,遇到一条特别长的记录整个 batch 的显存需求就跳一个量级。另外长时间运行不释放中间张量,累积下来也会出问题。
解决:批大小设小一点(1 到 4),并且在推理循环里显式清理缓存;对超长记录单独走一条路径,先切段再推理;用生成器逐条喂数据,不要一次性把所有记录读进内存。这套改法看起来笨,但稳定性比追求吞吐量重要得多。
6. 把自家台网数据喂进去:微调和一条我改不掉的习惯
预训练权重是在公开数据集上训的,台站条件、仪器响应、噪声环境跟你的台网差得远的时候,直接推理的效果会打折扣。这时候需要微调,但微调这件事有它自己的坑。
我的做法是冻结编码器,只训解码器和三个输出头。理由是编码器学的是「波形到特征」的通用映射,这部分跨台网迁移性最好;解码器和头部学的是「特征到到时」的映射,跟具体台网的噪声统计、仪器频响关系更紧,更需要适配。冻结编码器还有个额外好处:训练样本需求直接降一个量级,几百条人工拾取的记录就能训出明显改善。学习率设在 1e-4 到 1e-5 之间,冻结部分不掉学习率,只训的部分用余弦退火,训练轮数控制在 20 轮以内,再多就开始过拟合。
验证集的划分是另一件必须较真的事。按波形片段随机划分是最省事也最容易自欺欺人的做法——同一个事件的波形会被切进多个窗口,随机划分之后训练集和验证集里出现的是同一个事件的不同片段,验证指标虚高得离谱,实际部署时性能断崖式下跌。正确做法是按事件划分,同一事件的所有台站、所有窗口必须整体落在训练集或者验证集里,一条都不能跨。这个细节在论文里常常一笔带过,但在工程上它决定了你报出去的数字可不可信。
还有一个现成的能力值得用起来:模型支持多次采样估计不确定度(把estimate_uncertainty打开,指定采样次数),本质上是推理时开 dropout 做多次前向,看多次结果的离散程度。离散度大的拾取点,基本就是低信噪比或者波形畸变的位置,把这些点单独挑出来人工复核,比全量复核省力得多,也比一刀切提高阈值更精细。
从那以后,我每次接一个新台网的数据,都强制先走一遍「单条记录 + 叠图目视 + 三个容差下的匹配统计」这套流程,哪怕数据看着再干净也不跳过。因为跳过的那次,往往就是要返工的那次。这套流程不复杂,但它把「模型行不行」这个问题从感觉变成了数字。希望帮到你。
本文还有配套的精品资源,点击获取