brat数据标注:从入门到实战的技术解析与避坑指南

1次阅读
没有评论

共计 2209 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

低效标注的血泪教训

去年参与某三甲医院电子病历项目时,我们曾因标注不规范导致模型性能下降 40%。标注人员将『Ⅱ型糖尿病』时而标注为疾病实体,时而拆分成『Ⅱ型』和『糖尿病』,最终 F1 值仅 0.52。更糟糕的是某基因突变检测项目,因未统一标注 BRCA1 基因的别名(如 BRCA1_HUMAN),召回率直接归零。

brat 数据标注:从入门到实战的技术解析与避坑指南

工具选型:为什么选择 brat

  • 生物医学特化:原生支持实体嵌套(如『HER2 阳性乳腺癌』中的基因与疾病)
  • 关系可视化:通过箭头直观显示『药物 - 不良反应』等复杂关系
  • 轻量化部署:单机运行无需 SaaS 订阅费(对比 Prodigy 每年 $490/ 人)
pie
    title 生物医学标注工具使用占比
    "brat" : 68
    "Label Studio" : 22
    "Prodigy" : 10

核心原理:standoff 标注详解

  1. 文本与标注分离:原始文本存为.txt,标注数据存为.ann
  2. 坐标定位:T1 Disease 25 32 肺癌 表示从第 25 到 32 字符的文本
  3. 关系定义:R1 Cause Arg1:T1 Arg2:T2 建立实体间因果关系

规范编写黄金法则

# annotation.conf 关键配置示例
[entities]
# 必须定义允许嵌套的实体
Gene
Disease

[relations]
# 生物医学典型关系
Cause|Arg1:Gene, Arg2:Disease
Treatment|Arg1:Drug, Arg2:Disease

[events]
# 临床事件定义
Diagnosis|Theme:Disease

Python 交互实战

import requests
from typing import List, Dict

class BratClient:
    def __init__(self, base_url: str = "http://localhost:8001"):
        self.session = requests.Session()
        self.base_url = base_url

    def get_annotations(self, doc_id: str) -> Dict:
        try:
            res = self.session.get(f"{self.base_url}/data/{doc_id}.ann",
                timeout=10
            )
            res.raise_for_status()
            return self._parse_ann(res.text)
        except requests.exceptions.RequestException as e:
            print(f"API 请求失败: {str(e)}")
            return {}

    @staticmethod
    def _parse_ann(ann_text: str) -> Dict:
        """解析标准 brat .ann 格式"""
        entities = []
        for line in ann_text.split('\n'):
            if not line.startswith('T'):
                continue
            parts = line.split('\t')
            ent_id = parts[0]
            type_span = parts[1].split()
            entities.append({
                "id": ent_id,
                "type": type_span[0],
                "start": int(type_span[1]),
                "end": int(type_span[2]),
                "text": parts[2]
            })
        return {"entities": entities}

性能优化双刃剑

内存管理方案

  1. 分块加载:每处理 5000 字符自动保存并清空内存
  2. 懒加载:仅当标注者查看具体文档时才加载对应.ann 文件

多标注者协作

  • Git 分支策略:每个标注者在独立分支工作
  • 冲突检测 :定期执行git diff --check 查找重叠标注

避坑指南

实体嵌套三大陷阱

  1. 跨层级嵌套:『EGFR 突变型肺癌』应标记为:
    T1 Gene 0 4    EGFR
    T2 Disease 0 9 EGFR 突变型肺癌
  2. 部分重叠:『BRAF V600E 突变』中『V600E』需单独标注为蛋白质变异体
  3. 术语变异:『赫赛汀(曲妥珠单抗)』需建立同义词链接

BERT 对齐秘诀

# 处理 BERT 分词导致的 offset 错位
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

def align_offsets(text: str, start: int, end: int) -> tuple:
    pre_text = text[:start]
    tokenized = tokenizer.tokenize(pre_text + text[start:end])
    new_start = len(tokenizer.tokenize(pre_text))
    return (new_start, new_start + len(tokenizer.tokenize(text[start:end])))

开放问题

  1. 如何量化标注者间一致性?Krippendorff’s alpha 是否适合医学文本?
  2. 当标注指南需要频繁更新时,如何保证历史标注仍然可用?
  3. 对于『可能』『疑似』等不确定表述,应该建立独立标签还是通过置信度表示?

经过三个月实战,我们的标注效率从每天 200 条提升到 650 条,关键是严格执行了本文的嵌套实体规范和定期交叉校验。特别提醒:brat 的标注界面在 Chrome 114+ 版本存在渲染 bug,建议使用 Firefox 进行标注作业。

正文完
 0
评论(没有评论)