ARTICLE DETAIL

资讯详情

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

大模型如何高效清洗与标注地质勘探语料?实践与避坑指南

大模型如何高效清洗与标注地质勘探语料?实践与避坑指南 简介面向地质勘探研究人员、工程师与技术管理者这份PDF方案系统梳理了AI大模型在勘探语料清洗与标注中的落地路径核心解决非结构化地质数据质量参差、标注口径不一等问题。文件为单个PDF文档压缩包约940KB目前已有79人学习下载适合作为AI地质勘探项目启动时的参考框架。方案从数据源选择学术文章、行业报告、实地记录、清洗原则与去重算法、标注类别定义到模型训练与优化均有分章说明并补充人员培训、质量控制、数据库设计等实施细节。读者可获得一套可复用的项目推进思路包括数据精度筛选、噪声识别、开源标注工具比较、自定义平台开发以及风险管理与实施计划能帮助团队减少试错成本提升勘探智能化水平。1. 地质勘探语料清洗和标注大模型能省多少人力又挖了哪些坑项目上拿到一批老井的录井报告和勘探总结PDF 扫描件居多转出来的文本断行、错字、上下标乱成一团。过去我们靠两三个人对着屏幕人工摘录岩性、井段和层位一个中深井报告就能耗掉一周想用正则又被“灰绿色细砂岩夹薄层泥岩”和“灰绿 细砂 夹泥”这类写法差异击穿。大模型看起来是正解但它做地质勘探语料清洗和标注并不是“传一段文字进去、结构化结果出来”那么简单只要清洗策略、标注 schema、抽检流程中有一环松了标注结果就不可信。这套方案就是用来解决这个问题的先定标签边界再用本地或私有化大模型批量清洗和抽取实体人工只审疑点样本最后用几十条金标把整条链路量一遍。适合给接地质数据处理的算法工程师以及油服、地调侧的数据组看。2. 拆解标注任务先定边界再谈用大模型清洗和标注项目开做之前我倒是不会先拉起推理框架而是先问三个问题这些语料最终要变成什么是给知识图谱提供实体三元组还是给检索库做语义片段库还是给后续监督训练合成训练语料同一份报告三种目标对应的标签体系差别很大。地质勘探语料清洗和标注不是把文本变成一行行干净字而是要先定义“干净”和“标签”到底长什么样。2.1 地质语料和通用文本清洗目标是反着的很多文本清洗 pipeline 是去停用词、标准化大小写、压缩空白、去掉网址和标点。这一套拿来处理录井报告或岩心描述就会出问题。比如“2865.0—2878.0m灰绿色细砂岩油迹”若把数字、单位、标点和颜色词都去掉剩下的只剩“砂岩”后面要做深度区间的统计就无从谈起。地质语料里的价值很多是藏在数字和修饰词上的深度区间、颜色深浅、粒度、含油级别它们不是噪音是要被保留的字段。常见的地质语料来源有三种扫描 PDF 转出来的 OCR 文本、现场手工日报的转录文本、测井解释系统导出的固定栏位文本。三者的问题不一样OCR 会产生乱字和粘连手工日报会把“灰绿色”写成“灰绿”甚至断成两行导出文本会表格栏位拆成碎片。LLM 清洗要做的是“修复上下文并补全断句”但不能新增地质结论。所以我在写清洗提示词时“不得补全原文没有的结论”这句话永远放在第一条。2.2 把任务拆成三类清洗、字段标注、关系标注我不建议把“清洗标注”一次完成。虽然大模型有能力在一条提示词里同时做修复和抽取但这种一步到位的写法错误被封闭在一步里出了问题你根本分不清是清洗坏了还是标注错了。分两个阶段做更稳。清洗阶段把原始文本转成“一条条可读记录”输出还是文本但断行融合、明显乱字被修正。字段标注阶段在清洗后的文本上抽取实体输出结构化 JSON只做“哪个片段是岩性、哪个片段是深度区间”不做推理。关系标注阶段再把实体之间的关系补上比如“这一岩性属于哪一个深度区间”“这个深度区间归到哪个层位”。如果项目时间紧我强烈建议先只做字段标注。关系标注可以晚一个月等字段数据聚齐了再跑因为模型从字段中推断关系准确率比直接抽取差得多但跑起来却更费人工。还有一个容易忽略的点标签集里要有“否定/不确定”的旁路。比如“未见油气显示”和“油气显示不明”含义不同。如果模型只输出油气显示实体而不带否定关系后面做统计会把“未显示”也算成“正常显示”。地质语料的标注不只是抽名词还要把“有没有”和“有没有把握”分开。2.3 定标注方案可直接套用的实体与标签清单这里放一份我常用的基础 schema适合岩屑录井、岩心描述、钻井地质总结报告。实体类别控制在 8 个以内再多模型输出开始变乱。实体类别说明和示例是否建议必选深度区间“2865.0—2878.0m”“井段 2865-2878m”必选岩性“细砂岩”“灰岩”“泥岩夹粉砂岩”必选颜色与结构修饰“灰绿色”“含钙质”建议可选地层单位“沙河街组”“第四系”“沙四段”必选油气显示“油迹”“荧光”“油斑”必选构造“背斜”“断层”“不整合”按下游需求化石/古生物“介形虫”“孢粉”按下游需求含油性描述“油味”“油浸”可选注意不要把“颜色与结构修饰”合并到岩性。一旦合并模型输出时往往只挑一个代表词“灰绿色”这个信息就丢了。分开之后后处理可以单独校验颜色词是否保留也更容易做人工审核。同时要准备“规范词典”。地质地层名称没有绝对统一我通常要求提供一本“地层名称别名表”至少包含“曾用名—规范名”的映射。比如“沙河街组 沙河街 沙四段 沙河街4段”归一到“沙河街组”。这个表在用 LLM 标注之后再跑一次字符串归一比让模型在生成时就“想当然”归一靠谱得多。实测中让模型自己归一同一个批次里能出现三种写法。2.4 语料准入与分批策略拿到原始语料后先做一个准入脚本。脚本不复杂统计每个文件的段落数、平均行长度、乱码比例、是否含深度模式比如\d{3,5}\.*\d*[-—~]\d{3,5}。不含深度模式的段落先剔出一般不进入标注主流程因为地质记录缺少深度锚点后续抽取出的实体很难挂接。这一条看起来严格但非常能省时间。批次按“井”分也可以按“报告类型”分。同一口井的清洗结果必须放在同一批次里便于人工复审时对照井史上下文。如果一口井的文本太长再按“井段”拆分。常见做法是把一个井段例如“2865.0—2878.0m”作为最小处理单元前后各保留 50 到 100 字相邻井段作为上下文。切块策略会在下一章代码里看到。分块之后的清洗结果我一般会存成一个半结构化的中间格式每条记录包含source_file、depth_start、depth_end、raw_text、cleaned_text。为什么要保留 raw_text因为后面要做错误分析和人工回退如果只有清洗后的文本纠纷就说不清了。中间数据用 JSONL 存就行按井号做文件名每一行一条记录。以上做完才真正到调大模型的阶段。实际经验是这四步比模型选型更决定项目成败处理得越细后面标注 F1 越高。3. 用大模型做地质文本清洗提示词模板与最小批处理脚本正则表达式能处理“灰绿色细砂岩”但遇到 OCR 后“灰绿 色 细 砂 岩”就无能为力了。大模型的价值在于它能复用上下文把断字接回来同时保留专业词序。但这一步也是最容易“前言不搭后语”的地方所以提示词要写得像现场作业规范而不是像聊天。3.1 为什么是 LLM 而不是规则规则清洗的第一版看起来准确率很高因为例子都写在正则里。但地质写法太多单位不缺、符号多样、颜色词经常断行。规则会越写越厚最后变成一坨没人敢动的黑匣子。LLM 清洗的优点是维护成本低换一个领域的样本只需要调整提示词示例。缺点也明显模型会“脑补”原文没有的内容尤其是当地质资料本来就是残缺的现场记录时。所以在模型选择上如果数据不能出内网就用本地部署常见做法是 vLLM 或者 Ollama 起一个 OpenAI 兼容接口模型用量化后的指令模型。小团队用一张消费级显卡跑 7B 到 14B 的量化模型对清洗够用字段标注阶段我倾向 14B 以上7B 在颜色和粒度细节上容易漏。代码里的模型 ID 先写local-model你部署是什么模型就换成对应 ID。3.2 清洗用提示词模板清洗提示词不要写“请润色这段文本”一旦这么写模型就会主动帮你优化语言把“灰绿色细砂岩”变成“灰绿色砂岩”。正确姿势是让它当“修理工”而不是“作家”。CLEAN_SYSTEM_PROMPT 你是一名地质资料整编员负责把录井、岩心等地质文本清洗成规范工整的专业记录。 规则 1. 只修正常见的 OCR 错字、断行和乱码。 2. 保留全部深度、颜色、岩性、地层、油气显示等原始词汇不得简化或改序。 3. 不得补充原文不存在的地质结论。 4. 输出只包含清洗后的文本不要解释不要加引号。用户消息直接放原始文本。这样模型的任务边界很清楚只做“断路恢复”不做“地质解释”。我在一个大模型的 system prompt 里还会加一条“如果原文本身不可读直接输出原文”避免它拿上下文硬猜。因为拿上下文硬猜出来的地名、深度后面标注再准也是假的。3.3 批处理脚本读目录、切块、调用 LLM、写回下面是完整的清洗最小脚本用 OpenAI SDK 访问本地推理服务。import json import re from pathlib import Path from openai import OpenAI client OpenAI( base_urlhttp://127.0.0.1:8000/v1, # 本地 vLLM / Ollama 兼容接口 api_keyEMPTY ) MODEL local-model def split_with_overlap(text, chunk_size600, overlap100): 按字符滑窗切块保留前后重叠避免句子在边界被截断。 if len(text) chunk_size: return [text] chunks [] start 0 step chunk_size - overlap while start len(text): end min(start chunk_size, len(text)) chunks.append(text[start:end]) if end len(text): break start step return chunks def clean_chunk(chunk): resp client.chat.completions.create( modelMODEL, messages[ {role: system, content: CLEAN_SYSTEM_PROMPT}, {role: user, content: chunk} ], temperature0, # 清洗必须确定性输出 max_tokens1024, # 一个切块一般不够 ) return resp.choices[0].message.content.strip() def clean_file(raw_path, output_path): raw_text raw_path.read_text(encodingutf-8, errorsignore) cleaned_parts [] for chunk in split_with_overlap(raw_text): try: cleaned_parts.append(clean_chunk(chunk)) except Exception as exc: cleaned_parts.append(chunk) # 失败时退回原文不静默丢弃 result \n.join(cleaned_parts) output_path.write_text(result, encodingutf-8) input_dir Path(raw_output) output_dir Path(cleaned_output) output_dir.mkdir(exist_okTrue) for file in input_dir.glob(*.txt): clean_file(file, output_dir / file.name)这段代码的核心在split_with_overlap。chunk_size600表示每块最多 600 字overlap100表示下一块从上一块往前 100 字开始。为什么要重叠因为地质文本常以“井段 岩性描述”为一组切在井段和岩性交界处模型可能看到一半就乱接。重叠能降低这种概率。副作用也很明显重叠区的文本会被清洗两次最后join时可能出现重复行。我的处理方式是在写回文件之前做一个去重按行比较如果相邻行重复且长度小于 20就删掉后一行如果完全重复且是整段则只保留一次。这块逻辑不复杂建议在批处理里加一个dedup_lines函数。temperature0是必须的清洗阶段要的是同一段文本每次跑都一样。但有些推理框架对temperature0会报错或输出异常我就改成 0.01效果差别不大。max_tokens1024对 600 字以内的文本是安全的但如果出现finish_reasonlength说明被截断得把这块文本重新拆小再跑。3.4 参数选择top_p、上下文窗口和并发清洗阶段我一般不开top_p让它用框架默认值。真正影响输出质量的是切块大小不是采样参数。上下文窗口这个参数最容易被高估。模型标称 32K 上下文不代表你把 32K 文本塞进去效果还好。我把块大小设为模型可用上下文的四分之一以内实际常用 500 到 800 字。这样每个块里只有几条地质记录模型能记住全部约束。并发不要贪。本地部署模型在并发过高时显存占用和队列排队会同时变差。常见做法是ThreadPoolExecutor(max_workers2)起两个线程如果后端是纯 CPU 推理就只用一个线程。清洗阶段没有实时交互需求用同步调用即可等得久一点不会出错。提示不要在生产批处理里用流式输出。流式输出适合前端渲染让用户看到打字效果离线清洗和标注用它只会让代码复杂度上升没有任何收益。4. 把大模型当标注员结构化输出、置信度自评和人工复审闭环清洗后的文本是“干净但不结构化”的下一步是把岩性、深度、地层、油气显示这些字段抽出来。这一阶段的核心不是让模型写一段分析而是让它按固定 JSON 结构吐结果。大模型天然不擅长做精确的字符偏移所以我把定位工作放在后处理用原文片段回查而不是相信模型给的 start/end 数字。4.1 标注提示词设计让模型输出 JSON而不是自由发挥我不建议在用户提示词里堆一大段“请帮我提取地质信息请认真思考……”更好的方式是给一个严格的 system prompt外加两个 few-shot 示例。示例每条只写“原文 → JSON”不要写中间解释。ANNOT_SYSTEM_PROMPT 你是地质资料标注员。请从给定文本中抽取地质实体输出 JSON。 输出格式 { entities: [ {text: 原文片段, type: depth|lithology|color|stratigraphy|oil_show|structure|fossil, confidence: 0.0-1.0, need_review: true/false} ] } 要求 1. text 必须严格来自原文不允许改写。 2. 一个实体只产生一个对象“灰绿色细砂岩”拆成 color 和 lithology 两个实体。 3. 拿不准时 confidence 给低值并设置 need_review 为 true。 4. 如果没有任何实体entities 为空列表。 5. 只输出 JSON不要输出任何解释。注意要求第 2 条“灰绿色细砂岩”拆成两个实体。这样后处理就能校验颜色词有没有被吞掉。如果不拆模型只输出一个 lithology 实体内部信息容易丢。few-shot 示例我一般放两条一条正常的“灰绿色细砂岩油迹”对应三个实体一条带否定的“未见油气显示”对应一个空 entities 或一个带否定色彩的实体。带否定的示例必须放很多模型的默认行为是把“未见显示”也标成“显示”。4.2 一个标注轮次的最小实现下面是解析模型输出并进行基础校验的代码。模型返回的文本需要用parse_model_json处理因为本地模型经常不老实会包一个 markdown 代码块或者前后写两句话。import json import re def parse_model_json(text): 从模型输出里提取 JSON容忍 markdown 代码块和前后废话。 text text.strip() if text.startswith(): text text.strip() if text.startswith(json): text text[4:] start text.find({) end text.rfind(}) if start -1 or end -1: raise ValueError(model output contains no JSON object) return json.loads(text[start:end 1]) def annotate_text(cleaned_text): resp client.chat.completions.create( modelMODEL, messages[ {role: system, content: ANNOT_SYSTEM_PROMPT}, {role: user, content: cleaned_text} ], temperature0, max_tokens2048, response_format{type: json_object} # 若本地后端不支持就去掉 ) content resp.choices[0].message.content data parse_model_json(content) # 基础校验实体必须存在 text 且非空 valid_entities [] for ent in data.get(entities, []): if not isinstance(ent.get(text), str) or ent.get(text).strip() : ent[need_review] True continue if ent.get(type) not in {depth, lithology, color, stratigraphy, oil_show, structure, fossil}: ent[type] unknown ent[need_review] True valid_entities.append(ent) data[entities] valid_entities return data这段代码里有两个关键点。一是parse_model_json只认第一个{到最后一个}。这是偷懒做法但对模型输出很实用前提是你已经把 max_tokens 控制在足够生成完整 JSON 的范围。二是基础校验没有用“置信度”过滤实体只是把明显异常标记成need_review。原因是模型给的置信度校准很差很多模型无论有没有把握都输出 0.9 以上不能拿它当硬阈值。response_format{type: json_object}这行在 vLLM 和部分 OpenAI 兼容服务里是支持的如果报错去掉这行也能跑但输出有时会带前后解说。去掉后parse_model_json的容错就派上用场了。4.3 置信度自评与抽检比例模型输出的confidence不能直接当“置信度”用但可以用它做排序。抽检样本按下面的优先级生成人工先看优先级高的时间不够就只看第一档。优先级样本条件抽检比例第一优先新井前 10 条、need_reviewtrue、confidence 0.6全部第二优先实体类型为 unknown、实体 text 在原文中找不到全部第三优先同一个批里随机抽取10%—20%人工复审不只是改标注还要记录“模型错在哪里”。我在复审界面上会让标注员给错误打一个原因标签比如“颜色词被简化”“深度区间合并”“地层名未归一”“实体漏标”。这个原因标签积累到一百条左右就能看出来提示词哪里写得不对。这里强烈建议用现成的文本标注工具承接人工复审不用自己写前端。把模型输出的 JSON 导入进去标注员只需要在界面上改实体类型和边界。流程是“LLM 粗标 → 人工修正”而不是“人工从零标”效率能差三倍以上。4.4 多轮修正把复审结果回灌成 few-shot 示例刚开始跑标注模型一定会犯错误。我会在每批次结束后把人工修正过的结果挑出十几个典型转换成新的 few-shot 示例追加到ANNOT_SYSTEM_PROMPT后面。这个动作比改提示词措辞更有效因为示例直接告诉模型“在这种原文字眼里人类的正确答案是什么”。回灌时注意两条。第一每轮只追加 2 到 3 个示例多了会挤压业务文本的上下文空间模型反而开始模仿示例的“句式”而不是处理任务。第二要分错误类型回灌这轮专门解决颜色词丢失就只放颜色相关的修正对下一轮再解决地层名归一。混在一起会让模型学得四不像。人工修正集攒到几百条以上再考虑微调。这个时间点最好在生产流程稳定跑通之后因为微调需要把修正数据统一成指令格式数据量不够时不但没提升反而会把模型原来的中文共地学知识冲淡。第一次做这个方向我建议先用 few-shot 跑完整个闭环等 KL 标和修正集都有了再谈微调。5. 地质语料标注避坑这五个细节会让前面的活白干我在前面几轮项目里踩过不少坑。下面五条不是“偶尔发生”的边缘问题都是会在量产时成批出现的类型。每一条按“现象 → 原因 → 解决”写方便你直接对着检查。5.1 “浅灰绿色”被简化成“灰色”岩性粒度逐级丢失现象模型输出的实体 text 是“灰色细砂岩”但原文写的是“浅灰绿色细砂岩”。人工抽检时会发现这不是一两条而是成批的颜色词被降级。更严重的是粒度也有类似问题“含砾粗砂岩”可能被改成“粗砂岩”。原因大模型生成时倾向于把表述往高频词靠拢。“灰绿色”在通用语料里不如“灰色”常见模型在不理解地质规范时会“顺手”简化。标注任务如果只看实体抽取的准确率这种错误很难发现因为实体类型还是 lithology只是内部修饰丢了。解决提示词里明确要求“实体 text 必须逐字取自原文不得简化或改序”。后处理再加一条硬性校验如果原文包含“灰绿色”“浅灰”“细粒”等修饰词实体 text 也必须包含对应词否则把该实体need_review置为 true。这种硬校验不依赖模型自觉比一百句“请认真”都有用。5.2 深度区间被合并一口井的层位归属错乱现象一个上下文块里出现两个深度区间模型输出一个实体“2865—3020m”更麻烦的是后面挂接的地层和岩性也随之错乱。人工看起来像是“模型把两段当成一段了”。原因提示词没有把“一个深度区间”约束成独立实体模型按阅读习惯把连续出现的深度合并。另外切块时没有以深度边界为准导致一个块里堆了多个井段的描述。解决切块按井段来一个块尽量只包含一个完整的深度段。标注 schema 把 depth 设为必选实体。推理后做规则校验统计原文中匹配深度正则的数量再统计模型输出里 depth 实体的数量如果输出数量小于原文数量直接把该条整体标记为需人工复审。这个“数量对不上就送人工”的策略能拦截大部分区间合并问题。5.3 地层名不统一同一套层位被标成好几种写法现象“沙河街组四段”“沙四段”“沙河街 4 段”被标成三种实体。下游做层位统计时同一个层位被拆成三份数据根本没法用。原因提示词没给枚举模型把普通中文写作习惯带到了结果里。地质地层名的规范写法通常只存在于本地项目的地质词典里模型不可能从预训练中学到。解决在标注提示词里放一份“本批次的规范地层名单”并给一个别名映射表。模型先把原文片段抽取出来后处理再用字典做standard aliases.get(entity_text, entity_text)归一。如果实体不在字典里不打上“未知”而是要need_reviewTrue让地质人员确认。千万不要让模型在生成阶段自己归一到“标准名”它会编一个看起来像标准名的结果。5.4 上下文一长模型就开始编现象输入 5000 字以上的报告模型前 70% 的标注很准后 30% 开始出现幻觉实体甚至把“未见油气显示”反标成“油气显示”。原因长上下文的有效工作区没有宣传里那么大。本地量化模型在长文本后段早期约束的注意力会衰减再加上地质文本本身碎片化模型就把“未见”这类否定词忽视了。解决清洗和标注都用小块。单块控制在 500 到 800 字以内标注阶段的块可以更小450 字左右。对包含“未见”“无”“不含”等否定词的文本后处理必须强制检查这几个词并把油气显示、化石类实体的need_review置为 true。否定场景是漏标率最高的地方宁可多送人工不要让模型猜。5.5 结果看着对一跑回归就垮现象抽检 10 条错 2 条看起来准确率 80%还行。但把几百口井的结果汇总后同一个层位的实体缺失率很高统计出来的柱状图和原始录井剖面图对不上。原因人工抽检只看了准确率没看漏标也就是召回率。LLM 的默认策略是只输出它最有把握的实体难样本宁愿不输出。这时候表面准确率高但漏标在累积最终体现在下游统计里就是“少了很多层”。解决建立以召回为主的评价机制。人工抽检时除了看模型标了什么还要看它漏了什么。一个简单办法是拿金标集对比金标里的实体模型是不是也标出来了只要有一条漏掉就记一个召回漏损。我在生产流程里要求召回率必须大于 0.9 才允许全量跑否则先回来调提示词。这也是下一章验收方案的基础。6. 结果验证与验收用 50 条人工金标当尺子无论提示词写得多漂亮我都会在最后用金标集收口。金标不用多但要有代表性。我一般从三个不同区块或钻井类型的井中各抽 15 到 20 条清洗后文本请一位熟悉本区地质的人按第二章的 schema 标一遍形成 50 到 60 条金标。注意金标标的是清洗后的文本不是原始 OCR否则连错字都一起学进去了。6.1 建金标与统一口径金标建成后先把实体文本做一层归一深度区间的破折号统一成-全角符号转半角地层名按别名表归到标准名。这一步不做后面计算 F1 时同一个实体因为一个小破折号差异就被算成两回事结果完全没有参考价值。6.2 实体级 F1 与采纳阈值最稳的验证方式不是看模型输出的 JSON 整体有多接近而是做实体级 F1 计算。def entity_f1(gold_ents, pred_ents): gold {(e[type], normalize(e[text])) for e in gold_ents} pred {(e[type], normalize(e[text])) for e in pred_ents} tp len(gold pred) precision tp / len(pred) if pred else 0 recall tp / len(gold) if gold else 0 f1 2 * precision * recall / (precision recall) if (precision recall) else 0 return {precision: precision, recall: recall, f1: f1}normalize做的事就是把全角转半角、破折号统一、去空格。这样比较的是“语义实体是否一致”而不是“字符串是否一模一样”。我的验收习惯是F1 小于 0.75先不要铺开生产0.75 到 0.9 之间只能让模型做粗筛人工必须全量看疑点样本大于 0.9才允许用“自动标注 抽检”的方式量产。偏低的一方是地质人员复核而不是模型再跑一遍。这套流程走完模型输出会沉淀成两块资产一块是人工修正后的金标热数据将来可以做微调另一块是置信度排序后的待审列表直接派给标注员。我第一次做的时候贪快用一套效果还行的提示词直接全量跑以为“高置信就不用看了”拿金标一测召回率只有 0.62意味着四成实体根本没有产出。后来老老实实把人工复审闭环加上召回才回到 0.9 以上。这个教训让我现在无论模型参数调得多顺手都先拿 50 条金标当尺子量一遍。希望这套“先切块、再清洗、后约束抽取、最后金标验收”的流程能帮你少走一次全量重来的弯路。本文还有配套的精品资源点击获取
返回列表