ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenMed Android Span Parity Protocol:用共享 Fixture 钉死端上 NER 与 Python 参考实现的逐字节一致性

OpenMed Android Span Parity Protocol:用共享 Fixture 钉死端上 NER 与 Python 参考实现的逐字节一致性 OpenMed Android Span Parity Protocol用共享 Fixture 钉死端上 NER 与 Python 参考实现的逐字节一致性【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmedOpenMed 的 Android ONNX Runtime Mobile 导出必须与 Python 参考路径在**分词tokenization与 span 解码span decoding**上保持完全一致这是本地优先local-first医疗 AI 在端上交付临床 NER 与 HIPAA PII 脱敏时的正确性底线。本文围绕 docs/android-parity.md 定义的 Span Parity Protocol完整讲解如何用openmed.onnx.convert导出 Android 配置文件、用generate_parity_fixtures.py生成并提交 parity JSON、理解其严格容差契约与隐私规则并深入到 Python 与 Kotlin 两侧的源码实现说明一条 token ID、字符偏移、标签与 span 边界全部以exact模式对齐的可验证链路。读完本文你将掌握 OpenMed Android 端 NER 一致性验证的完整工作流与底层原理。一、什么是 Android Span Parity ProtocolAndroid 端 OpenMedKit 通过 ONNX Runtime Mobile 执行 token 分类token classification模型完成 PII 识别但端上推理要真正可依赖必须保证同一段文本在 Android 端与 Python 参考实现上产出相同的 tokenizer ID 序列、相同的字符偏移、相同的 argmax 标签和相同的解码 span。这就是 Span Parity Protocol 的使命——它不是一个口号而是一份被提交进仓库的机器可读契约。该契约由固定格式的 parity fixture 钉住android/openmedkit/src/test/resources/parity/android_span_parity.jsonfixture 针对合成临床文本synthetic clinical text记录 Python 参考路径的完整输出Android 侧 JVM 单元测试加载该 JSON逐字段比对端上 tokenizer 与 ONNX session 的推理结果。任何一端的行为漂移例如 tokenizer 版本变化、onnxruntime 算子行为差异、解码器实现不同都会让 parity 测试失败从而在发布前暴露问题。从源码结构看这份契约由三个组件协同维护导出侧openmed/onnx/android_profile.py 定义了 Android 导出 profile 的图契约生成侧scripts/android/generate_parity_fixtures.py 以 Python 参考实现运行推理并产出 fixture消费侧android/openmedkit 模块的 JVM 测试加载 fixture 并断言端上行为一致。二、第一步用 Android Profile 导出 ONNX 模型生成 parity fixture 前必须先导出一个满足 Android ONNX profile 的 token-classification checkpoint。文档给出的命令如下.venv/bin/python -m openmed.onnx.convert \ --model dslim/bert-base-NER \ --output dist/example-android-onnx \ --profile android--profile android会走openmed/onnx/android_profile.py中定义的 profile 约束。从 android_profile.py 源码可以看到该 profile 的具体契约契约项值来源profile 名称androidANDROID_PROFILE_NAMEopenmed/onnx/android_profile.pyONNX 格式标识onnx-androidANDROID_ONNX_FORMAT同上固定 opset18ANDROID_ONNX_OPSET同上必选输入input_ids、attention_maskANDROID_REQUIRED_INPUTS同上可选输入token_type_idsANDROID_OPTIONAL_INPUTS同上唯一输出logitsANDROID_OUTPUTS同上输入 dtypeint64ANDROID_INPUT_DTYPE同上logits dtypefloat32ANDROID_LOGITS_DTYPE同上预期执行提供方NNAPI、XNNPACKANDROID_EXECUTION_PROVIDERS同上profile 验证validate_android_profile会执行几类硬性检查opset 必须等于 18否则抛ValueError张量契约输入必须恰好是input_ids、attention_mask可选再加token_type_ids顺序固定不能出现意外输入输出只能是logits轴语义batch与sequence轴必须是动态dynamic且命名正确labels轴必须是静态static——这样单个产物既能服务于可变 batch 推理又能让后续 ONNX Runtime Mobile 的.ort转换保持图结构可预测算子白名单图内所有算子必须落在_MOBILE_SAFE_OPS集合内如LayerNormalization、Gelu、MatMul、Softmax等不在 NNAPI/XNNPACK 安全集内的算子会产生警告并被记录进AndroidProfileValidation.warnings。profile 还提供export_android_fp16辅助函数将权重转换为 fp16 变体model_fp16.onnx默认保留 IO 类型并在转换后再次验证图。这对应 Android 端轻量化部署的常见诉求。三、第二步生成并提交 parity fixture导出完成后用生成器产出 parity JSON.venv/bin/python scripts/android/generate_parity_fixtures.py \ --export-dir dist/example-android-onnx \ --output android/openmedkit/src/test/resources/parity/android_span_parity.json生成器scripts/android/generate_parity_fixtures.py的执行流程如下build_fixture_payload与_run_reference加载 Android 导出产物读取导出目录中的openmed-onnx.jsonmanifest确认其包含onnx-android格式产物并读取id2label.json标签映射_load_android_export验证 ONNX 图默认调用validate_android_profile校验 Android profile 契约可用--skip-model-validation跳过运行 Python 参考推理用AutoTokenizer.from_pretrained(..., use_fastTrue)对每条合成文本做max_length截断、return_offsets_mappingTrue的分词再用 onnxruntimeCPUExecutionProvider跑 session取出logits输出确定性解码对每个 token 取 logits 行的argmax并列时取最小 label id_argmax_lowest_id计算 softmax 分数得到 token 记录合并 span按 BIOES 标签流式合并 token 为 span_decode_spans对 span 头尾做空白裁剪标注canonical_label、policy_label、entity_type、text_hash等字段隐私校验与落盘validate_fixture_payload校验结构与隐私约束write_fixture以sort_keysTrue的确定性 JSON 写出。CLI 参数汇总main参数作用默认值--export-dir必填openmed.onnx.convert --profile android产出的目录无--output输出的 fixture JSON 路径android/openmedkit/src/test/resources/parity/android_span_parity.json--cases可选 JSON 列表[{id, text}]自定义合成用例内置DEFAULT_SYNTHETIC_CASES--skip-model-validation跳过 Android profile 图验证直接推理False仓库已提交的 fixtureandroid_span_parity.json内置两条合成用例synth-note-001SYNTH_PATIENT_ALPHA visited SYNTH_CLINIC_BETA on SYNTH_DATE_2099Q4D18.预期产出PERSONDIRECT_IDENTIFIER、ORGANIZATIONQUASI_IDENTIFIER、DATEQUASI_IDENTIFIER三个 spansynth-note-002Route results to SYNTH_PERSON_BRAVO through SYNTH_PHONE_TOKEN_42.预期产出PERSON、PHONEDIRECT_IDENTIFIER两个 span。fixture 头部记录了模型元数据source_model_id、model_path、label_map_path、tokenizer_path、max_sequence_length以及生成器与文档的相对路径便于溯源。四、Fixture 契约严格到零容忍的字段对比Android parity 测试必须逐字段比对以下内容来自 docs/android-parity.mdcases[].text与合成输入文本完全一致cases[].tokens[].idtokenizer ID 序列完全一致cases[].tokens[].offset[start, end)字符偏移完全一致cases[].spans[].canonical_label规范化标签完全一致cases[].spans[].start/cases[].spans[].endspan 边界完全一致。提交进 fixture 的容差契约与仓库中android_span_parity.json的contract字段逐字一致{ token_ids: exact, char_offsets: exact, span_labels: exact, span_boundaries: { mode: exact, tolerance_chars: 0 }, logit_ties: lowest_label_id }契约的语义非常明确零字符容差tolerance_chars: 0span_boundaries.mode为exact。如果 Android 解码器产出了相同标签但边界偏移了一个字符parity 测试必须失败确定性的并列处理logit_ties: lowest_label_id即 logits 出现并列时统一取 label id 最小的那个。Python 侧_argmax_lowest_id的实现是max(range(len(row)), keylambda index: (float(row[index]), -index))Android 侧 OnnxTokenClassifier.kt 的decodeToken使用logit maxLogit的严格大于比较同样保证首个最大值的索引即最小 label id。两侧行为在此处被显式钉死。文档特别强调边界容差被显式记录在 fixture 中是为了让未来的任何变更都以可评审的方式出现而不是在测试中静默漂移。若未来确实需要放宽边界要求必须显式修改契约并经过评审而不是让测试悄悄放过偏差。五、隐私规则合成输入与表面文本隔离Parity 输入必须保持合成syntheticfixture 的隐私约束由 Python 侧validate_fixture_payload强制generate_parity_fixtures.py每个 case 必须标记synthetic: true与phi_free: true文本必须包含SYNTH_占位符例如SYNTH_PATIENT_ALPHA拒绝纯自然文本文本命中以下 PHI 形态即被拒绝SSN 形态\d{3}-\d{2}-\d{4}北美电话号码形态邮箱形态常见的John Doe/Jane Doe姓名形态。此外还有一层更精细的设计span 记录不包含表面文本surface text。_validate_spans明确拒绝 span 中出现text、word、surface等字段。Android 测试如果需要 span 的原文表面应当用预期偏移去切片cases[].text并通过text_hash做确定性完整性校验——fixture 中每个 span 都带hmac-sha256:...形式的text_hash校验函数会用hmac_text_hash(text[start:end], PARITY_HASH_SECRET)重新计算并比对。这样即使 fixture 被读取端误用也不会在产物中直接沉淀真实患者文本。六、Android 端资源布局与 JVM 测试消费方式fixture 作为 Android 模块的普通测试资源存放android/openmedkit/src/test/resources/parity/android_span_parity.json资源是纯 JSON没有 Android 专属的二进制编码因此 JVM 单元测试可以直接用类加载器读取javaClass.classLoader.getResourceAsStream(...)、解析cases、对每条text运行 Android tokenizer 与 ONNX session再与契约字段逐一比对。从 android/openmedkit/src/test/kotlin/com/openmed/openmedkit/parity 的测试结构看parity 验证被拆成了几个互补的层面SpanEquivalenceTest读取synthetic_clinical_note.txt与expected_spans.json用OpenMed().extractPII(note)跑端上推理断言每个实体的label、start、end与期望完全一致并验证entity.text与按偏移切片的原文一致OffsetContractParityTestRobolectricConfig(sdk [33])重点验证Unicode scalar 偏移契约——Android 侧AndroidIcuTextSegmentationAdapter与 JVM 侧Icu4jTextSegmentationAdapter在 emoji、CJK 扩展 B 区等增补平面字符上的偏移换算完全一致拒绝代理对surrogate pair被拆开并覆盖实体对齐、解码、redaction 与 policy 全链路ApiParityTest验证 Android API 面analyzeText、extractPII、extractPIIChunkedEntityPrediction的label/text/confidence/start/end字段等与 Swift 端公开 API 对齐。Android 端解码器如何呼应契约端上推理入口在 OnnxTokenClassifier.kt输入张量名与 profile 契约一致input_ids、attention_mask若 session 存在token_type_ids则补零张量输出读取logitsdecodeToken使用与 Python 等价的确定性 argmax严格大于并对 logits 做 softmax减最大值后指数归一得到分数模型与id2label.json均可从文件、字节数组加载支持intraOpThreadCount配置与Closeable资源释放。token 预测合并为实体 span 的逻辑在 TokenClassificationDecoder.kt支持 BIO/BIOES 标签流B开始、I延续、E结束、S单实体、O非实体repairEntitySpans负责把 span 对齐到字形边界grapheme boundary、扩展词形字符并裁剪首尾空白。多 token 置信度聚合策略由 AggregationStrategy.kt 提供FIRST/AVERAGE/MAX三种选择。这些行为与 Python 参考实现_decode_tokens/_decode_spans在语义上对齐再被 fixture 的exact契约统一检验。七、把 Parity 纳入端上发布流程综合来看Android span parity 的完整工作流是用openmed.onnx.convert --profile android导出满足 opset 18、动态 batch/sequence 轴、白名单算子的 token-classification ONNX 模型必要时用export_android_fp16生成 fp16 变体用generate_parity_fixtures.py在合成文本上运行 Python 参考推理生成确定性 JSON 并提交到android/openmedkit/src/test/resources/parity/Android 端 JVM/Robolectric 测试加载 fixture逐字段断言 token ID、字符偏移、canonical 标签与 span 边界为exact任何一端行为变化tokenizer、onnxruntime、解码器都会在 CI 中被零容差的契约拦截需要显式评审后才能变更契约。这套协议与仓库中其他 Android 相关文档如 docs/android-integration.md、docs/export-onnx-android.md、docs/android-tokenization.md、docs/android-quickstart.md共同构成 OpenMed 端上能力的质量保障体系先有导出侧的 profile 约束再有参考侧的 fixture 钉桩最后有消费侧的逐字节断言——三层闭环确保本地优先的临床 NER 与 PII 脱敏在 Android 端与 Python 参考实现之间不产生静默漂移。【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表